Bug 1966784: Vendor application-services fa8a72a r=adw
Differential Revision: https://phabricator.services.mozilla.com/D249694
This commit is contained in:
committed by
dakatsuka.birchill@mozilla.com
parent
70e402a241
commit
8565135320
@@ -70,9 +70,9 @@ git = "https://github.com/jfkthame/mapped_hyph.git"
|
||||
rev = "eff105f6ad7ec9b79816cfc1985a28e5340ad14b"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source."git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197"]
|
||||
[source."git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285"]
|
||||
git = "https://github.com/mozilla/application-services"
|
||||
rev = "40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
rev = "fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source."git+https://github.com/mozilla/audioipc?rev=e6f44a2bd1e57d11dfc737632a9e849077632330"]
|
||||
|
||||
32
Cargo.lock
generated
32
Cargo.lock
generated
@@ -1894,7 +1894,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "error-support"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"error-support-macros",
|
||||
"lazy_static",
|
||||
@@ -1906,7 +1906,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "error-support-macros"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2025,7 +2025,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "firefox-versioning"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"thiserror 1.999.999",
|
||||
@@ -3365,7 +3365,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "interrupt-support"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"parking_lot",
|
||||
@@ -5086,7 +5086,7 @@ checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
|
||||
[[package]]
|
||||
name = "payload-support"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -5589,7 +5589,7 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||
[[package]]
|
||||
name = "relevancy"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.999",
|
||||
@@ -5614,7 +5614,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "remote_settings"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
@@ -5916,7 +5916,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "search"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"error-support",
|
||||
"firefox-versioning",
|
||||
@@ -6208,7 +6208,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "sql-support"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"interrupt-support",
|
||||
"lazy_static",
|
||||
@@ -6414,7 +6414,7 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
[[package]]
|
||||
name = "suggest"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -6466,7 +6466,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "sync-guid"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"base64 0.21.999",
|
||||
"rand",
|
||||
@@ -6477,7 +6477,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "sync15"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"error-support",
|
||||
@@ -6517,7 +6517,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tabs"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"error-support",
|
||||
@@ -6861,7 +6861,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
[[package]]
|
||||
name = "types"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"rusqlite 0.33.0",
|
||||
"serde",
|
||||
@@ -7264,7 +7264,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
[[package]]
|
||||
name = "viaduct"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"ffi-support",
|
||||
"log",
|
||||
@@ -7434,7 +7434,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "webext-storage"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=40ae79e2825fa242a349e17fcfc84fb99fd92197#40ae79e2825fa242a349e17fcfc84fb99fd92197"
|
||||
source = "git+https://github.com/mozilla/application-services?rev=fa8a72a77f88bc8b3743b50d76fb85cb37a38285#fa8a72a77f88bc8b3743b50d76fb85cb37a38285"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"error-support",
|
||||
|
||||
18
Cargo.toml
18
Cargo.toml
@@ -262,14 +262,14 @@ wr_malloc_size_of = { path = "gfx/wr/wr_malloc_size_of" }
|
||||
objc = { git = "https://github.com/glandium/rust-objc", rev = "4de89f5aa9851ceca4d40e7ac1e2759410c04324" }
|
||||
|
||||
# application-services overrides to make updating them all simpler.
|
||||
interrupt-support = { git = "https://github.com/mozilla/application-services", rev = "40ae79e2825fa242a349e17fcfc84fb99fd92197" }
|
||||
relevancy = { git = "https://github.com/mozilla/application-services", rev = "40ae79e2825fa242a349e17fcfc84fb99fd92197" }
|
||||
search = { git = "https://github.com/mozilla/application-services", rev = "40ae79e2825fa242a349e17fcfc84fb99fd92197" }
|
||||
sql-support = { git = "https://github.com/mozilla/application-services", rev = "40ae79e2825fa242a349e17fcfc84fb99fd92197" }
|
||||
suggest = { git = "https://github.com/mozilla/application-services", rev = "40ae79e2825fa242a349e17fcfc84fb99fd92197" }
|
||||
sync15 = { git = "https://github.com/mozilla/application-services", rev = "40ae79e2825fa242a349e17fcfc84fb99fd92197" }
|
||||
tabs = { git = "https://github.com/mozilla/application-services", rev = "40ae79e2825fa242a349e17fcfc84fb99fd92197" }
|
||||
viaduct = { git = "https://github.com/mozilla/application-services", rev = "40ae79e2825fa242a349e17fcfc84fb99fd92197" }
|
||||
webext-storage = { git = "https://github.com/mozilla/application-services", rev = "40ae79e2825fa242a349e17fcfc84fb99fd92197" }
|
||||
interrupt-support = { git = "https://github.com/mozilla/application-services", rev = "fa8a72a77f88bc8b3743b50d76fb85cb37a38285" }
|
||||
relevancy = { git = "https://github.com/mozilla/application-services", rev = "fa8a72a77f88bc8b3743b50d76fb85cb37a38285" }
|
||||
search = { git = "https://github.com/mozilla/application-services", rev = "fa8a72a77f88bc8b3743b50d76fb85cb37a38285" }
|
||||
sql-support = { git = "https://github.com/mozilla/application-services", rev = "fa8a72a77f88bc8b3743b50d76fb85cb37a38285" }
|
||||
suggest = { git = "https://github.com/mozilla/application-services", rev = "fa8a72a77f88bc8b3743b50d76fb85cb37a38285" }
|
||||
sync15 = { git = "https://github.com/mozilla/application-services", rev = "fa8a72a77f88bc8b3743b50d76fb85cb37a38285" }
|
||||
tabs = { git = "https://github.com/mozilla/application-services", rev = "fa8a72a77f88bc8b3743b50d76fb85cb37a38285" }
|
||||
viaduct = { git = "https://github.com/mozilla/application-services", rev = "fa8a72a77f88bc8b3743b50d76fb85cb37a38285" }
|
||||
webext-storage = { git = "https://github.com/mozilla/application-services", rev = "fa8a72a77f88bc8b3743b50d76fb85cb37a38285" }
|
||||
|
||||
allocator-api2 = { path = "third_party/rust/allocator-api2" }
|
||||
|
||||
@@ -18,6 +18,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
TestUtils: "resource://testing-common/TestUtils.sys.mjs",
|
||||
UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
|
||||
UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
|
||||
YelpSubjectType: "resource://gre/modules/RustSuggest.sys.mjs",
|
||||
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
||||
});
|
||||
|
||||
@@ -960,6 +961,7 @@ class _QuickSuggestTestUtils {
|
||||
isSuggestedIndexRelativeToGroup = true,
|
||||
originalUrl = undefined,
|
||||
displayUrl = undefined,
|
||||
suggestedType = lazy.YelpSubjectType.SERVICE,
|
||||
}) {
|
||||
const utmParameters = "&utm_medium=partner&utm_source=mozilla";
|
||||
|
||||
@@ -1012,6 +1014,7 @@ class _QuickSuggestTestUtils {
|
||||
0.2, // score
|
||||
false, // hasLocationSign
|
||||
false, // subjectExactMatch
|
||||
suggestedType, // subjectType
|
||||
"find_loc" // locationParam
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,6 +68,10 @@
|
||||
:members:
|
||||
:exclude-members: SuggestionProvider
|
||||
```
|
||||
```{js:autoclass} RustSuggest.sys.YelpSubjectType
|
||||
:members:
|
||||
:exclude-members: YelpSubjectType
|
||||
```
|
||||
```{js:autoclass} RustSuggest.sys.Network
|
||||
:members:
|
||||
:exclude-members: Network
|
||||
|
||||
File diff suppressed because one or more lines are too long
218
third_party/rust/remote_settings/src/client.rs
vendored
218
third_party/rust/remote_settings/src/client.rs
vendored
@@ -294,6 +294,12 @@ impl<C: ApiClient> RemoteSettingsClient<C> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_last_modified_timestamp(&self) -> Result<Option<u64>> {
|
||||
let mut inner = self.inner.lock();
|
||||
let collection_url = inner.api_client.collection_url();
|
||||
inner.storage.get_last_modified_timestamp(&collection_url)
|
||||
}
|
||||
|
||||
/// Synchronizes the local collection with the remote server by performing the following steps:
|
||||
/// 1. Fetches the last modified timestamp of the collection from local storage.
|
||||
/// 2. Fetches the changeset from the remote server based on the last modified timestamp.
|
||||
@@ -507,9 +513,15 @@ impl RemoteSettingsClient<ViaductApiClient> {
|
||||
Self::new_from_parts(collection_name, storage, jexl_filter, api_client)
|
||||
}
|
||||
|
||||
pub fn update_config(&self, server_url: BaseUrl, bucket_name: String) -> Result<()> {
|
||||
pub fn update_config(
|
||||
&self,
|
||||
server_url: BaseUrl,
|
||||
bucket_name: String,
|
||||
context: Option<RemoteSettingsContext>,
|
||||
) -> Result<()> {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.api_client = ViaductApiClient::new(server_url, &bucket_name, &self.collection_name);
|
||||
inner.jexl_filter = JexlFilter::new(context);
|
||||
inner.storage.empty()
|
||||
}
|
||||
}
|
||||
@@ -554,12 +566,12 @@ impl ViaductApiClient {
|
||||
|
||||
fn make_request(&mut self, url: Url) -> Result<Response> {
|
||||
log::trace!("make_request: {url}");
|
||||
self.ensure_no_backoff()?;
|
||||
self.remote_state.ensure_no_backoff()?;
|
||||
|
||||
let req = Request::get(url);
|
||||
let resp = req.send()?;
|
||||
|
||||
self.handle_backoff_hint(&resp)?;
|
||||
self.remote_state.handle_backoff_hint(&resp)?;
|
||||
|
||||
if resp.is_success() {
|
||||
Ok(resp)
|
||||
@@ -570,46 +582,6 @@ impl ViaductApiClient {
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_no_backoff(&mut self) -> Result<()> {
|
||||
if let BackoffState::Backoff {
|
||||
observed_at,
|
||||
duration,
|
||||
} = self.remote_state.backoff
|
||||
{
|
||||
let elapsed_time = observed_at.elapsed();
|
||||
if elapsed_time >= duration {
|
||||
self.remote_state.backoff = BackoffState::Ok;
|
||||
} else {
|
||||
let remaining = duration - elapsed_time;
|
||||
return Err(Error::BackoffError(remaining.as_secs()));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_backoff_hint(&mut self, response: &Response) -> Result<()> {
|
||||
let extract_backoff_header = |header| -> Result<u64> {
|
||||
Ok(response
|
||||
.headers
|
||||
.get_as::<u64, _>(header)
|
||||
.transpose()
|
||||
.unwrap_or_default() // Ignore number parsing errors.
|
||||
.unwrap_or(0))
|
||||
};
|
||||
// In practice these two headers are mutually exclusive.
|
||||
let backoff = extract_backoff_header(HEADER_BACKOFF)?;
|
||||
let retry_after = extract_backoff_header(HEADER_RETRY_AFTER)?;
|
||||
let max_backoff = backoff.max(retry_after);
|
||||
|
||||
if max_backoff > 0 {
|
||||
self.remote_state.backoff = BackoffState::Backoff {
|
||||
observed_at: Instant::now(),
|
||||
duration: Duration::from_secs(max_backoff),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiClient for ViaductApiClient {
|
||||
@@ -803,14 +775,14 @@ impl Client {
|
||||
|
||||
fn make_request(&self, url: Url) -> Result<Response> {
|
||||
let mut current_remote_state = self.remote_state.lock();
|
||||
self.ensure_no_backoff(&mut current_remote_state.backoff)?;
|
||||
current_remote_state.ensure_no_backoff()?;
|
||||
drop(current_remote_state);
|
||||
|
||||
let req = Request::get(url);
|
||||
let resp = req.send()?;
|
||||
|
||||
let mut current_remote_state = self.remote_state.lock();
|
||||
self.handle_backoff_hint(&resp, &mut current_remote_state.backoff)?;
|
||||
current_remote_state.handle_backoff_hint(&resp)?;
|
||||
|
||||
if resp.is_success() {
|
||||
Ok(resp)
|
||||
@@ -821,50 +793,6 @@ impl Client {
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_no_backoff(&self, current_state: &mut BackoffState) -> Result<()> {
|
||||
if let BackoffState::Backoff {
|
||||
observed_at,
|
||||
duration,
|
||||
} = *current_state
|
||||
{
|
||||
let elapsed_time = observed_at.elapsed();
|
||||
if elapsed_time >= duration {
|
||||
*current_state = BackoffState::Ok;
|
||||
} else {
|
||||
let remaining = duration - elapsed_time;
|
||||
return Err(Error::BackoffError(remaining.as_secs()));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_backoff_hint(
|
||||
&self,
|
||||
response: &Response,
|
||||
current_state: &mut BackoffState,
|
||||
) -> Result<()> {
|
||||
let extract_backoff_header = |header| -> Result<u64> {
|
||||
Ok(response
|
||||
.headers
|
||||
.get_as::<u64, _>(header)
|
||||
.transpose()
|
||||
.unwrap_or_default() // Ignore number parsing errors.
|
||||
.unwrap_or(0))
|
||||
};
|
||||
// In practice these two headers are mutually exclusive.
|
||||
let backoff = extract_backoff_header(HEADER_BACKOFF)?;
|
||||
let retry_after = extract_backoff_header(HEADER_RETRY_AFTER)?;
|
||||
let max_backoff = backoff.max(retry_after);
|
||||
|
||||
if max_backoff > 0 {
|
||||
*current_state = BackoffState::Backoff {
|
||||
observed_at: Instant::now(),
|
||||
duration: Duration::from_secs(max_backoff),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores all the endpoints for a Remote Settings server
|
||||
@@ -1027,6 +955,48 @@ impl Default for RemoteState {
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteState {
|
||||
pub fn handle_backoff_hint(&mut self, response: &Response) -> Result<()> {
|
||||
let extract_backoff_header = |header| -> Result<u64> {
|
||||
Ok(response
|
||||
.headers
|
||||
.get_as::<u64, _>(header)
|
||||
.transpose()
|
||||
.unwrap_or_default() // Ignore number parsing errors.
|
||||
.unwrap_or(0))
|
||||
};
|
||||
// In practice these two headers are mutually exclusive.
|
||||
let backoff = extract_backoff_header(HEADER_BACKOFF)?;
|
||||
let retry_after = extract_backoff_header(HEADER_RETRY_AFTER)?;
|
||||
let max_backoff = backoff.max(retry_after);
|
||||
|
||||
if max_backoff > 0 {
|
||||
self.backoff = BackoffState::Backoff {
|
||||
observed_at: Instant::now(),
|
||||
duration: Duration::from_secs(max_backoff),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ensure_no_backoff(&mut self) -> Result<()> {
|
||||
if let BackoffState::Backoff {
|
||||
observed_at,
|
||||
duration,
|
||||
} = self.backoff
|
||||
{
|
||||
let elapsed_time = observed_at.elapsed();
|
||||
if elapsed_time >= duration {
|
||||
self.backoff = BackoffState::Ok;
|
||||
} else {
|
||||
let remaining = duration - elapsed_time;
|
||||
return Err(Error::BackoffError(remaining.as_secs()));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Used in handling backoff responses from the Remote Settings server.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum BackoffState {
|
||||
@@ -2040,6 +2010,76 @@ mod jexl_tests {
|
||||
Some(vec![])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_jexl_context() {
|
||||
let mut api_client = MockApiClient::new();
|
||||
let records = vec![RemoteSettingsRecord {
|
||||
id: "record-0001".into(),
|
||||
last_modified: 100,
|
||||
deleted: false,
|
||||
attachment: None,
|
||||
fields: serde_json::json!({
|
||||
"filter_expression": "env.country == \"US\""
|
||||
})
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.clone(),
|
||||
}];
|
||||
let changeset = ChangesetResponse {
|
||||
changes: records.clone(),
|
||||
timestamp: 42,
|
||||
metadata: CollectionMetadata::default(),
|
||||
};
|
||||
api_client.expect_collection_url().returning(|| {
|
||||
"http://rs.example.com/v1/buckets/main/collections/test-collection".into()
|
||||
});
|
||||
api_client.expect_fetch_changeset().returning({
|
||||
let changeset = changeset.clone();
|
||||
move |timestamp| {
|
||||
assert_eq!(timestamp, None);
|
||||
Ok(changeset.clone())
|
||||
}
|
||||
});
|
||||
api_client.expect_is_prod_server().returning(|| Ok(false));
|
||||
|
||||
let context = RemoteSettingsContext {
|
||||
country: Some("US".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut storage = Storage::new(":memory:".into());
|
||||
let _ = storage.insert_collection_content(
|
||||
"http://rs.example.com/v1/buckets/main/collections/test-collection",
|
||||
&records,
|
||||
42,
|
||||
CollectionMetadata::default(),
|
||||
);
|
||||
|
||||
let rs_client = RemoteSettingsClient::new_from_parts(
|
||||
"test-collection".into(),
|
||||
storage,
|
||||
JexlFilter::new(Some(context)),
|
||||
api_client,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
rs_client.get_records(false).expect("Error getting records"),
|
||||
Some(records)
|
||||
);
|
||||
|
||||
// We can't call `update_config` directly, since that only works with a real API client.
|
||||
// Instead, just execute the code from that method that updates the JEXL filter.
|
||||
rs_client.inner.lock().jexl_filter = JexlFilter::new(Some(RemoteSettingsContext {
|
||||
country: Some("UK".to_string()),
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
assert_eq!(
|
||||
rs_client.get_records(false).expect("Error getting records"),
|
||||
Some(vec![])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "signatures")]
|
||||
|
||||
@@ -165,4 +165,8 @@ impl BaseUrl {
|
||||
// error for cannot-be-a-base URLs.
|
||||
self.url.path_segments_mut().unwrap()
|
||||
}
|
||||
|
||||
pub fn query_pairs_mut(&mut self) -> url::form_urlencoded::Serializer<'_, url::UrlQuery<'_>> {
|
||||
self.url.query_pairs_mut()
|
||||
}
|
||||
}
|
||||
|
||||
96
third_party/rust/remote_settings/src/service.rs
vendored
96
third_party/rust/remote_settings/src/service.rs
vendored
@@ -3,16 +3,18 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
collections::{HashMap, HashSet},
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
use camino::Utf8PathBuf;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
use viaduct::Request;
|
||||
|
||||
use crate::{
|
||||
config::BaseUrl, storage::Storage, RemoteSettingsClient, RemoteSettingsConfig2,
|
||||
RemoteSettingsContext, RemoteSettingsServer, Result,
|
||||
client::RemoteState, config::BaseUrl, error::Error, storage::Storage, RemoteSettingsClient,
|
||||
RemoteSettingsConfig2, RemoteSettingsContext, RemoteSettingsServer, Result,
|
||||
};
|
||||
|
||||
/// Internal Remote settings service API
|
||||
@@ -25,6 +27,7 @@ struct RemoteSettingsServiceInner {
|
||||
base_url: BaseUrl,
|
||||
bucket_name: String,
|
||||
app_context: Option<RemoteSettingsContext>,
|
||||
remote_state: RemoteState,
|
||||
/// Weakrefs for all clients that we've created. Note: this stores the
|
||||
/// top-level/public `RemoteSettingsClient` structs rather than `client::RemoteSettingsClient`.
|
||||
/// The reason for this is that we return Arcs to the public struct to the foreign code, so we
|
||||
@@ -51,6 +54,7 @@ impl RemoteSettingsService {
|
||||
base_url,
|
||||
bucket_name,
|
||||
app_context: config.app_context,
|
||||
remote_state: RemoteState::default(),
|
||||
clients: vec![],
|
||||
}),
|
||||
}
|
||||
@@ -81,13 +85,30 @@ impl RemoteSettingsService {
|
||||
// Make sure we only sync each collection once, even if there are multiple clients
|
||||
let mut synced_collections = HashSet::new();
|
||||
|
||||
// TODO: poll the server using `/buckets/monitor/collections/changes/changeset` to fetch
|
||||
// the current timestamp for all collections. That way we can avoid fetching collections
|
||||
// we know haven't changed and also pass the `?_expected{ts}` param to the server.
|
||||
let mut inner = self.inner.lock();
|
||||
let changes = inner.fetch_changes()?;
|
||||
let change_map: HashMap<_, _> = changes
|
||||
.changes
|
||||
.iter()
|
||||
.map(|c| ((c.collection.as_str(), &c.bucket), c.last_modified))
|
||||
.collect();
|
||||
let bucket_name = inner.bucket_name.clone();
|
||||
|
||||
for client in self.inner.lock().active_clients() {
|
||||
if synced_collections.insert(client.collection_name()) {
|
||||
client.internal.sync()?;
|
||||
for client in inner.active_clients() {
|
||||
let client = &client.internal;
|
||||
let collection_name = client.collection_name();
|
||||
if let Some(client_last_modified) = client.get_last_modified_timestamp()? {
|
||||
if let Some(server_last_modified) = change_map.get(&(collection_name, &bucket_name))
|
||||
{
|
||||
if client_last_modified != *server_last_modified {
|
||||
log::trace!("skipping up-to-date collection: {collection_name}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if synced_collections.insert(collection_name.to_string()) {
|
||||
log::trace!("syncing collection: {collection_name}");
|
||||
client.sync()?;
|
||||
}
|
||||
}
|
||||
Ok(synced_collections.into_iter().collect())
|
||||
@@ -105,12 +126,15 @@ impl RemoteSettingsService {
|
||||
let bucket_name = config.bucket_name.unwrap_or_else(|| String::from("main"));
|
||||
let mut inner = self.inner.lock();
|
||||
for client in inner.active_clients() {
|
||||
client
|
||||
.internal
|
||||
.update_config(base_url.clone(), bucket_name.clone())?;
|
||||
client.internal.update_config(
|
||||
base_url.clone(),
|
||||
bucket_name.clone(),
|
||||
config.app_context.clone(),
|
||||
)?;
|
||||
}
|
||||
inner.base_url = base_url;
|
||||
inner.bucket_name = bucket_name;
|
||||
inner.app_context = config.app_context;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -131,4 +155,52 @@ impl RemoteSettingsServiceInner {
|
||||
});
|
||||
active_clients
|
||||
}
|
||||
|
||||
fn fetch_changes(&mut self) -> Result<Changes> {
|
||||
let mut url = self.base_url.clone();
|
||||
url.path_segments_mut()
|
||||
.push("buckets")
|
||||
.push("monitor")
|
||||
.push("collections")
|
||||
.push("changes")
|
||||
.push("changeset");
|
||||
// For now, always use `0` for the expected value. This means we'll get updates based on
|
||||
// the default TTL of 1 hour.
|
||||
//
|
||||
// Eventually, we should add support for push notifications and use the timestamp from the
|
||||
// notification.
|
||||
url.query_pairs_mut().append_pair("_expected", "0");
|
||||
let url = url.into_inner();
|
||||
log::trace!("make_request: {url}");
|
||||
self.remote_state.ensure_no_backoff()?;
|
||||
|
||||
let req = Request::get(url);
|
||||
let resp = req.send()?;
|
||||
|
||||
self.remote_state.handle_backoff_hint(&resp)?;
|
||||
|
||||
if resp.is_success() {
|
||||
Ok(resp.json()?)
|
||||
} else {
|
||||
Err(Error::ResponseError(format!(
|
||||
"status code: {}",
|
||||
resp.status
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Data from the changes endpoint
|
||||
///
|
||||
/// https://remote-settings.readthedocs.io/en/latest/client-specifications.html#endpoints
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Changes {
|
||||
changes: Vec<ChangesCollection>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ChangesCollection {
|
||||
collection: String,
|
||||
bucket: String,
|
||||
last_modified: u64,
|
||||
}
|
||||
|
||||
2
third_party/rust/search/.cargo-checksum.json
vendored
2
third_party/rust/search/.cargo-checksum.json
vendored
@@ -1 +1 @@
|
||||
{"files":{"Cargo.toml":"ac0ac2103375f1c3906436b53627f88515da74864dd3f86ebb2a18952ba72b30","README.md":"d59a6ad6232a86a7bd3632ca62c44ba8bd466615c5d47ce0d836b270bac5562c","android/build.gradle":"e3b617d653aa0221f2229bb16c2fd635003fe82d0274c4b9a6f2d8154851985a","android/proguard-rules.pro":"1cf8c57e8f79c250b0af9c1a5a4edad71a5c348a79ab70243b6bae086c150ad2","android/src/main/AndroidManifest.xml":"0a05039a6124be0296764c2b0f41e863b5538d75e6164dd9ae945b59d983c318","src/configuration_overrides_types.rs":"220a5e12ee3deb309a1571c5820ec5132c959f56667c4c48f997bbe2be0c7eeb","src/configuration_types.rs":"a495199fc19cf9ce1aefe41058a38a0ffad4eb6f719fac11c11dcc3cfe4f234a","src/environment_matching.rs":"5a1ade9a900942c62e8740597528a34df6fb3fdb72c801a647a3386acd42fcc8","src/error.rs":"d3c1eda7a8da15446a321139d4d29dd9ceee99e916519690d5eb2d45ed628598","src/filter.rs":"88a39e594397708db888726cac00ad1e5b4a892c5a121c96cc11d20f41851b45","src/lib.rs":"9c83780a74048fbbc7bbba5706067b9dc5db2ae25a0cc751687d2738903723b4","src/selector.rs":"e065e05aec54e01fd106d453ece2cfee72793c8a47569cc32ac016aef3ac265b","src/sort_helpers.rs":"12d41c34fc2ca5387edc248189335fb11702618b7253f8b486f0c84576084faa","src/types.rs":"8721ccf9443b28435ba211d5705f4a6c82eb0354ba1d100045438800c7e2bf9a","uniffi.toml":"96f1cd569483ff59e3c73852f085a03889fa24a2ce20ff7a3003799a9f48a51e"},"package":null}
|
||||
{"files":{"Cargo.toml":"ac0ac2103375f1c3906436b53627f88515da74864dd3f86ebb2a18952ba72b30","README.md":"d59a6ad6232a86a7bd3632ca62c44ba8bd466615c5d47ce0d836b270bac5562c","android/build.gradle":"e3b617d653aa0221f2229bb16c2fd635003fe82d0274c4b9a6f2d8154851985a","android/proguard-rules.pro":"1cf8c57e8f79c250b0af9c1a5a4edad71a5c348a79ab70243b6bae086c150ad2","android/src/main/AndroidManifest.xml":"0a05039a6124be0296764c2b0f41e863b5538d75e6164dd9ae945b59d983c318","src/configuration_overrides_types.rs":"220a5e12ee3deb309a1571c5820ec5132c959f56667c4c48f997bbe2be0c7eeb","src/configuration_types.rs":"a495199fc19cf9ce1aefe41058a38a0ffad4eb6f719fac11c11dcc3cfe4f234a","src/environment_matching.rs":"5a1ade9a900942c62e8740597528a34df6fb3fdb72c801a647a3386acd42fcc8","src/error.rs":"d3c1eda7a8da15446a321139d4d29dd9ceee99e916519690d5eb2d45ed628598","src/filter.rs":"88a39e594397708db888726cac00ad1e5b4a892c5a121c96cc11d20f41851b45","src/lib.rs":"9c83780a74048fbbc7bbba5706067b9dc5db2ae25a0cc751687d2738903723b4","src/selector.rs":"6585b4d487179353f4dc8bae396cc18a97e5fcde58f6afa09373cd04510daa03","src/sort_helpers.rs":"5bcae57c230e1d1123d3c5be1ae38b481c6a1fc5096bc0fdede6f7a7c8d27032","src/types.rs":"8721ccf9443b28435ba211d5705f4a6c82eb0354ba1d100045438800c7e2bf9a","uniffi.toml":"96f1cd569483ff59e3c73852f085a03889fa24a2ce20ff7a3003799a9f48a51e"},"package":null}
|
||||
49
third_party/rust/search/src/selector.rs
vendored
49
third_party/rust/search/src/selector.rs
vendored
@@ -1592,7 +1592,7 @@ mod tests {
|
||||
"recordType": "engine",
|
||||
"identifier": "b-engine",
|
||||
"base": {
|
||||
"name": "b-engine",
|
||||
"name": "First Alphabetical",
|
||||
"classification": "general",
|
||||
"urls": {
|
||||
"search": {
|
||||
@@ -1611,7 +1611,7 @@ mod tests {
|
||||
"recordType": "engine",
|
||||
"identifier": "a-engine",
|
||||
"base": {
|
||||
"name": "a-engine",
|
||||
"name": "Last Alphabetical",
|
||||
"classification": "general",
|
||||
"urls": {
|
||||
"search": {
|
||||
@@ -1731,10 +1731,10 @@ mod tests {
|
||||
"default-engine".to_string(),
|
||||
"default-private-engine".to_string(),
|
||||
"after-defaults".to_string(),
|
||||
"a-engine".to_string(),
|
||||
"b-engine".to_string(),
|
||||
"a-engine".to_string(),
|
||||
],
|
||||
"Should order the default engine first, default private engine second, and the rest of the engines based on order hint then alphabetically."
|
||||
"Should order the default engine first, default private engine second, and the rest of the engines based on order hint then alphabetically by name."
|
||||
);
|
||||
|
||||
let starts_with_wiki_config = Arc::clone(&selector).set_search_config(
|
||||
@@ -1919,6 +1919,18 @@ mod tests {
|
||||
selector
|
||||
}
|
||||
|
||||
fn mock_changes_endpoint() -> mockito::Mock {
|
||||
mock(
|
||||
"GET",
|
||||
"/v1/buckets/monitor/collections/changes/changeset?_expected=0",
|
||||
)
|
||||
.with_body(response_body_changes())
|
||||
.with_status(200)
|
||||
.with_header("content-type", "application/json")
|
||||
.with_header("etag", "\"1000\"")
|
||||
.create()
|
||||
}
|
||||
|
||||
fn response_body() -> String {
|
||||
json!({
|
||||
"metadata": {
|
||||
@@ -2021,6 +2033,20 @@ mod tests {
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn response_body_changes() -> String {
|
||||
json!({
|
||||
"timestamp": 1000,
|
||||
"changes": [
|
||||
{
|
||||
"collection": "search-config-v2",
|
||||
"bucket": "main",
|
||||
"last_modified": 1000,
|
||||
}
|
||||
],
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn response_body_locales() -> String {
|
||||
json!({
|
||||
"metadata": {
|
||||
@@ -2129,6 +2155,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_remote_settings_empty_search_config_records_throws_error() {
|
||||
let changes_mock = mock_changes_endpoint();
|
||||
let m = mock(
|
||||
"GET",
|
||||
"/v1/buckets/main/collections/search-config-v2/changeset?_expected=0",
|
||||
@@ -2168,11 +2195,13 @@ mod tests {
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("No search config v2 records received from remote settings"));
|
||||
changes_mock.expect(1).assert();
|
||||
m.expect(1).assert();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remote_settings_search_config_records_is_none_throws_error() {
|
||||
let changes_mock = mock_changes_endpoint();
|
||||
let m1 = mock(
|
||||
"GET",
|
||||
"/v1/buckets/main/collections/search-config-v2/changeset?_expected=0",
|
||||
@@ -2197,11 +2226,13 @@ mod tests {
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("No search config v2 records received from remote settings"));
|
||||
changes_mock.expect(1).assert();
|
||||
m1.expect(1).assert();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remote_settings_empty_search_config_overrides_filtered_without_error() {
|
||||
let changes_mock = mock_changes_endpoint();
|
||||
let m1 = mock(
|
||||
"GET",
|
||||
"/v1/buckets/main/collections/search-config-v2/changeset?_expected=0",
|
||||
@@ -2248,12 +2279,14 @@ mod tests {
|
||||
"Should have filtered the configuration using an empty search config overrides without causing an error. {:?}",
|
||||
result
|
||||
);
|
||||
changes_mock.expect(1).assert();
|
||||
m1.expect(1).assert();
|
||||
m2.expect(1).assert();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remote_settings_search_config_overrides_records_is_none_throws_error() {
|
||||
let changes_mock = mock_changes_endpoint();
|
||||
let m1 = mock(
|
||||
"GET",
|
||||
"/v1/buckets/main/collections/search-config-v2/changeset?_expected=0",
|
||||
@@ -2288,12 +2321,14 @@ mod tests {
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("No search config overrides v2 records received from remote settings"));
|
||||
changes_mock.expect(1).assert();
|
||||
m1.expect(1).assert();
|
||||
m2.expect(1).assert();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter_with_remote_settings_overrides() {
|
||||
let changes_mock = mock_changes_endpoint();
|
||||
let m1 = mock(
|
||||
"GET",
|
||||
"/v1/buckets/main/collections/search-config-v2/changeset?_expected=0",
|
||||
@@ -2355,12 +2390,15 @@ mod tests {
|
||||
test_engine.clone(),
|
||||
"Should have applied the overrides to the matching engine"
|
||||
);
|
||||
changes_mock.expect(1).assert();
|
||||
m1.expect(1).assert();
|
||||
m2.expect(1).assert();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter_with_remote_settings() {
|
||||
let changes_mock = mock_changes_endpoint();
|
||||
|
||||
let m = mock(
|
||||
"GET",
|
||||
"/v1/buckets/main/collections/search-config-v2/changeset?_expected=0",
|
||||
@@ -2468,11 +2506,13 @@ mod tests {
|
||||
},
|
||||
"Should have selected the private default engine for the matching specific default"
|
||||
);
|
||||
changes_mock.expect(1).assert();
|
||||
m.expect(1).assert();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter_with_remote_settings_negotiate_locales() {
|
||||
let changes_mock = mock_changes_endpoint();
|
||||
let m = mock(
|
||||
"GET",
|
||||
"/v1/buckets/main/collections/search-config-v2/changeset?_expected=0",
|
||||
@@ -2551,6 +2591,7 @@ mod tests {
|
||||
},
|
||||
"Should have selected the en-us engine when given another english locale we don't support"
|
||||
);
|
||||
changes_mock.expect(1).assert();
|
||||
m.expect(1).assert();
|
||||
}
|
||||
|
||||
|
||||
61
third_party/rust/search/src/sort_helpers.rs
vendored
61
third_party/rust/search/src/sort_helpers.rs
vendored
@@ -32,7 +32,7 @@ pub(crate) fn sort(
|
||||
// See Bug 1945295: https://bugzilla.mozilla.org/show_bug.cgi?id=1945295
|
||||
// If order is equal and order_hint is None for both, fall back to alphabetical sorting
|
||||
if order == std::cmp::Ordering::Equal {
|
||||
return a.identifier.cmp(&b.identifier);
|
||||
return a.name.cmp(&b.name);
|
||||
}
|
||||
|
||||
order
|
||||
@@ -76,9 +76,14 @@ mod tests {
|
||||
use crate::types::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn create_engine(engine_id: &str, order_hint: Option<u32>) -> SearchEngineDefinition {
|
||||
fn create_engine(
|
||||
engine_id: &str,
|
||||
order_hint: Option<u32>,
|
||||
name: Option<&str>,
|
||||
) -> SearchEngineDefinition {
|
||||
SearchEngineDefinition {
|
||||
identifier: engine_id.to_string(),
|
||||
name: name.unwrap_or(engine_id).to_string(),
|
||||
order_hint,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -87,9 +92,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_find_engine_with_match_mut_starts_with() {
|
||||
let mut engines = vec![
|
||||
create_engine("wiki-ca", None),
|
||||
create_engine("wiki-uk", None),
|
||||
create_engine("test-engine", None),
|
||||
create_engine("wiki-ca", None, None),
|
||||
create_engine("wiki-uk", None, None),
|
||||
create_engine("test-engine", None, None),
|
||||
];
|
||||
let found_engine = find_engine_with_match_mut(&mut engines, &"wiki*".to_string());
|
||||
|
||||
@@ -103,9 +108,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_set_engine_order_full_list() {
|
||||
let mut engines = vec![
|
||||
create_engine("last-engine", None),
|
||||
create_engine("secondary-engine", None),
|
||||
create_engine("primary-engine", None),
|
||||
create_engine("last-engine", None, None),
|
||||
create_engine("secondary-engine", None, None),
|
||||
create_engine("primary-engine", None, None),
|
||||
];
|
||||
let ordered_engines_list = vec![
|
||||
"primary-engine".to_string(),
|
||||
@@ -133,9 +138,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_set_engine_order_partial_list() {
|
||||
let mut engines = vec![
|
||||
create_engine("secondary-engine", None),
|
||||
create_engine("primary-engine", None),
|
||||
create_engine("no-order-hint-engine", None),
|
||||
create_engine("secondary-engine", None, None),
|
||||
create_engine("primary-engine", None, None),
|
||||
create_engine("no-order-hint-engine", None, None),
|
||||
];
|
||||
let ordered_engines_list =
|
||||
vec!["primary-engine".to_string(), "secondary-engine".to_string()];
|
||||
@@ -161,9 +166,9 @@ mod tests {
|
||||
let default_engine_id = None;
|
||||
let default_private_engine_id = None;
|
||||
let mut engines = vec![
|
||||
create_engine("c-engine", Some(3)),
|
||||
create_engine("b-engine", Some(2)),
|
||||
create_engine("a-engine", Some(1)),
|
||||
create_engine("c-engine", Some(3), None),
|
||||
create_engine("b-engine", Some(2), None),
|
||||
create_engine("a-engine", Some(1), None),
|
||||
];
|
||||
engines.sort_by(|a, b| {
|
||||
sort(
|
||||
@@ -187,9 +192,9 @@ mod tests {
|
||||
let default_engine_id = None;
|
||||
let default_private_engine_id = None;
|
||||
let mut engines = vec![
|
||||
create_engine("c-engine", None),
|
||||
create_engine("b-engine", None),
|
||||
create_engine("a-engine", None),
|
||||
create_engine("c-engine", None, None),
|
||||
create_engine("b-engine", None, None),
|
||||
create_engine("a-engine", None, None),
|
||||
];
|
||||
engines.sort_by(|a, b| {
|
||||
sort(
|
||||
@@ -213,12 +218,14 @@ mod tests {
|
||||
let default_engine_id = None;
|
||||
let default_private_engine_id = None;
|
||||
let mut engines = vec![
|
||||
create_engine("f-engine", None),
|
||||
create_engine("e-engine", None),
|
||||
create_engine("d-engine", None),
|
||||
create_engine("c-engine", Some(4)),
|
||||
create_engine("b-engine", Some(5)),
|
||||
create_engine("a-engine", Some(6)),
|
||||
// Identifiers are the opposite order to the names, so that we
|
||||
// can show that we are sorting alphabetically by name.
|
||||
create_engine("d-engine", None, Some("Charlie")),
|
||||
create_engine("e-engine", None, Some("Beta")),
|
||||
create_engine("f-engine", None, Some("Alpha")),
|
||||
create_engine("c-engine", Some(4), None),
|
||||
create_engine("b-engine", Some(5), None),
|
||||
create_engine("a-engine", Some(6), None),
|
||||
];
|
||||
engines.sort_by(|a, b| {
|
||||
sort(
|
||||
@@ -231,7 +238,7 @@ mod tests {
|
||||
|
||||
let actual_order: Vec<&str> = engines.iter().map(|e| e.identifier.as_str()).collect();
|
||||
let expected_order = vec![
|
||||
"a-engine", "b-engine", "c-engine", "d-engine", "e-engine", "f-engine",
|
||||
"a-engine", "b-engine", "c-engine", "f-engine", "e-engine", "d-engine",
|
||||
];
|
||||
assert_eq!(
|
||||
actual_order, expected_order,
|
||||
@@ -244,9 +251,9 @@ mod tests {
|
||||
let default_engine_id = Some("a-engine".to_string());
|
||||
let default_private_engine_id = Some("b-engine".to_string());
|
||||
let mut engines = vec![
|
||||
create_engine("c-engine", Some(3)),
|
||||
create_engine("a-engine", Some(1)), // Default engine should be first
|
||||
create_engine("b-engine", Some(2)), // Default private engine should be second
|
||||
create_engine("c-engine", Some(3), None),
|
||||
create_engine("a-engine", Some(1), None), // Default engine should be first
|
||||
create_engine("b-engine", Some(2), None), // Default private engine should be second
|
||||
];
|
||||
engines.sort_by(|a, b| {
|
||||
sort(
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"files":{"Cargo.toml":"922b2e4d85f325dbef99d5e439558a75dbfda82e1d263c2dd3bfadac9879df18","README.md":"5e28baf874b643d756228bdab345e287bf107d3182dfe6a18aafadcc4b9a3fc9","benches/benchmark_all.rs":"5909dfb1e62793afb1f2bc15b75914527a4d14fce6796307c04a309e45c0598c","metrics.yaml":"0540ab2271aeab7f07335c7ceec12acde942995f9dcb3c29070489aa61899d56","src/benchmarks/README.md":"ccee8dbddba8762d0453fa855bd6984137b224b8c019f3dd8e86a3c303f51d71","src/benchmarks/client.rs":"e5897d4e2eda06809fa6dc6db4e780b9ef266f613fb113aa6613b83f7005dd0b","src/benchmarks/geoname.rs":"00fab05cf9465cf8e22e143cde75a81885411001b240af00efda4071975d0563","src/benchmarks/ingest.rs":"1f3b5eca704c51bc8f972e7a3492a518516461e5834f97a5f7d1855a048ab16b","src/benchmarks/mod.rs":"2c9a39b7a5144674d2475f4d7d69d77c4545f9aa5f123968cb32574e76f10b1a","src/benchmarks/query.rs":"d54946063e72cf98e7f46d94665c17c66af637774c2bb50cd5798dbe63d74f3c","src/bin/debug_ingestion_sizes.rs":"ce6e810be7b3fc19e826d75b622b82cfab5a1a99397a6d0833c2c4eebff2d364","src/config.rs":"0ca876e845841bb6429862c0904c82265003f53b55aea053fac60aed278586a7","src/db.rs":"c22aab621ae8c1b70595c2073e62ff766272400be13f393327c27451bce10498","src/error.rs":"e2ef3ec0e0b2b8ecbb8f2f1717d4cb753af06913b8395d086b7643098ad100a7","src/fakespot.rs":"f501c9fe5296e7c130a9fcb532b861465717652cb5ef688230bc7a3b94df91b1","src/geoname.rs":"77376dbc7d06532a7797a93b863f150317df7f31d9200d375c8ea489ac8bee6f","src/lib.rs":"a4c0989a01a7c13184049c1f11bc7813cd3cbfb6354fcca1f5a7204e45a0dc9c","src/metrics.rs":"871f0d834efbbc9e26d61f66fa31f0021dcf41444746cd7c082f93ba9628e399","src/pocket.rs":"1316668840ec9b4ea886223921dc9d3b5a1731d1a5206c0b1089f2a6c45c1b7b","src/provider.rs":"e85d606e98a8ba37557072f91c6906b1a4d7c5586a9913bf3570ef25106b007f","src/query.rs":"66f229272c9245eb8ee0cab237071627aec599f145f64da8894bcaeb1ed7c6f9","src/rs.rs":"ad2edb0301c0510fb31b12c2f2eb65c5aee596caaee39ed5f98c1de3149afcdf","src/schema.rs":"4fe542fc6572780679854e238ee6b03baa72ca810767c935b435f79d096d3b9b","src/store.rs":"167fc224c1f56a3e3f29ec6a74091b060556993a2e010e6099b095e89df41d57","src/suggestion.rs":"bd4acd1d7f0bfd4ceebd52d44d4ea6ac639d32ef43819e217fbffaa9346b50f2","src/testing/client.rs":"47a32fd84c733001f11e8bfff94dc8c060b6b0780346dca5ddc7a5f5489c1d85","src/testing/data.rs":"6b3dad0414dd862d939f31672547e33a852056e8f891cfec9c1a9cc9fb91d54d","src/testing/mod.rs":"34120abb160a913069c3f7df03c4f52815be3461dbbf08fb0fcc4251a517ba71","src/util.rs":"52c6ec405637afa2d1a89f29fbbb7dcc341546b6deb97d326c4490bbf8713cb0","src/weather.rs":"7cc9167dcdfca49d6ad91eba6fba4d5fd49f45052f25a7fe3ad6749d3e6783fb","src/yelp.rs":"f147608a27030a0e7821d3fd050dc54a41305dc29431970733cafd59e4e5ace6","uniffi.toml":"8205e4679ac26d53e70af0f85c013fd27cda1119f4322aebf5f2b9403d45a611"},"package":null}
|
||||
{"files":{"Cargo.toml":"922b2e4d85f325dbef99d5e439558a75dbfda82e1d263c2dd3bfadac9879df18","README.md":"5e28baf874b643d756228bdab345e287bf107d3182dfe6a18aafadcc4b9a3fc9","benches/benchmark_all.rs":"5909dfb1e62793afb1f2bc15b75914527a4d14fce6796307c04a309e45c0598c","metrics.yaml":"0540ab2271aeab7f07335c7ceec12acde942995f9dcb3c29070489aa61899d56","src/benchmarks/README.md":"ccee8dbddba8762d0453fa855bd6984137b224b8c019f3dd8e86a3c303f51d71","src/benchmarks/client.rs":"e5897d4e2eda06809fa6dc6db4e780b9ef266f613fb113aa6613b83f7005dd0b","src/benchmarks/geoname.rs":"00fab05cf9465cf8e22e143cde75a81885411001b240af00efda4071975d0563","src/benchmarks/ingest.rs":"1f3b5eca704c51bc8f972e7a3492a518516461e5834f97a5f7d1855a048ab16b","src/benchmarks/mod.rs":"2c9a39b7a5144674d2475f4d7d69d77c4545f9aa5f123968cb32574e76f10b1a","src/benchmarks/query.rs":"d54946063e72cf98e7f46d94665c17c66af637774c2bb50cd5798dbe63d74f3c","src/bin/debug_ingestion_sizes.rs":"ce6e810be7b3fc19e826d75b622b82cfab5a1a99397a6d0833c2c4eebff2d364","src/config.rs":"0ca876e845841bb6429862c0904c82265003f53b55aea053fac60aed278586a7","src/db.rs":"c22aab621ae8c1b70595c2073e62ff766272400be13f393327c27451bce10498","src/error.rs":"e2ef3ec0e0b2b8ecbb8f2f1717d4cb753af06913b8395d086b7643098ad100a7","src/fakespot.rs":"f501c9fe5296e7c130a9fcb532b861465717652cb5ef688230bc7a3b94df91b1","src/geoname.rs":"77376dbc7d06532a7797a93b863f150317df7f31d9200d375c8ea489ac8bee6f","src/lib.rs":"a4c0989a01a7c13184049c1f11bc7813cd3cbfb6354fcca1f5a7204e45a0dc9c","src/metrics.rs":"871f0d834efbbc9e26d61f66fa31f0021dcf41444746cd7c082f93ba9628e399","src/pocket.rs":"1316668840ec9b4ea886223921dc9d3b5a1731d1a5206c0b1089f2a6c45c1b7b","src/provider.rs":"e85d606e98a8ba37557072f91c6906b1a4d7c5586a9913bf3570ef25106b007f","src/query.rs":"66f229272c9245eb8ee0cab237071627aec599f145f64da8894bcaeb1ed7c6f9","src/rs.rs":"3e2310d069b4cbc7447c2bb625f03bb49439b218a1e8f04190015a31cde22842","src/schema.rs":"68dbdc960097cc3421247cd9f705f6dcf74c9d357b37a5824b80e37837cbf053","src/store.rs":"76e6e2134d1d0e6f8dcf30ed65fe18eb093531bdddec461ad708b1eb4ac6a01c","src/suggestion.rs":"33dd2fb8e966a72f9843476bc006c8dfb1326ed1268ad88aa91801356f2623a1","src/testing/client.rs":"47a32fd84c733001f11e8bfff94dc8c060b6b0780346dca5ddc7a5f5489c1d85","src/testing/data.rs":"ad710b31532a9540491a73cba33a54db02e85dd5ec0a4f2260430f144c3d7380","src/testing/mod.rs":"fe930be25229517831111fb6d7796ae957ec0eb1b9a190c59cf538ac41ae27f5","src/util.rs":"52c6ec405637afa2d1a89f29fbbb7dcc341546b6deb97d326c4490bbf8713cb0","src/weather.rs":"7cc9167dcdfca49d6ad91eba6fba4d5fd49f45052f25a7fe3ad6749d3e6783fb","src/yelp.rs":"1fe3b7eb6b3f7462e9758b6eb62457dfa26f7549a8290cdff7637d2fb3ffea4f","uniffi.toml":"8205e4679ac26d53e70af0f85c013fd27cda1119f4322aebf5f2b9403d45a611"},"package":null}
|
||||
2
third_party/rust/suggest/src/rs.rs
vendored
2
third_party/rust/suggest/src/rs.rs
vendored
@@ -474,6 +474,8 @@ impl ToSql for DownloadedYelpLocationSign {
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub(crate) struct DownloadedYelpSuggestion {
|
||||
pub subjects: Vec<String>,
|
||||
#[serde(rename = "businessSubjects")]
|
||||
pub business_subjects: Option<Vec<String>>,
|
||||
#[serde(rename = "preModifiers")]
|
||||
pub pre_modifiers: Vec<String>,
|
||||
#[serde(rename = "postModifiers")]
|
||||
|
||||
17
third_party/rust/suggest/src/schema.rs
vendored
17
third_party/rust/suggest/src/schema.rs
vendored
@@ -23,7 +23,7 @@ use sql_support::{
|
||||
/// `clear_database()` by adding their names to `conditional_tables`, unless
|
||||
/// they are cleared via a deletion trigger or there's some other good
|
||||
/// reason not to do so.
|
||||
pub const VERSION: u32 = 37;
|
||||
pub const VERSION: u32 = 38;
|
||||
|
||||
/// The current Suggest database schema.
|
||||
pub const SQL: &str = "
|
||||
@@ -161,6 +161,7 @@ CREATE TABLE icons(
|
||||
|
||||
CREATE TABLE yelp_subjects(
|
||||
keyword TEXT PRIMARY KEY,
|
||||
subject_type INTEGER NOT NULL DEFAULT 0,
|
||||
record_id TEXT NOT NULL
|
||||
) WITHOUT ROWID;
|
||||
|
||||
@@ -647,6 +648,20 @@ impl ConnectionInitializer for SuggestConnectionInitializer<'_> {
|
||||
tx.execute_batch("DROP TABLE IF EXISTS yelp_location_signs;")?;
|
||||
Ok(())
|
||||
}
|
||||
37 => {
|
||||
clear_database(tx)?;
|
||||
tx.execute_batch(
|
||||
"
|
||||
DROP TABLE yelp_subjects;
|
||||
CREATE TABLE yelp_subjects(
|
||||
keyword TEXT PRIMARY KEY,
|
||||
subject_type INTEGER NOT NULL DEFAULT 0,
|
||||
record_id TEXT NOT NULL
|
||||
) WITHOUT ROWID;
|
||||
",
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(open_database::Error::IncompatibleVersion(version)),
|
||||
}
|
||||
}
|
||||
|
||||
20
third_party/rust/suggest/src/store.rs
vendored
20
third_party/rust/suggest/src/store.rs
vendored
@@ -975,6 +975,7 @@ impl SuggestStoreDbs {
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::suggestion::YelpSubjectType;
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
@@ -2247,6 +2248,25 @@ pub(crate) mod tests {
|
||||
)
|
||||
.has_location_sign(false)],
|
||||
);
|
||||
// Business subject.
|
||||
assert_eq!(
|
||||
store.fetch_suggestions(SuggestionQuery::yelp("the shop tokyo")),
|
||||
vec![ramen_suggestion(
|
||||
"the shop tokyo",
|
||||
"https://www.yelp.com/search?find_desc=the+shop&find_loc=tokyo"
|
||||
)
|
||||
.has_location_sign(false)
|
||||
.subject_type(YelpSubjectType::Business)]
|
||||
);
|
||||
assert_eq!(
|
||||
store.fetch_suggestions(SuggestionQuery::yelp("the sho")),
|
||||
vec![
|
||||
ramen_suggestion("the shop", "https://www.yelp.com/search?find_desc=the+shop")
|
||||
.has_location_sign(false)
|
||||
.subject_exact_match(false)
|
||||
.subject_type(YelpSubjectType::Business)
|
||||
]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
11
third_party/rust/suggest/src/suggestion.rs
vendored
11
third_party/rust/suggest/src/suggestion.rs
vendored
@@ -16,6 +16,16 @@ const TIMESTAMP_TEMPLATE: &str = "%YYYYMMDDHH%";
|
||||
/// 2 bytes shorter than [`TIMESTAMP_TEMPLATE`].
|
||||
const TIMESTAMP_LENGTH: usize = 10;
|
||||
|
||||
/// Subject type for Yelp suggestion.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, uniffi::Enum)]
|
||||
#[repr(u8)]
|
||||
pub enum YelpSubjectType {
|
||||
// Service such as sushi, ramen, yoga etc.
|
||||
Service = 0,
|
||||
// Specific business such as the shop name.
|
||||
Business = 1,
|
||||
}
|
||||
|
||||
/// A suggestion from the database to show in the address bar.
|
||||
#[derive(Clone, Debug, PartialEq, uniffi::Enum)]
|
||||
pub enum Suggestion {
|
||||
@@ -66,6 +76,7 @@ pub enum Suggestion {
|
||||
score: f64,
|
||||
has_location_sign: bool,
|
||||
subject_exact_match: bool,
|
||||
subject_type: YelpSubjectType,
|
||||
location_param: String,
|
||||
},
|
||||
Mdn {
|
||||
|
||||
4
third_party/rust/suggest/src/testing/data.rs
vendored
4
third_party/rust/suggest/src/testing/data.rs
vendored
@@ -4,7 +4,7 @@
|
||||
|
||||
//! Test data that we use in many tests
|
||||
|
||||
use crate::{suggestion::FtsMatchInfo, testing::MockIcon, Suggestion};
|
||||
use crate::{suggestion::FtsMatchInfo, suggestion::YelpSubjectType, testing::MockIcon, Suggestion};
|
||||
use serde_json::json;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
@@ -294,6 +294,7 @@ pub fn burnout_suggestion(is_top_pick: bool) -> Suggestion {
|
||||
pub fn ramen_yelp() -> JsonValue {
|
||||
json!({
|
||||
"subjects": ["ramen", "spicy ramen", "spicy random ramen", "rats", "raven", "raccoon", "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789Z"],
|
||||
"businessSubjects": ["the shop"],
|
||||
"preModifiers": ["best", "super best", "same_modifier"],
|
||||
"postModifiers": ["delivery", "super delivery", "same_modifier"],
|
||||
"locationSigns": [
|
||||
@@ -319,6 +320,7 @@ pub fn ramen_suggestion(title: &str, url: &str) -> Suggestion {
|
||||
score: 0.5,
|
||||
has_location_sign: true,
|
||||
subject_exact_match: true,
|
||||
subject_type: YelpSubjectType::Service,
|
||||
location_param: "find_loc".into(),
|
||||
}
|
||||
}
|
||||
|
||||
36
third_party/rust/suggest/src/testing/mod.rs
vendored
36
third_party/rust/suggest/src/testing/mod.rs
vendored
@@ -8,7 +8,8 @@ mod data;
|
||||
pub use client::{MockAttachment, MockIcon, MockRecord, MockRemoteSettingsClient};
|
||||
pub use data::*;
|
||||
|
||||
use crate::Suggestion;
|
||||
use crate::{suggestion::YelpSubjectType, Suggestion};
|
||||
|
||||
use parking_lot::Once;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
@@ -79,6 +80,7 @@ impl Suggestion {
|
||||
icon_mimetype,
|
||||
score,
|
||||
subject_exact_match,
|
||||
subject_type,
|
||||
location_param,
|
||||
..
|
||||
} => Self::Yelp {
|
||||
@@ -88,6 +90,7 @@ impl Suggestion {
|
||||
icon_mimetype,
|
||||
score,
|
||||
subject_exact_match,
|
||||
subject_type,
|
||||
location_param,
|
||||
has_location_sign,
|
||||
},
|
||||
@@ -103,6 +106,7 @@ impl Suggestion {
|
||||
icon,
|
||||
icon_mimetype,
|
||||
score,
|
||||
subject_type,
|
||||
has_location_sign,
|
||||
location_param,
|
||||
..
|
||||
@@ -113,10 +117,38 @@ impl Suggestion {
|
||||
icon_mimetype,
|
||||
score,
|
||||
subject_exact_match,
|
||||
subject_type,
|
||||
location_param,
|
||||
has_location_sign,
|
||||
},
|
||||
_ => panic!("has_location_sign only valid for yelp suggestions"),
|
||||
_ => panic!("subject_exact_match only valid for yelp suggestions"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subject_type(self, subject_type: YelpSubjectType) -> Self {
|
||||
match self {
|
||||
Self::Yelp {
|
||||
title,
|
||||
url,
|
||||
icon,
|
||||
icon_mimetype,
|
||||
score,
|
||||
subject_exact_match,
|
||||
has_location_sign,
|
||||
location_param,
|
||||
..
|
||||
} => Self::Yelp {
|
||||
title,
|
||||
url,
|
||||
icon,
|
||||
icon_mimetype,
|
||||
score,
|
||||
subject_exact_match,
|
||||
subject_type,
|
||||
location_param,
|
||||
has_location_sign,
|
||||
},
|
||||
_ => panic!("subject_type only valid for yelp suggestions"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
214
third_party/rust/suggest/src/yelp.rs
vendored
214
third_party/rust/suggest/src/yelp.rs
vendored
@@ -3,7 +3,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
use rusqlite::types::ToSqlOutput;
|
||||
use rusqlite::types::{FromSql, FromSqlResult, ToSqlOutput, ValueRef};
|
||||
use rusqlite::{named_params, Result as RusqliteResult, ToSql};
|
||||
use sql_support::ConnExt;
|
||||
use url::form_urlencoded;
|
||||
@@ -13,6 +13,7 @@ use crate::{
|
||||
provider::SuggestionProvider,
|
||||
rs::{DownloadedYelpSuggestion, SuggestRecordId},
|
||||
suggestion::Suggestion,
|
||||
suggestion::YelpSubjectType,
|
||||
Result, SuggestionQuery,
|
||||
};
|
||||
|
||||
@@ -31,6 +32,22 @@ impl ToSql for Modifier {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for YelpSubjectType {
|
||||
fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::from(*self as u8))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for YelpSubjectType {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
if value.as_i64().unwrap_or_default() == 0 {
|
||||
Ok(YelpSubjectType::Service)
|
||||
} else {
|
||||
Ok(YelpSubjectType::Business)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
enum FindFrom {
|
||||
First,
|
||||
@@ -57,6 +74,18 @@ const MAX_MODIFIER_WORDS_NUMBER: usize = 2;
|
||||
/// At least this many characters must be typed for a subject to be matched.
|
||||
const SUBJECT_PREFIX_MATCH_THRESHOLD: usize = 2;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct FindSubjectData<'a> {
|
||||
// The keyword in DB (but the case is inherited by query).
|
||||
subject: String,
|
||||
// Whether or not the keyword is exact match.
|
||||
exact_match: bool,
|
||||
// The subject type.
|
||||
subject_type: YelpSubjectType,
|
||||
// Words after removed matching subject.
|
||||
rest: &'a [&'a str],
|
||||
}
|
||||
|
||||
impl SuggestDao<'_> {
|
||||
/// Inserts the suggestions for Yelp attachment into the database.
|
||||
pub(crate) fn insert_yelp_suggestions(
|
||||
@@ -67,10 +96,23 @@ impl SuggestDao<'_> {
|
||||
for keyword in &suggestion.subjects {
|
||||
self.scope.err_if_interrupted()?;
|
||||
self.conn.execute_cached(
|
||||
"INSERT INTO yelp_subjects(record_id, keyword) VALUES(:record_id, :keyword)",
|
||||
"INSERT INTO yelp_subjects(record_id, keyword, subject_type) VALUES(:record_id, :keyword, :subject_type)",
|
||||
named_params! {
|
||||
":record_id": record_id.as_str(),
|
||||
":keyword": keyword,
|
||||
":subject_type": YelpSubjectType::Service,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
for keyword in suggestion.business_subjects.as_ref().unwrap_or(&vec![]) {
|
||||
self.scope.err_if_interrupted()?;
|
||||
self.conn.execute_cached(
|
||||
"INSERT INTO yelp_subjects(record_id, keyword, subject_type) VALUES(:record_id, :keyword, :subject_type)",
|
||||
named_params! {
|
||||
":record_id": record_id.as_str(),
|
||||
":keyword": keyword,
|
||||
":subject_type": YelpSubjectType::Business,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
@@ -163,10 +205,10 @@ impl SuggestDao<'_> {
|
||||
query_words = rest;
|
||||
}
|
||||
|
||||
let Some(subject_tuple) = self.find_subject(query_words)? else {
|
||||
let Some(subject_data) = self.find_subject(query_words)? else {
|
||||
return Ok(vec![]);
|
||||
};
|
||||
query_words = subject_tuple.2;
|
||||
query_words = subject_data.rest;
|
||||
|
||||
let post_modifier_tuple =
|
||||
self.find_modifier(query_words, Modifier::Post, FindFrom::First)?;
|
||||
@@ -194,8 +236,9 @@ impl SuggestDao<'_> {
|
||||
|
||||
let (icon, icon_mimetype, score) = self.fetch_custom_details()?;
|
||||
let builder = SuggestionBuilder {
|
||||
subject: &subject_tuple.0,
|
||||
subject_exact_match: subject_tuple.1,
|
||||
subject: &subject_data.subject,
|
||||
subject_exact_match: subject_data.exact_match,
|
||||
subject_type: subject_data.subject_type,
|
||||
pre_modifier: pre_modifier_tuple.map(|(words, _)| words.to_string()),
|
||||
post_modifier: post_modifier_tuple.map(|(words, _)| words.to_string()),
|
||||
location_sign: location_sign_tuple.map(|(words, _)| words.to_string()),
|
||||
@@ -266,16 +309,8 @@ impl SuggestDao<'_> {
|
||||
}
|
||||
|
||||
/// Find the subject for given query.
|
||||
/// It returns Option<tuple> as follows:
|
||||
/// (
|
||||
/// String: The keyword in DB (but the case is inherited by query).
|
||||
/// bool: Whether or not the keyword is exact match.
|
||||
/// &[&str]: Words after removed matching subject.
|
||||
/// )
|
||||
fn find_subject<'a>(
|
||||
&self,
|
||||
query_words: &'a [&'a str],
|
||||
) -> Result<Option<(String, bool, &'a [&'a str])>> {
|
||||
/// It returns Option<FindSubjectData>.
|
||||
fn find_subject<'a>(&self, query_words: &'a [&'a str]) -> Result<Option<FindSubjectData<'a>>> {
|
||||
if query_words.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -283,8 +318,8 @@ impl SuggestDao<'_> {
|
||||
let mut query_string = query_words.join(" ");
|
||||
|
||||
// This checks if keyword is a substring of the query.
|
||||
if let Some(keyword_lowercase) = self.conn.try_query_one::<String, _>(
|
||||
"SELECT keyword
|
||||
if let Ok((keyword_lowercase, subject_type)) = self.conn.query_row_and_then_cachable(
|
||||
"SELECT keyword, subject_type
|
||||
FROM yelp_subjects
|
||||
WHERE :query BETWEEN keyword AND keyword || ' ' || x'FFFF'
|
||||
ORDER BY LENGTH(keyword) ASC, keyword ASC
|
||||
@@ -292,16 +327,20 @@ impl SuggestDao<'_> {
|
||||
named_params! {
|
||||
":query": query_string.to_lowercase(),
|
||||
},
|
||||
|row| -> Result<_> {
|
||||
Ok((row.get::<_, String>(0)?, row.get::<_, YelpSubjectType>(1)?))
|
||||
},
|
||||
true,
|
||||
)? {
|
||||
) {
|
||||
// Preserve the query as the user typed it including its case.
|
||||
return Ok(query_string.get(0..keyword_lowercase.len()).map(|keyword| {
|
||||
let count = keyword.split_whitespace().count();
|
||||
(
|
||||
keyword.to_string(),
|
||||
true,
|
||||
query_words.get(count..).unwrap_or_default(),
|
||||
)
|
||||
FindSubjectData {
|
||||
subject: keyword.to_string(),
|
||||
exact_match: true,
|
||||
subject_type,
|
||||
rest: query_words.get(count..).unwrap_or_default(),
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -310,8 +349,8 @@ impl SuggestDao<'_> {
|
||||
}
|
||||
|
||||
// Oppositely, this checks if the query is a substring of keyword.
|
||||
if let Some(keyword_lowercase) = self.conn.try_query_one::<String, _>(
|
||||
"SELECT keyword
|
||||
if let Ok((keyword_lowercase, subject_type)) = self.conn.query_row_and_then_cachable(
|
||||
"SELECT keyword, subject_type
|
||||
FROM yelp_subjects
|
||||
WHERE keyword BETWEEN :query AND :query || x'FFFF'
|
||||
ORDER BY LENGTH(keyword) ASC, keyword ASC
|
||||
@@ -319,8 +358,11 @@ impl SuggestDao<'_> {
|
||||
named_params! {
|
||||
":query": query_string.to_lowercase(),
|
||||
},
|
||||
|row| -> Result<_> {
|
||||
Ok((row.get::<_, String>(0)?, row.get::<_, YelpSubjectType>(1)?))
|
||||
},
|
||||
true,
|
||||
)? {
|
||||
) {
|
||||
// Preserve the query as the user typed it including its case.
|
||||
return Ok(keyword_lowercase
|
||||
.get(query_string.len()..)
|
||||
@@ -328,11 +370,12 @@ impl SuggestDao<'_> {
|
||||
query_string.push_str(keyword_rest);
|
||||
let count =
|
||||
std::cmp::min(query_words.len(), query_string.split_whitespace().count());
|
||||
(
|
||||
query_string,
|
||||
false,
|
||||
query_words.get(count..).unwrap_or_default(),
|
||||
)
|
||||
FindSubjectData {
|
||||
subject: query_string,
|
||||
exact_match: false,
|
||||
subject_type,
|
||||
rest: query_words.get(count..).unwrap_or_default(),
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -383,6 +426,7 @@ impl SuggestDao<'_> {
|
||||
struct SuggestionBuilder<'a> {
|
||||
subject: &'a str,
|
||||
subject_exact_match: bool,
|
||||
subject_type: YelpSubjectType,
|
||||
pre_modifier: Option<String>,
|
||||
post_modifier: Option<String>,
|
||||
location_sign: Option<String>,
|
||||
@@ -435,6 +479,7 @@ impl<'a> From<SuggestionBuilder<'a>> for Suggestion {
|
||||
score: builder.score,
|
||||
has_location_sign: builder.location_sign.is_some(),
|
||||
subject_exact_match: builder.subject_exact_match,
|
||||
subject_type: builder.subject_type,
|
||||
location_param: "find_loc".to_string(),
|
||||
}
|
||||
}
|
||||
@@ -645,31 +690,114 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
type FindSubjectTestCase<'a> = (&'a str, Option<(String, bool, &'a [&'a str])>);
|
||||
type FindSubjectTestCase<'a> = (&'a str, Option<FindSubjectData<'a>>);
|
||||
let find_subject_tests: &[FindSubjectTestCase] = &[
|
||||
// Query, Expected result.
|
||||
("", None),
|
||||
("r", None),
|
||||
("ra", Some(("rats".to_string(), false, &[]))),
|
||||
("ram", Some(("ramen".to_string(), false, &[]))),
|
||||
("rame", Some(("ramen".to_string(), false, &[]))),
|
||||
("ramen", Some(("ramen".to_string(), true, &[]))),
|
||||
("spi", Some(("spicy ramen".to_string(), false, &[]))),
|
||||
("spicy ra ", Some(("spicy ramen".to_string(), false, &[]))),
|
||||
("spicy ramen", Some(("spicy ramen".to_string(), true, &[]))),
|
||||
(
|
||||
"ra",
|
||||
Some(FindSubjectData {
|
||||
subject: "rats".to_string(),
|
||||
exact_match: false,
|
||||
subject_type: YelpSubjectType::Service,
|
||||
rest: &[],
|
||||
}),
|
||||
),
|
||||
(
|
||||
"ram",
|
||||
Some(FindSubjectData {
|
||||
subject: "ramen".to_string(),
|
||||
exact_match: false,
|
||||
subject_type: YelpSubjectType::Service,
|
||||
rest: &[],
|
||||
}),
|
||||
),
|
||||
(
|
||||
"rame",
|
||||
Some(FindSubjectData {
|
||||
subject: "ramen".to_string(),
|
||||
exact_match: false,
|
||||
subject_type: YelpSubjectType::Service,
|
||||
rest: &[],
|
||||
}),
|
||||
),
|
||||
(
|
||||
"ramen",
|
||||
Some(FindSubjectData {
|
||||
subject: "ramen".to_string(),
|
||||
exact_match: true,
|
||||
subject_type: YelpSubjectType::Service,
|
||||
rest: &[],
|
||||
}),
|
||||
),
|
||||
(
|
||||
"spi",
|
||||
Some(FindSubjectData {
|
||||
subject: "spicy ramen".to_string(),
|
||||
exact_match: false,
|
||||
subject_type: YelpSubjectType::Service,
|
||||
rest: &[],
|
||||
}),
|
||||
),
|
||||
(
|
||||
"spicy ra ",
|
||||
Some(FindSubjectData {
|
||||
subject: "spicy ramen".to_string(),
|
||||
exact_match: false,
|
||||
subject_type: YelpSubjectType::Service,
|
||||
rest: &[],
|
||||
}),
|
||||
),
|
||||
(
|
||||
"spicy ramen",
|
||||
Some(FindSubjectData {
|
||||
subject: "spicy ramen".to_string(),
|
||||
exact_match: true,
|
||||
subject_type: YelpSubjectType::Service,
|
||||
rest: &[],
|
||||
}),
|
||||
),
|
||||
(
|
||||
"spicy ramen gogo",
|
||||
Some(("spicy ramen".to_string(), true, &["gogo"])),
|
||||
Some(FindSubjectData {
|
||||
subject: "spicy ramen".to_string(),
|
||||
exact_match: true,
|
||||
subject_type: YelpSubjectType::Service,
|
||||
rest: &["gogo"],
|
||||
}),
|
||||
),
|
||||
(
|
||||
"SpIcY rAmEn GoGo",
|
||||
Some(("SpIcY rAmEn".to_string(), true, &["GoGo"])),
|
||||
Some(FindSubjectData {
|
||||
subject: "SpIcY rAmEn".to_string(),
|
||||
exact_match: true,
|
||||
subject_type: YelpSubjectType::Service,
|
||||
rest: &["GoGo"],
|
||||
}),
|
||||
),
|
||||
("ramenabc", None),
|
||||
("ramenabc xyz", None),
|
||||
("spicy ramenabc", None),
|
||||
("spicy ramenabc xyz", None),
|
||||
("ramen abc", Some(("ramen".to_string(), true, &["abc"]))),
|
||||
(
|
||||
"ramen abc",
|
||||
Some(FindSubjectData {
|
||||
subject: "ramen".to_string(),
|
||||
exact_match: true,
|
||||
subject_type: YelpSubjectType::Service,
|
||||
rest: &["abc"],
|
||||
}),
|
||||
),
|
||||
(
|
||||
"the shop",
|
||||
Some(FindSubjectData {
|
||||
subject: "the shop".to_string(),
|
||||
exact_match: true,
|
||||
subject_type: YelpSubjectType::Business,
|
||||
rest: &[],
|
||||
}),
|
||||
),
|
||||
];
|
||||
for (query, expected) in find_subject_tests {
|
||||
assert_eq!(
|
||||
|
||||
@@ -2945,6 +2945,7 @@ Suggestion.Yelp = class extends Suggestion{
|
||||
score,
|
||||
hasLocationSign,
|
||||
subjectExactMatch,
|
||||
subjectType,
|
||||
locationParam
|
||||
) {
|
||||
super();
|
||||
@@ -2955,6 +2956,7 @@ Suggestion.Yelp = class extends Suggestion{
|
||||
this.score = score;
|
||||
this.hasLocationSign = hasLocationSign;
|
||||
this.subjectExactMatch = subjectExactMatch;
|
||||
this.subjectType = subjectType;
|
||||
this.locationParam = locationParam;
|
||||
}
|
||||
}
|
||||
@@ -3100,6 +3102,7 @@ export class FfiConverterTypeSuggestion extends FfiConverterArrayBuffer {
|
||||
FfiConverterF64.read(dataStream),
|
||||
FfiConverterBool.read(dataStream),
|
||||
FfiConverterBool.read(dataStream),
|
||||
FfiConverterTypeYelpSubjectType.read(dataStream),
|
||||
FfiConverterString.read(dataStream)
|
||||
);
|
||||
case 6:
|
||||
@@ -3200,6 +3203,7 @@ export class FfiConverterTypeSuggestion extends FfiConverterArrayBuffer {
|
||||
FfiConverterF64.write(dataStream, value.score);
|
||||
FfiConverterBool.write(dataStream, value.hasLocationSign);
|
||||
FfiConverterBool.write(dataStream, value.subjectExactMatch);
|
||||
FfiConverterTypeYelpSubjectType.write(dataStream, value.subjectType);
|
||||
FfiConverterString.write(dataStream, value.locationParam);
|
||||
return;
|
||||
}
|
||||
@@ -3300,6 +3304,7 @@ export class FfiConverterTypeSuggestion extends FfiConverterArrayBuffer {
|
||||
totalSize += FfiConverterF64.computeSize(value.score);
|
||||
totalSize += FfiConverterBool.computeSize(value.hasLocationSign);
|
||||
totalSize += FfiConverterBool.computeSize(value.subjectExactMatch);
|
||||
totalSize += FfiConverterTypeYelpSubjectType.computeSize(value.subjectType);
|
||||
totalSize += FfiConverterString.computeSize(value.locationParam);
|
||||
return totalSize;
|
||||
}
|
||||
@@ -3477,6 +3482,63 @@ export class FfiConverterTypeSuggestionProvider extends FfiConverterArrayBuffer
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Subject type for Yelp suggestion.
|
||||
*/
|
||||
export const YelpSubjectType = {
|
||||
/**
|
||||
* SERVICE
|
||||
*/
|
||||
SERVICE:0,
|
||||
/**
|
||||
* BUSINESS
|
||||
*/
|
||||
BUSINESS:1,
|
||||
};
|
||||
|
||||
Object.freeze(YelpSubjectType);
|
||||
// Export the FFIConverter object to make external types work.
|
||||
export class FfiConverterTypeYelpSubjectType extends FfiConverterArrayBuffer {
|
||||
static #validValues = Object.values(YelpSubjectType);
|
||||
|
||||
static read(dataStream) {
|
||||
// Use sequential indices (1-based) for the wire format to match Python bindings
|
||||
switch (dataStream.readInt32()) {
|
||||
case 1:
|
||||
return YelpSubjectType.SERVICE
|
||||
case 2:
|
||||
return YelpSubjectType.BUSINESS
|
||||
default:
|
||||
throw new UniFFITypeError("Unknown YelpSubjectType variant");
|
||||
}
|
||||
}
|
||||
|
||||
static write(dataStream, value) {
|
||||
if (value === YelpSubjectType.SERVICE) {
|
||||
dataStream.writeInt32(1);
|
||||
return;
|
||||
}
|
||||
if (value === YelpSubjectType.BUSINESS) {
|
||||
dataStream.writeInt32(2);
|
||||
return;
|
||||
}
|
||||
throw new UniFFITypeError("Unknown YelpSubjectType variant");
|
||||
}
|
||||
|
||||
static computeSize(value) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
static checkType(value) {
|
||||
// Check that the value is a valid enum variant
|
||||
if (!this.#validValues.includes(value)) {
|
||||
throw new UniFFITypeError(`${value} is not a valid value for YelpSubjectType`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Export the FFIConverter object to make external types work.
|
||||
export class FfiConverterOptionali32 extends FfiConverterArrayBuffer {
|
||||
static checkType(value) {
|
||||
|
||||
Reference in New Issue
Block a user