Bug 1878987 - Part 1: Vendor application-services into mozilla-central to pick up Suggest changes. r=lina

Differential Revision: https://phabricator.services.mozilla.com/D200891
This commit is contained in:
Drew Willcoxon 2024-02-07 20:07:24 +00:00
parent badedf773a
commit a5961357d0
34 changed files with 2258 additions and 558 deletions

View file

@ -60,9 +60,9 @@ git = "https://github.com/mozilla-spidermonkey/jsparagus"
rev = "61f399c53a641ebd3077c1f39f054f6d396a633c" rev = "61f399c53a641ebd3077c1f39f054f6d396a633c"
replace-with = "vendored-sources" replace-with = "vendored-sources"
[source."git+https://github.com/mozilla/application-services?rev=9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253"] [source."git+https://github.com/mozilla/application-services?rev=41367fda038268843a87c459698691f33f0d9063"]
git = "https://github.com/mozilla/application-services" git = "https://github.com/mozilla/application-services"
rev = "9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" rev = "41367fda038268843a87c459698691f33f0d9063"
replace-with = "vendored-sources" replace-with = "vendored-sources"
[source."git+https://github.com/mozilla/audioipc?rev=596bdb7fbb5745ea415726e16bd497e6c850a540"] [source."git+https://github.com/mozilla/audioipc?rev=596bdb7fbb5745ea415726e16bd497e6c850a540"]

26
Cargo.lock generated
View file

@ -1585,7 +1585,7 @@ dependencies = [
[[package]] [[package]]
name = "error-support" name = "error-support"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/mozilla/application-services?rev=9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253#9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" source = "git+https://github.com/mozilla/application-services?rev=41367fda038268843a87c459698691f33f0d9063#41367fda038268843a87c459698691f33f0d9063"
dependencies = [ dependencies = [
"error-support-macros", "error-support-macros",
"lazy_static", "lazy_static",
@ -1597,7 +1597,7 @@ dependencies = [
[[package]] [[package]]
name = "error-support-macros" name = "error-support-macros"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/mozilla/application-services?rev=9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253#9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" source = "git+https://github.com/mozilla/application-services?rev=41367fda038268843a87c459698691f33f0d9063#41367fda038268843a87c459698691f33f0d9063"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2855,7 +2855,7 @@ dependencies = [
[[package]] [[package]]
name = "interrupt-support" name = "interrupt-support"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/mozilla/application-services?rev=9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253#9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" source = "git+https://github.com/mozilla/application-services?rev=41367fda038268843a87c459698691f33f0d9063#41367fda038268843a87c459698691f33f0d9063"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"parking_lot", "parking_lot",
@ -4041,7 +4041,7 @@ dependencies = [
[[package]] [[package]]
name = "nss_build_common" name = "nss_build_common"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/mozilla/application-services?rev=9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253#9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" source = "git+https://github.com/mozilla/application-services?rev=41367fda038268843a87c459698691f33f0d9063#41367fda038268843a87c459698691f33f0d9063"
[[package]] [[package]]
name = "nsstring" name = "nsstring"
@ -4717,7 +4717,7 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]] [[package]]
name = "remote_settings" name = "remote_settings"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/mozilla/application-services?rev=9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253#9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" source = "git+https://github.com/mozilla/application-services?rev=41367fda038268843a87c459698691f33f0d9063#41367fda038268843a87c459698691f33f0d9063"
dependencies = [ dependencies = [
"parking_lot", "parking_lot",
"serde", "serde",
@ -5240,7 +5240,7 @@ dependencies = [
[[package]] [[package]]
name = "sql-support" name = "sql-support"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/mozilla/application-services?rev=9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253#9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" source = "git+https://github.com/mozilla/application-services?rev=41367fda038268843a87c459698691f33f0d9063#41367fda038268843a87c459698691f33f0d9063"
dependencies = [ dependencies = [
"ffi-support", "ffi-support",
"interrupt-support", "interrupt-support",
@ -5421,10 +5421,11 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]] [[package]]
name = "suggest" name = "suggest"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/mozilla/application-services?rev=9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253#9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" source = "git+https://github.com/mozilla/application-services?rev=41367fda038268843a87c459698691f33f0d9063#41367fda038268843a87c459698691f33f0d9063"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
"error-support",
"interrupt-support", "interrupt-support",
"once_cell", "once_cell",
"parking_lot", "parking_lot",
@ -5436,6 +5437,7 @@ dependencies = [
"thiserror", "thiserror",
"uniffi", "uniffi",
"url", "url",
"viaduct",
] ]
[[package]] [[package]]
@ -5468,7 +5470,7 @@ dependencies = [
[[package]] [[package]]
name = "sync-guid" name = "sync-guid"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/mozilla/application-services?rev=9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253#9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" source = "git+https://github.com/mozilla/application-services?rev=41367fda038268843a87c459698691f33f0d9063#41367fda038268843a87c459698691f33f0d9063"
dependencies = [ dependencies = [
"base64 0.21.3", "base64 0.21.3",
"rand", "rand",
@ -5479,7 +5481,7 @@ dependencies = [
[[package]] [[package]]
name = "sync15" name = "sync15"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/mozilla/application-services?rev=9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253#9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" source = "git+https://github.com/mozilla/application-services?rev=41367fda038268843a87c459698691f33f0d9063#41367fda038268843a87c459698691f33f0d9063"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"error-support", "error-support",
@ -5511,7 +5513,7 @@ dependencies = [
[[package]] [[package]]
name = "tabs" name = "tabs"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/mozilla/application-services?rev=9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253#9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" source = "git+https://github.com/mozilla/application-services?rev=41367fda038268843a87c459698691f33f0d9063#41367fda038268843a87c459698691f33f0d9063"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"error-support", "error-support",
@ -6161,7 +6163,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]] [[package]]
name = "viaduct" name = "viaduct"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/mozilla/application-services?rev=9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253#9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" source = "git+https://github.com/mozilla/application-services?rev=41367fda038268843a87c459698691f33f0d9063#41367fda038268843a87c459698691f33f0d9063"
dependencies = [ dependencies = [
"ffi-support", "ffi-support",
"log", "log",
@ -6307,7 +6309,7 @@ dependencies = [
[[package]] [[package]]
name = "webext-storage" name = "webext-storage"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/mozilla/application-services?rev=9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253#9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" source = "git+https://github.com/mozilla/application-services?rev=41367fda038268843a87c459698691f33f0d9063#41367fda038268843a87c459698691f33f0d9063"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"error-support", "error-support",

View file

@ -206,13 +206,13 @@ warp = { git = "https://github.com/seanmonstar/warp", rev = "9d081461ae1167eb321
malloc_size_of_derive = { path = "xpcom/rust/malloc_size_of_derive" } malloc_size_of_derive = { path = "xpcom/rust/malloc_size_of_derive" }
# application-services overrides to make updating them all simpler. # application-services overrides to make updating them all simpler.
interrupt-support = { git = "https://github.com/mozilla/application-services", rev = "9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" } interrupt-support = { git = "https://github.com/mozilla/application-services", rev = "41367fda038268843a87c459698691f33f0d9063" }
sql-support = { git = "https://github.com/mozilla/application-services", rev = "9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" } sql-support = { git = "https://github.com/mozilla/application-services", rev = "41367fda038268843a87c459698691f33f0d9063" }
suggest = { git = "https://github.com/mozilla/application-services", rev = "9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" } suggest = { git = "https://github.com/mozilla/application-services", rev = "41367fda038268843a87c459698691f33f0d9063" }
sync15 = { git = "https://github.com/mozilla/application-services", rev = "9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" } sync15 = { git = "https://github.com/mozilla/application-services", rev = "41367fda038268843a87c459698691f33f0d9063" }
tabs = { git = "https://github.com/mozilla/application-services", rev = "9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" } tabs = { git = "https://github.com/mozilla/application-services", rev = "41367fda038268843a87c459698691f33f0d9063" }
viaduct = { git = "https://github.com/mozilla/application-services", rev = "9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" } viaduct = { git = "https://github.com/mozilla/application-services", rev = "41367fda038268843a87c459698691f33f0d9063" }
webext-storage = { git = "https://github.com/mozilla/application-services", rev = "9d1ab279d2aa31e4c6c80dd1ee0baf9345e26253" } webext-storage = { git = "https://github.com/mozilla/application-services", rev = "41367fda038268843a87c459698691f33f0d9063" }
# Patch mio 0.8.8 to use windows-sys 0.52 (backport https://github.com/tokio-rs/mio/commit/eea9e3e0c469480e5c59c01e6c3c7e5fd88f0848) # Patch mio 0.8.8 to use windows-sys 0.52 (backport https://github.com/tokio-rs/mio/commit/eea9e3e0c469480e5c59c01e6c3c7e5fd88f0848)
mio_0_8 = { package = "mio", git = "https://github.com/glandium/mio", rev = "9a2ef335c366044ffe73b1c4acabe50a1daefe05" } mio_0_8 = { package = "mio", git = "https://github.com/glandium/mio", rev = "9a2ef335c366044ffe73b1c4acabe50a1daefe05" }

View file

@ -1 +1 @@
{"files":{"Cargo.toml":"cf73fe7b6066cb2ccccb0939f19b14d4e27d9cfdc20c9e527de3210029f2ee6f","README.md":"8030b4a314b1be31ba018ac12c3b586bb736db5307c3c395f2857fffe0130322","android/build.gradle":"d4ecda8eebf9c1b3c7542ca86652a3e8c0d2dfc0ad7426e78447a35e4fb39eab","android/src/main/AndroidManifest.xml":"108cabbbdc93da70e1da3e60b74171580872017d996c20e37946c27aaa078031","build.rs":"c8d3c38c1208eea36224662b284d8daf3e7ad1b07d22d750524f3da1cc66ccca","src/errorsupport.udl":"e793034d01a2608298528051757f38405e006ee1abc4cf65dc6f18c53590ace8","src/handling.rs":"6e0568b18d426531cb2ae9967c8dd0d51ece5a065f68b15eeb308b995edaa167","src/lib.rs":"96ae3cc2c1077ae45442ace6b5b5311b86267d0b9067f3ff58396af30ccbbc07","src/macros.rs":"0d03f82fab20c96a182f941baf3fcf2a286b00fea871ee7fd8e339abc14f9522","src/redact.rs":"c9a4df1a87be68b15d583587bda941d4c60a1d0449e2d43ff99f3611a290a863","src/reporting.rs":"38efd24d86ba8facfb181cb27e8b698d2831db0afab85691ffda034a4dc68dfa","uniffi.toml":"644fe81c12fe3c01ee81e017ca3c00d0e611f014b7eade51aadaf208179a3450"},"package":null} {"files":{"Cargo.toml":"49ef90bd388b59229db34b35fe06eb769183431c88b5712e6e9992851aef605d","README.md":"8030b4a314b1be31ba018ac12c3b586bb736db5307c3c395f2857fffe0130322","build.rs":"c8d3c38c1208eea36224662b284d8daf3e7ad1b07d22d750524f3da1cc66ccca","src/errorsupport.udl":"e793034d01a2608298528051757f38405e006ee1abc4cf65dc6f18c53590ace8","src/handling.rs":"6e0568b18d426531cb2ae9967c8dd0d51ece5a065f68b15eeb308b995edaa167","src/lib.rs":"96ae3cc2c1077ae45442ace6b5b5311b86267d0b9067f3ff58396af30ccbbc07","src/macros.rs":"0d03f82fab20c96a182f941baf3fcf2a286b00fea871ee7fd8e339abc14f9522","src/redact.rs":"c9a4df1a87be68b15d583587bda941d4c60a1d0449e2d43ff99f3611a290a863","src/reporting.rs":"38efd24d86ba8facfb181cb27e8b698d2831db0afab85691ffda034a4dc68dfa","uniffi.toml":"644fe81c12fe3c01ee81e017ca3c00d0e611f014b7eade51aadaf208179a3450"},"package":null}

View file

@ -14,6 +14,7 @@ edition = "2021"
name = "error-support" name = "error-support"
version = "0.1.0" version = "0.1.0"
authors = ["Thom Chiovoloni <tchiovoloni@mozilla.com>"] authors = ["Thom Chiovoloni <tchiovoloni@mozilla.com>"]
exclude = ["/android"]
autotests = false autotests = false
readme = "README.md" readme = "README.md"
license = "MPL-2.0" license = "MPL-2.0"

View file

@ -1,10 +0,0 @@
apply from: "$rootDir/build-scripts/component-common.gradle"
apply from: "$rootDir/publish.gradle"
android {
namespace 'org.mozilla.appservices.errorsupport'
}
ext.configureUniFFIBindgen("../src/errorsupport.udl")
ext.dependsOnTheMegazord()
ext.configurePublish()

View file

@ -1 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"/>

View file

@ -1 +1 @@
{"files":{"Cargo.toml":"cf0a08d6b0d6285a459b78115aa24818a04b5987652655e64b80ffd8c8ae0813","build.rs":"4326f03729cf8f1673e4228e6dc111de1ea4d8bcc06351f7ae563efb2613f866","src/client.rs":"3d87162e6913a81cc6f5178a7ca791e262d0d029e7dedf3df4fe2f66e5501185","src/config.rs":"7bb678addfae3b4ed5f2892d32263e5b33cc05e5a12a250f664150e78211f94a","src/error.rs":"192ca42af7c6b882f3129378c23b45dab8a0d2b179e23a8813a335ffd56b21dc","src/lib.rs":"416e99894e152f6cea7418ad2fabfd94bc3d907efd9f33fbd2a83fb99452b2df","src/remote_settings.udl":"e38758592ca75adbebb8fe688b10520d9931a5f3292d94f229cba05310756a43","uniffi.toml":"f8ec8dc593e0d501c2e9e40368ec93ec33b1edd8608e29495e0a54b63144e880"},"package":null} {"files":{"Cargo.toml":"4fa89b0606fe8ec8ac8c479b8b9adf33d0c936b09fa5af108ded74139ace37fb","build.rs":"4326f03729cf8f1673e4228e6dc111de1ea4d8bcc06351f7ae563efb2613f866","src/client.rs":"3d87162e6913a81cc6f5178a7ca791e262d0d029e7dedf3df4fe2f66e5501185","src/config.rs":"7bb678addfae3b4ed5f2892d32263e5b33cc05e5a12a250f664150e78211f94a","src/error.rs":"192ca42af7c6b882f3129378c23b45dab8a0d2b179e23a8813a335ffd56b21dc","src/lib.rs":"416e99894e152f6cea7418ad2fabfd94bc3d907efd9f33fbd2a83fb99452b2df","src/remote_settings.udl":"2e71491ad3894d17e5bde0663d9490bfea6294d99cdbe9d67a36137faeedc593","uniffi.toml":"f8ec8dc593e0d501c2e9e40368ec93ec33b1edd8608e29495e0a54b63144e880"},"package":null}

View file

@ -17,7 +17,10 @@ authors = [
"The Android Mobile Team <firefox-android-team@mozilla.com>", "The Android Mobile Team <firefox-android-team@mozilla.com>",
"The Glean Team <glean-team@mozilla.com>", "The Glean Team <glean-team@mozilla.com>",
] ]
exclude = ["/android"] exclude = [
"/android",
"/ios",
]
description = "A Remote Settings client intended for application layer platforms." description = "A Remote Settings client intended for application layer platforms."
license = "MPL-2.0" license = "MPL-2.0"

View file

@ -8,9 +8,9 @@ typedef string RsJsonObject;
namespace remote_settings {}; namespace remote_settings {};
dictionary RemoteSettingsConfig { dictionary RemoteSettingsConfig {
string? server_url = null;
string? bucket_name = null;
string collection_name; string collection_name;
string? bucket_name = null;
string? server_url = null;
}; };
dictionary RemoteSettingsResponse { dictionary RemoteSettingsResponse {
@ -54,7 +54,7 @@ interface RemoteSettings {
[Throws=RemoteSettingsError] [Throws=RemoteSettingsError]
RemoteSettingsResponse get_records(); RemoteSettingsResponse get_records();
// Fetch all records added to the server since the provided timestamp, // Fetch all records added to the server since the provided timestamp,
// using the configuration this client was initialized with. // using the configuration this client was initialized with.
[Throws=RemoteSettingsError] [Throws=RemoteSettingsError]
RemoteSettingsResponse get_records_since(u64 timestamp); RemoteSettingsResponse get_records_since(u64 timestamp);

View file

@ -1 +1 @@
{"files":{"Cargo.toml":"691ec27ed968518a05aa650230c80abe07282c1feb483d40aae4ae95cbc046ef","src/conn_ext.rs":"e48e862e47c000c545dcc766fc1889498a8709bee00e240ed68d247b0fbef577","src/debug_tools.rs":"bece2bc3d35379b81ea2f942a0a3e909e0ab0553656505904745548eacaf402a","src/each_chunk.rs":"8aaba842e43b002fbc0fee95d14ce08faa7187b1979c765b2e270cd4802607a5","src/lib.rs":"af704ec04beb6c2c388d4566710e1167b18fb64acb248ccf37a67679daffddb6","src/maybe_cached.rs":"0b18425595055883a98807fbd62ff27a79c18af34e7cb3439f8c3438463ef2dd","src/open_database.rs":"40ad2da7d5559f0e5180e35d403c307ce230fe9d0d2a3fec7c9481ce13acda64","src/repeat.rs":"b4c5ff5d083afba7f9f153f54aba2e6859b78b85c82d48dbd6bd58f67da9e6b9"},"package":null} {"files":{"Cargo.toml":"812811e5a8e00abe3ec345cd8fd435e27fec7cb8f2e45a0e93e5becf564c46ad","src/conn_ext.rs":"e48e862e47c000c545dcc766fc1889498a8709bee00e240ed68d247b0fbef577","src/debug_tools.rs":"bece2bc3d35379b81ea2f942a0a3e909e0ab0553656505904745548eacaf402a","src/each_chunk.rs":"8aaba842e43b002fbc0fee95d14ce08faa7187b1979c765b2e270cd4802607a5","src/lib.rs":"af704ec04beb6c2c388d4566710e1167b18fb64acb248ccf37a67679daffddb6","src/maybe_cached.rs":"0b18425595055883a98807fbd62ff27a79c18af34e7cb3439f8c3438463ef2dd","src/open_database.rs":"40ad2da7d5559f0e5180e35d403c307ce230fe9d0d2a3fec7c9481ce13acda64","src/repeat.rs":"b4c5ff5d083afba7f9f153f54aba2e6859b78b85c82d48dbd6bd58f67da9e6b9"},"package":null}

View file

@ -40,7 +40,7 @@ features = [
] ]
[dev-dependencies.env_logger] [dev-dependencies.env_logger]
version = "0.7" version = "0.10"
default-features = false default-features = false
[build-dependencies.nss_build_common] [build-dependencies.nss_build_common]

View file

@ -1 +1 @@
{"files":{"Cargo.toml":"7e5e03ee92f01a28fa398d77167619e5a55c73db083cc27b74c4d63ab44db173","README.md":"8d7457893194e255b87e5a2667ee25c87bd470f5338d7078506f866a67a3fdbd","build.rs":"78780c5cccfe22c3ff4198624b9e188559c437c3e6fa1c8bb66548eee6aa66bf","src/db.rs":"6d39e96bcdaa2b01dc662b3ed17b3a022246534b919b13be637e4646c3b9c1dd","src/error.rs":"f47763a1a5d228b446eb8f718433e49fdb1c7b304de1255891215144dddd7a43","src/keyword.rs":"988d0ab021c0df19cfd3c519df7d37f606bf984cd14d0efca4e5a7aff88344dd","src/lib.rs":"95ac0da8585ddfe712884e05ae53fd5fdf79564675a1070222e4a310a79959f4","src/pocket.rs":"c4dda43390d1c39dc795933596b3c1e4e282932cac6c69da53c6e05d39e9ef29","src/provider.rs":"8cecefb01d3c09c164d7334647e586c5407e14d0ee7ef99eea6e5095ec59586c","src/rs.rs":"1388ae7473d1d87f7eb731b1e34bd1d2f8ef844b7e0f09d7e3442cfc7d56286a","src/schema.rs":"3135d28652e3f0df6670a3afa3bdf03d95039383720f1f0d77523c0f4faf1797","src/store.rs":"6743136b02bcc3caec89ff1386a8ae5a5e89bab2bb095c60eb905f5c19735be9","src/suggest.udl":"e129cc04665a8484572c57c87b4a1713a7600254bf4110e06a5fe0d4eb746bb5","src/suggestion.rs":"dfe7c02e11ea9a01d3fdd71eb2c83b1c1d1b830c34f76829a91a01cda44c38b8","src/yelp.rs":"5d788854c3e1f9e5a2e55a8ecc4c5a10c1ad93ab84791535e4d88ec94602d615","uniffi.toml":"f26317442ddb5b3281245bef6e60ffcb78bb95d29fe4a351a56dbb88d4ec8aab"},"package":null} {"files":{"Cargo.toml":"4aa81cff67e67b08ba3348c1acddaa5aee887df3c35006754c9cda4273a94458","README.md":"8d7457893194e255b87e5a2667ee25c87bd470f5338d7078506f866a67a3fdbd","build.rs":"78780c5cccfe22c3ff4198624b9e188559c437c3e6fa1c8bb66548eee6aa66bf","src/config.rs":"03630b2219b6674e332a1f96f44db74def17f985c850a800299b815fa72241c2","src/db.rs":"208916c915f29fa2fd8725f635822d63ac86023ea0eb2050a92d4c7fbc9f6697","src/error.rs":"f563210a6c050d98ec85e0f6d9401e7373bfb816e865e8edabbabb23d848ba13","src/keyword.rs":"988d0ab021c0df19cfd3c519df7d37f606bf984cd14d0efca4e5a7aff88344dd","src/lib.rs":"65a035dbfb17e2d2d9f237ad52dc03982ae28c70e3dcf3d96cc9f2d7af79efe3","src/pocket.rs":"c4dda43390d1c39dc795933596b3c1e4e282932cac6c69da53c6e05d39e9ef29","src/provider.rs":"3fe8f90d77586f5ff683374f24df026110bfaaab128fd4503d2c695eed71d4fe","src/rs.rs":"038e954eaeaa6898a2edb4d29728233a6286ac818b42028da1ba5c1b03a3efc0","src/schema.rs":"f7995c1cdd98c642e4497b5f3d0881a3ab6ac4f086172142c195adb0934b93de","src/store.rs":"b868b2853beba800cf43183f2b629a4024e435fa9617dcd751480ee89f8fe12f","src/suggest.udl":"9b82f97afb49d94a82cad3a9d44a25e8cab3b998b018ea9c3467b170b795e65b","src/suggestion.rs":"355f01b8a82a55a506e0a4c7d26dcf6d059802f84df3789309f5e0f42b80d793","src/yelp.rs":"2844e639bdf163fc23ba6e463495f47ec8d9c617de4754b49a58aa16d6ea39f5","uniffi.toml":"f26317442ddb5b3281245bef6e60ffcb78bb95d29fe4a351a56dbb88d4ec8aab"},"package":null}

View file

@ -30,6 +30,9 @@ serde_json = "1"
thiserror = "1" thiserror = "1"
uniffi = "0.25.2" uniffi = "0.25.2"
[dependencies.error-support]
path = "../support/error"
[dependencies.interrupt-support] [dependencies.interrupt-support]
path = "../support/interrupt" path = "../support/interrupt"
@ -54,12 +57,15 @@ path = "../support/sql"
version = "2.1" version = "2.1"
features = ["serde"] features = ["serde"]
[dependencies.viaduct]
path = "../viaduct"
[dev-dependencies] [dev-dependencies]
expect-test = "1.4" expect-test = "1.4"
hex = "0.4" hex = "0.4"
[dev-dependencies.env_logger] [dev-dependencies.env_logger]
version = "0.7" version = "0.10"
default-features = false default-features = false
[dev-dependencies.rc_crypto] [dev-dependencies.rc_crypto]

31
third_party/rust/suggest/src/config.rs vendored Normal file
View file

@ -0,0 +1,31 @@
use serde::{Deserialize, Serialize};
use crate::rs::{DownloadedGlobalConfig, DownloadedWeatherData};
/// Global Suggest configuration data.
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
pub struct SuggestGlobalConfig {
pub show_less_frequently_cap: i32,
}
impl From<&DownloadedGlobalConfig> for SuggestGlobalConfig {
fn from(config: &DownloadedGlobalConfig) -> Self {
Self {
show_less_frequently_cap: config.configuration.show_less_frequently_cap,
}
}
}
/// Per-provider configuration data.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum SuggestProviderConfig {
Weather { min_keyword_length: i32 },
}
impl From<&DownloadedWeatherData> for SuggestProviderConfig {
fn from(data: &DownloadedWeatherData) -> Self {
Self::Weather {
min_keyword_length: data.weather.min_keyword_length,
}
}
}

View file

@ -3,10 +3,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
use std::{path::Path, sync::Arc}; use std::{collections::HashSet, path::Path, sync::Arc};
use interrupt_support::{SqlInterruptHandle, SqlInterruptScope}; use interrupt_support::{SqlInterruptHandle, SqlInterruptScope};
use parking_lot::Mutex; use parking_lot::Mutex;
use remote_settings::RemoteSettingsRecord;
use rusqlite::{ use rusqlite::{
named_params, named_params,
types::{FromSql, ToSql}, types::{FromSql, ToSql},
@ -14,12 +15,15 @@ use rusqlite::{
}; };
use sql_support::{open_database::open_database_with_flags, ConnExt}; use sql_support::{open_database::open_database_with_flags, ConnExt};
use crate::rs::{DownloadedAmoSuggestion, DownloadedPocketSuggestion};
use crate::{ use crate::{
config::{SuggestGlobalConfig, SuggestProviderConfig},
keyword::full_keyword, keyword::full_keyword,
pocket::{split_keyword, KeywordConfidence}, pocket::{split_keyword, KeywordConfidence},
provider::SuggestionProvider, provider::SuggestionProvider,
rs::{DownloadedAmpWikipediaSuggestion, SuggestRecordId}, rs::{
DownloadedAmoSuggestion, DownloadedAmpWikipediaSuggestion, DownloadedMdnSuggestion,
DownloadedPocketSuggestion, DownloadedWeatherData, SuggestRecordId,
},
schema::{SuggestConnectionInitializer, VERSION}, schema::{SuggestConnectionInitializer, VERSION},
store::{UnparsableRecord, UnparsableRecords}, store::{UnparsableRecord, UnparsableRecords},
suggestion::{cook_raw_suggestion_url, Suggestion}, suggestion::{cook_raw_suggestion_url, Suggestion},
@ -32,6 +36,16 @@ pub const LAST_INGEST_META_KEY: &str = "last_quicksuggest_ingest";
/// The metadata key whose value keeps track of records of suggestions /// The metadata key whose value keeps track of records of suggestions
/// that aren't parsable and which schema version it was first seen in. /// that aren't parsable and which schema version it was first seen in.
pub const UNPARSABLE_RECORDS_META_KEY: &str = "unparsable_records"; pub const UNPARSABLE_RECORDS_META_KEY: &str = "unparsable_records";
/// The metadata key whose value is a JSON string encoding a
/// `SuggestGlobalConfig`, which contains global Suggest configuration data.
pub const GLOBAL_CONFIG_META_KEY: &str = "global_config";
/// Prefix of metadata keys whose values are JSON strings encoding
/// `SuggestProviderConfig`, which contains per-provider configuration data. The
/// full key is this prefix plus the `SuggestionProvider` value as a u8.
pub const PROVIDER_CONFIG_META_KEY_PREFIX: &str = "provider_config_";
// Default value when Suggestion does not have a value for score
pub const DEFAULT_SUGGESTION_SCORE: f64 = 0.2;
/// The database connection type. /// The database connection type.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -123,65 +137,93 @@ impl<'a> SuggestDao<'a> {
Self { conn, scope } Self { conn, scope }
} }
// =============== High level API ===============
//
// These methods combine several low-level calls into one logical operation.
pub fn handle_unparsable_record(&mut self, record: &RemoteSettingsRecord) -> Result<()> {
let record_id = SuggestRecordId::from(&record.id);
// Remember this record's ID so that we will try again later
self.put_unparsable_record_id(&record_id)?;
// Advance the last fetch time, so that we can resume
// fetching after this record if we're interrupted.
self.put_last_ingest_if_newer(record.last_modified)
}
pub fn handle_ingested_record(&mut self, record: &RemoteSettingsRecord) -> Result<()> {
let record_id = SuggestRecordId::from(&record.id);
// Remove this record's ID from the list of unparsable
// records, since we understand it now.
self.drop_unparsable_record_id(&record_id)?;
// Advance the last fetch time, so that we can resume
// fetching after this record if we're interrupted.
self.put_last_ingest_if_newer(record.last_modified)
}
pub fn handle_deleted_record(&mut self, record: &RemoteSettingsRecord) -> Result<()> {
let record_id = SuggestRecordId::from(&record.id);
// Drop either the icon or suggestions, records only contain one or the other
match record_id.as_icon_id() {
Some(icon_id) => self.drop_icon(icon_id)?,
None => self.drop_suggestions(&record_id)?,
};
// Remove this record's ID from the list of unparsable
// records, since we understand it now.
self.drop_unparsable_record_id(&record_id)?;
// Advance the last fetch time, so that we can resume
// fetching after this record if we're interrupted.
self.put_last_ingest_if_newer(record.last_modified)
}
// =============== Low level API ===============
//
// These methods implement CRUD operations
/// Fetches suggestions that match the given query from the database. /// Fetches suggestions that match the given query from the database.
pub fn fetch_suggestions(&self, query: &SuggestionQuery) -> Result<Vec<Suggestion>> { pub fn fetch_suggestions(&self, query: &SuggestionQuery) -> Result<Vec<Suggestion>> {
if let Some(suggestion) = self.fetch_yelp_suggestion(query)? { let unique_providers = query.providers.iter().collect::<HashSet<_>>();
return Ok(vec![suggestion]); unique_providers
}
let keyword_lowercased = &query.keyword.to_lowercase();
let (keyword_prefix, keyword_suffix) = split_keyword(keyword_lowercased);
let suggestions_limit = query.limit.unwrap_or(-1);
let (mut statement, params) = if query
.providers
.iter() .iter()
.any(|p| matches!(p, SuggestionProvider::Pocket | SuggestionProvider::Amo)) .try_fold(vec![], |mut acc, provider| {
{ let suggestions = match provider {
(self.conn.prepare_cached( SuggestionProvider::Amp => self.fetch_amp_suggestions(query),
&format!( SuggestionProvider::Wikipedia => self.fetch_wikipedia_suggestions(query),
"SELECT s.id, k.rank, s.title, s.url, s.provider, NULL as confidence, NULL as keyword_suffix SuggestionProvider::Amo => self.fetch_amo_suggestions(query),
FROM suggestions s SuggestionProvider::Pocket => self.fetch_pocket_suggestions(query),
JOIN keywords k ON k.suggestion_id = s.id SuggestionProvider::Yelp => self.fetch_yelp_suggestions(query),
WHERE s.provider IN ({}) AND SuggestionProvider::Mdn => self.fetch_mdn_suggestions(query),
k.keyword = :keyword SuggestionProvider::Weather => self.fetch_weather_suggestions(query),
UNION ALL }?;
SELECT s.id, k.rank, s.title, s.url, s.provider, k.confidence, k.keyword_suffix acc.extend(suggestions);
FROM suggestions s Ok(acc)
JOIN prefix_keywords k ON k.suggestion_id = s.id })
WHERE k.keyword_prefix = :keyword_prefix .map(|mut suggestions| {
ORDER BY s.provider suggestions.sort();
LIMIT :suggestions_limit", if let Some(limit) = query.limit.and_then(|limit| usize::try_from(limit).ok()) {
providers_to_sql_list(&query.providers), suggestions.truncate(limit);
), }
)?, vec![ suggestions
(":keyword", keyword_lowercased as &dyn ToSql), })
(":keyword_prefix", &keyword_prefix as &dyn ToSql), }
(":suggestions_limit", &suggestions_limit as &dyn ToSql),
])
} else {
(self.conn.prepare_cached(
&format!(
"SELECT s.id, k.rank, s.title, s.url, s.provider, NULL as confidence, NULL as keyword_suffix
FROM suggestions s
JOIN keywords k ON k.suggestion_id = s.id
WHERE s.provider IN ({}) AND
k.keyword = :keyword
ORDER BY s.provider
LIMIT :suggestions_limit",
providers_to_sql_list(&query.providers),
),
)?, vec![
(":keyword", keyword_lowercased as &dyn ToSql),
(":suggestions_limit", &suggestions_limit as &dyn ToSql),
])
};
let suggestions = statement.query_and_then(&*params, |row| -> Result<Option<Suggestion>> { /// Fetches Suggestions of type Amp provider that match the given query
pub fn fetch_amp_suggestions(&self, query: &SuggestionQuery) -> Result<Vec<Suggestion>> {
let keyword_lowercased = &query.keyword.to_lowercase();
let suggestions = self.conn.query_rows_and_then_cached(
"SELECT s.id, k.rank, s.title, s.url, s.provider, s.score
FROM suggestions s
JOIN keywords k ON k.suggestion_id = s.id
WHERE s.provider = :provider AND
k.keyword = :keyword",
named_params! {
":keyword": keyword_lowercased,
":provider": SuggestionProvider::Amp
},
|row| -> Result<Suggestion>{
let suggestion_id: i64 = row.get("id")?; let suggestion_id: i64 = row.get("id")?;
let title = row.get("title")?; let title = row.get("title")?;
let raw_url = row.get::<_, String>("url")?; let raw_url = row.get::<_, String>("url")?;
let provider = row.get("provider")?; let score = row.get::<_, f64>("score")?;
let keywords: Vec<String> = self.conn.query_rows_and_then_cached( let keywords: Vec<String> = self.conn.query_rows_and_then_cached(
"SELECT keyword FROM keywords "SELECT keyword FROM keywords
@ -193,120 +235,280 @@ impl<'a> SuggestDao<'a> {
}, },
|row| row.get(0), |row| row.get(0),
)?; )?;
self.conn.query_row_and_then(
match provider { "SELECT amp.advertiser, amp.block_id, amp.iab_category, amp.impression_url, amp.click_url,
SuggestionProvider::Amp => { (SELECT i.data FROM icons i WHERE i.id = amp.icon_id) AS icon
self.conn.query_row_and_then( FROM amp_custom_details amp
"SELECT amp.advertiser, amp.block_id, amp.iab_category, amp.impression_url, amp.click_url, WHERE amp.suggestion_id = :suggestion_id",
(SELECT i.data FROM icons i WHERE i.id = amp.icon_id) AS icon named_params! {
FROM amp_custom_details amp ":suggestion_id": suggestion_id
WHERE amp.suggestion_id = :suggestion_id",
named_params! {
":suggestion_id": suggestion_id
},
|row| {
let cooked_url = cook_raw_suggestion_url(&raw_url);
let raw_click_url = row.get::<_, String>("click_url")?;
let cooked_click_url = cook_raw_suggestion_url(&raw_click_url);
Ok(Some(Suggestion::Amp {
block_id: row.get("block_id")?,
advertiser: row.get("advertiser")?,
iab_category: row.get("iab_category")?,
title,
url: cooked_url,
raw_url,
full_keyword: full_keyword(keyword_lowercased, &keywords),
icon: row.get("icon")?,
impression_url: row.get("impression_url")?,
click_url: cooked_click_url,
raw_click_url,
}))
}
)
}, },
SuggestionProvider::Wikipedia => { |row| {
let icon = self.conn.try_query_one( let cooked_url = cook_raw_suggestion_url(&raw_url);
"SELECT i.data let raw_click_url = row.get::<_, String>("click_url")?;
FROM icons i let cooked_click_url = cook_raw_suggestion_url(&raw_click_url);
JOIN wikipedia_custom_details s ON s.icon_id = i.id Ok(Suggestion::Amp {
WHERE s.suggestion_id = :suggestion_id", block_id: row.get("block_id")?,
named_params! { advertiser: row.get("advertiser")?,
":suggestion_id": suggestion_id iab_category: row.get("iab_category")?,
}, title,
true, url: cooked_url,
)?; raw_url,
Ok(Some(Suggestion::Wikipedia { full_keyword: full_keyword(keyword_lowercased, &keywords),
icon: row.get("icon")?,
impression_url: row.get("impression_url")?,
click_url: cooked_click_url,
raw_click_url,
score,
})
}
)
}
)?;
Ok(suggestions)
}
/// Fetches Suggestions of type Wikipedia provider that match the given query
pub fn fetch_wikipedia_suggestions(&self, query: &SuggestionQuery) -> Result<Vec<Suggestion>> {
let keyword_lowercased = &query.keyword.to_lowercase();
let suggestions = self.conn.query_rows_and_then_cached(
"SELECT s.id, k.rank, s.title, s.url
FROM suggestions s
JOIN keywords k ON k.suggestion_id = s.id
WHERE s.provider = :provider AND
k.keyword = :keyword",
named_params! {
":keyword": keyword_lowercased,
":provider": SuggestionProvider::Wikipedia
},
|row| -> Result<Suggestion> {
let suggestion_id: i64 = row.get("id")?;
let title = row.get("title")?;
let raw_url = row.get::<_, String>("url")?;
let keywords: Vec<String> = self.conn.query_rows_and_then_cached(
"SELECT keyword FROM keywords
WHERE suggestion_id = :suggestion_id AND rank >= :rank
ORDER BY rank ASC",
named_params! {
":suggestion_id": suggestion_id,
":rank": row.get::<_, i64>("rank")?,
},
|row| row.get(0),
)?;
let icon = self.conn.try_query_one(
"SELECT i.data
FROM icons i
JOIN wikipedia_custom_details s ON s.icon_id = i.id
WHERE s.suggestion_id = :suggestion_id",
named_params! {
":suggestion_id": suggestion_id
},
true,
)?;
Ok(Suggestion::Wikipedia {
title,
url: raw_url,
full_keyword: full_keyword(keyword_lowercased, &keywords),
icon,
})
},
)?;
Ok(suggestions)
}
/// Fetches Suggestions of type Amo provider that match the given query
pub fn fetch_amo_suggestions(&self, query: &SuggestionQuery) -> Result<Vec<Suggestion>> {
let keyword_lowercased = &query.keyword.to_lowercase();
let (keyword_prefix, keyword_suffix) = split_keyword(keyword_lowercased);
let suggestions_limit = &query.limit.unwrap_or(-1);
let suggestions = self.conn.query_rows_and_then_cached(
"SELECT s.id, k.rank, s.title, s.url, s.provider, s.score, k.confidence, k.keyword_suffix
FROM suggestions s
JOIN prefix_keywords k ON k.suggestion_id = s.id
WHERE k.keyword_prefix = :keyword_prefix AND s.provider = :provider
ORDER by s.score DESC
LIMIT :suggestions_limit",
named_params! {
":keyword_prefix": keyword_prefix,
":provider": SuggestionProvider::Amo,
":suggestions_limit": suggestions_limit,
},
|row| -> Result<Option<Suggestion>>{
let suggestion_id: i64 = row.get("id")?;
let title = row.get("title")?;
let raw_url = row.get::<_, String>("url")?;
let score = row.get::<_, f64>("score")?;
let full_suffix = row.get::<_, String>("keyword_suffix")?;
full_suffix.starts_with(keyword_suffix).then(||
self.conn.query_row_and_then(
"SELECT amo.description, amo.guid, amo.rating, amo.icon_url, amo.number_of_ratings
FROM amo_custom_details amo
WHERE amo.suggestion_id = :suggestion_id",
named_params! {
":suggestion_id": suggestion_id
},
|row| {
Ok(Suggestion::Amo {
title, title,
url: raw_url, url: raw_url,
full_keyword: full_keyword(keyword_lowercased, &keywords), icon_url: row.get("icon_url")?,
icon, description: row.get("description")?,
})) rating: row.get("rating")?,
} number_of_ratings: row.get("number_of_ratings")?,
SuggestionProvider::Amo => { guid: row.get("guid")?,
let full_suffix = row.get::<_, String>("keyword_suffix")?; score,
self.conn.query_row_and_then( })
"SELECT amo.description, amo.guid, amo.rating, amo.icon_url, amo.number_of_ratings, amo.score })).transpose()
FROM amo_custom_details amo }
WHERE amo.suggestion_id = :suggestion_id", )?.into_iter().flatten().collect();
named_params! { Ok(suggestions)
":suggestion_id": suggestion_id }
},
|row| { /// Fetches Suggestions of type pocket provider that match the given query
if full_suffix.starts_with(keyword_suffix) { pub fn fetch_pocket_suggestions(&self, query: &SuggestionQuery) -> Result<Vec<Suggestion>> {
Ok(Some(Suggestion::Amo{ let keyword_lowercased = &query.keyword.to_lowercase();
title, let (keyword_prefix, keyword_suffix) = split_keyword(keyword_lowercased);
url: raw_url, let suggestions_limit = &query.limit.unwrap_or(-1);
icon_url: row.get("icon_url")?, let suggestions = self.conn.query_rows_and_then_cached(
description: row.get("description")?, "SELECT s.id, k.rank, s.title, s.url, s.provider, s.score, k.confidence, k.keyword_suffix
rating: row.get("rating")?, FROM suggestions s
number_of_ratings: row.get("number_of_ratings")?, JOIN prefix_keywords k ON k.suggestion_id = s.id
guid: row.get("guid")?, WHERE k.keyword_prefix = :keyword_prefix AND s.provider = :provider
score: row.get("score")?, ORDER BY s.score DESC
})) LIMIT :suggestions_limit",
} else { named_params! {
Ok(None) ":keyword_prefix": keyword_prefix,
} ":provider": SuggestionProvider::Pocket,
}) ":suggestions_limit": suggestions_limit,
},
SuggestionProvider::Pocket => { },
let confidence = row.get("confidence")?; |row| -> Result<Option<Suggestion>>{
let full_suffix = row.get::<_, String>("keyword_suffix")?; let title = row.get("title")?;
let suffixes_match = match confidence { let raw_url = row.get::<_, String>("url")?;
KeywordConfidence::Low => full_suffix.starts_with(keyword_suffix), let score = row.get::<_, f64>("score")?;
KeywordConfidence::High => full_suffix == keyword_suffix, let confidence = row.get("confidence")?;
}; let full_suffix = row.get::<_, String>("keyword_suffix")?;
if suffixes_match { let suffixes_match = match confidence {
KeywordConfidence::Low => full_suffix.starts_with(keyword_suffix),
KeywordConfidence::High => full_suffix == keyword_suffix,
};
if suffixes_match {
Ok(Some(Suggestion::Pocket {
title,
url: raw_url,
score,
is_top_pick: matches!(
confidence,
KeywordConfidence::High)
}))
} else {
Ok(None)
}
}
)?.into_iter().flatten().collect();
Ok(suggestions)
}
/// Fetches suggestions for MDN
pub fn fetch_mdn_suggestions(&self, query: &SuggestionQuery) -> Result<Vec<Suggestion>> {
let keyword_lowercased = &query.keyword.to_lowercase();
let (keyword_prefix, keyword_suffix) = split_keyword(keyword_lowercased);
let suggestions_limit = &query.limit.unwrap_or(-1);
let suggestions = self
.conn
.query_rows_and_then_cached(
r#"
SELECT
s.id, s.title, s.url, s.provider, s.score, k.keyword_suffix
FROM
suggestions s
JOIN
prefix_keywords k ON k.suggestion_id = s.id
WHERE
k.keyword_prefix = :keyword_prefix
AND
s.provider = :provider
ORDER BY
s.score DESC
LIMIT :suggestions_limit
"#,
named_params! {
":keyword_prefix": keyword_prefix,
":provider": SuggestionProvider::Mdn,
":suggestions_limit": suggestions_limit,
},
|row| -> Result<Option<Suggestion>> {
let suggestion_id: i64 = row.get("id")?;
let title = row.get("title")?;
let raw_url = row.get::<_, String>("url")?;
let score = row.get::<_, f64>("score")?;
let full_suffix = row.get::<_, String>("keyword_suffix")?;
full_suffix
.starts_with(keyword_suffix)
.then(|| {
self.conn.query_row_and_then( self.conn.query_row_and_then(
"SELECT p.score r#"
FROM pocket_custom_details p SELECT
WHERE p.suggestion_id = :suggestion_id", description
FROM
mdn_custom_details
WHERE
suggestion_id = :suggestion_id
"#,
named_params! { named_params! {
":suggestion_id": suggestion_id ":suggestion_id": suggestion_id
}, },
|row| { |row| {
Ok(Some(Suggestion::Pocket { Ok(Suggestion::Mdn {
title, title,
url: raw_url, url: raw_url,
score: row.get("score")?, description: row.get("description")?,
is_top_pick: matches!( score,
confidence, })
KeywordConfidence::High },
)
}))
}
) )
} else { })
Ok(None) .transpose()
} },
}, )?
_ => Ok(None), .into_iter()
} .flatten()
} .collect();
)?.flat_map(Result::transpose).collect::<Result<_>>()?;
Ok(suggestions) Ok(suggestions)
} }
/// Fetches weather suggestions
pub fn fetch_weather_suggestions(&self, query: &SuggestionQuery) -> Result<Vec<Suggestion>> {
// Weather keywords are matched by prefix but the query must be at least
// three chars long. Unlike the prefix matching of other suggestion
// types, the query doesn't need to contain the first full word.
if query.keyword.len() < 3 {
return Ok(vec![]);
}
let keyword_lowercased = &query.keyword.trim().to_lowercase();
let suggestions = self.conn.query_rows_and_then_cached(
"SELECT s.score
FROM suggestions s
JOIN keywords k ON k.suggestion_id = s.id
WHERE s.provider = :provider AND (k.keyword BETWEEN :keyword AND :keyword || X'FFFF')",
named_params! {
":keyword": keyword_lowercased,
":provider": SuggestionProvider::Weather
},
|row| -> Result<Suggestion> {
Ok(Suggestion::Weather {
score: row.get::<_, f64>("score")?,
})
},
)?;
Ok(suggestions)
}
/// Inserts all suggestions from a downloaded AMO attachment into /// Inserts all suggestions from a downloaded AMO attachment into
/// the database. /// the database.
pub fn insert_amo_suggestions( pub fn insert_amo_suggestions(
@ -322,13 +524,15 @@ impl<'a> SuggestDao<'a> {
record_id, record_id,
provider, provider,
title, title,
url url,
score
) )
VALUES( VALUES(
:record_id, :record_id,
{}, {},
:title, :title,
:url :url,
:score
) )
RETURNING id", RETURNING id",
SuggestionProvider::Amo as u8 SuggestionProvider::Amo as u8
@ -337,6 +541,7 @@ impl<'a> SuggestDao<'a> {
":record_id": record_id.as_str(), ":record_id": record_id.as_str(),
":title": suggestion.title, ":title": suggestion.title,
":url": suggestion.url, ":url": suggestion.url,
":score": suggestion.score,
}, },
|row| row.get(0), |row| row.get(0),
true, true,
@ -348,8 +553,7 @@ impl<'a> SuggestDao<'a> {
guid, guid,
icon_url, icon_url,
rating, rating,
number_of_ratings, number_of_ratings
score
) )
VALUES( VALUES(
:suggestion_id, :suggestion_id,
@ -357,8 +561,7 @@ impl<'a> SuggestDao<'a> {
:guid, :guid,
:icon_url, :icon_url,
:rating, :rating,
:number_of_ratings, :number_of_ratings
:score
)", )",
named_params! { named_params! {
":suggestion_id": suggestion_id, ":suggestion_id": suggestion_id,
@ -366,8 +569,7 @@ impl<'a> SuggestDao<'a> {
":guid": suggestion.guid, ":guid": suggestion.guid,
":icon_url": suggestion.icon_url, ":icon_url": suggestion.icon_url,
":rating": suggestion.rating, ":rating": suggestion.rating,
":number_of_ratings": suggestion.number_of_ratings, ":number_of_ratings": suggestion.number_of_ratings
":score": suggestion.score,
}, },
)?; )?;
for (index, keyword) in suggestion.keywords.iter().enumerate() { for (index, keyword) in suggestion.keywords.iter().enumerate() {
@ -414,13 +616,15 @@ impl<'a> SuggestDao<'a> {
record_id, record_id,
provider, provider,
title, title,
url url,
score
) )
VALUES( VALUES(
:record_id, :record_id,
{}, {},
:title, :title,
:url :url,
:score
) )
RETURNING id", RETURNING id",
provider as u8 provider as u8
@ -429,7 +633,7 @@ impl<'a> SuggestDao<'a> {
":record_id": record_id.as_str(), ":record_id": record_id.as_str(),
":title": common_details.title, ":title": common_details.title,
":url": common_details.url, ":url": common_details.url,
":score": common_details.score.unwrap_or(DEFAULT_SUGGESTION_SCORE)
}, },
|row| row.get(0), |row| row.get(0),
true, true,
@ -521,13 +725,15 @@ impl<'a> SuggestDao<'a> {
record_id, record_id,
provider, provider,
title, title,
url url,
score
) )
VALUES( VALUES(
:record_id, :record_id,
{}, {},
:title, :title,
:url :url,
:score
) )
RETURNING id", RETURNING id",
SuggestionProvider::Pocket as u8 SuggestionProvider::Pocket as u8
@ -536,24 +742,12 @@ impl<'a> SuggestDao<'a> {
":record_id": record_id.as_str(), ":record_id": record_id.as_str(),
":title": suggestion.title, ":title": suggestion.title,
":url": suggestion.url, ":url": suggestion.url,
":score": suggestion.score,
}, },
|row| row.get(0), |row| row.get(0),
true, true,
)?; )?;
self.conn.execute(
"INSERT INTO pocket_custom_details(
suggestion_id,
score
)
VALUES(
:suggestion_id,
:score
)",
named_params! {
":suggestion_id": suggestion_id,
":score": suggestion.score,
},
)?;
for ((rank, keyword), confidence) in suggestion for ((rank, keyword), confidence) in suggestion
.high_confidence_keywords .high_confidence_keywords
.iter() .iter()
@ -596,6 +790,123 @@ impl<'a> SuggestDao<'a> {
Ok(()) Ok(())
} }
/// Inserts all suggestions from a downloaded MDN attachment into
/// the database.
pub fn insert_mdn_suggestions(
&mut self,
record_id: &SuggestRecordId,
suggestions: &[DownloadedMdnSuggestion],
) -> Result<()> {
for suggestion in suggestions {
self.scope.err_if_interrupted()?;
let suggestion_id: i64 = self.conn.query_row_and_then_cachable(
&format!(
"INSERT INTO suggestions(
record_id,
provider,
title,
url,
score
)
VALUES(
:record_id,
{},
:title,
:url,
:score
)
RETURNING id",
SuggestionProvider::Mdn as u8
),
named_params! {
":record_id": record_id.as_str(),
":title": suggestion.title,
":url": suggestion.url,
":score": suggestion.score,
},
|row| row.get(0),
true,
)?;
self.conn.execute_cached(
"INSERT INTO mdn_custom_details(
suggestion_id,
description
)
VALUES(
:suggestion_id,
:description
)",
named_params! {
":suggestion_id": suggestion_id,
":description": suggestion.description,
},
)?;
for (index, keyword) in suggestion.keywords.iter().enumerate() {
let (keyword_prefix, keyword_suffix) = split_keyword(keyword);
self.conn.execute_cached(
"INSERT INTO prefix_keywords(
keyword_prefix,
keyword_suffix,
suggestion_id,
rank
)
VALUES(
:keyword_prefix,
:keyword_suffix,
:suggestion_id,
:rank
)",
named_params! {
":keyword_prefix": keyword_prefix,
":keyword_suffix": keyword_suffix,
":rank": index,
":suggestion_id": suggestion_id,
},
)?;
}
}
Ok(())
}
/// Inserts weather record data into the database.
pub fn insert_weather_data(
&mut self,
record_id: &SuggestRecordId,
data: &DownloadedWeatherData,
) -> Result<()> {
self.scope.err_if_interrupted()?;
let suggestion_id: i64 = self.conn.query_row_and_then_cachable(
&format!(
"INSERT INTO suggestions(record_id, provider, title, url, score)
VALUES(:record_id, {}, '', '', :score)
RETURNING id",
SuggestionProvider::Weather as u8
),
named_params! {
":record_id": record_id.as_str(),
":score": data.weather.score.unwrap_or(DEFAULT_SUGGESTION_SCORE),
},
|row| row.get(0),
true,
)?;
for (index, keyword) in data.weather.keywords.iter().enumerate() {
self.conn.execute(
"INSERT INTO keywords(keyword, suggestion_id, rank)
VALUES(:keyword, :suggestion_id, :rank)",
named_params! {
":keyword": keyword,
":suggestion_id": suggestion_id,
":rank": index,
},
)?;
}
self.put_provider_config(
SuggestionProvider::Weather,
&SuggestProviderConfig::from(data),
)?;
Ok(())
}
/// Inserts or replaces an icon for a suggestion into the database. /// Inserts or replaces an icon for a suggestion into the database.
pub fn put_icon(&mut self, icon_id: &str, data: &[u8]) -> Result<()> { pub fn put_icon(&mut self, icon_id: &str, data: &[u8]) -> Result<()> {
self.conn.execute( self.conn.execute(
@ -723,13 +1034,45 @@ impl<'a> SuggestDao<'a> {
}; };
self.put_meta(UNPARSABLE_RECORDS_META_KEY, unparsable_records) self.put_meta(UNPARSABLE_RECORDS_META_KEY, unparsable_records)
} }
/// Stores global Suggest configuration data.
pub fn put_global_config(&mut self, config: &SuggestGlobalConfig) -> Result<()> {
self.put_meta(GLOBAL_CONFIG_META_KEY, serde_json::to_string(config)?)
}
/// Gets the stored global Suggest configuration data or a default config if
/// none is stored.
pub fn get_global_config(&self) -> Result<SuggestGlobalConfig> {
self.get_meta::<String>(GLOBAL_CONFIG_META_KEY)?
.map_or_else(
|| Ok(SuggestGlobalConfig::default()),
|json| Ok(serde_json::from_str(&json)?),
)
}
/// Stores configuration data for a given provider.
pub fn put_provider_config(
&mut self,
provider: SuggestionProvider,
config: &SuggestProviderConfig,
) -> Result<()> {
self.put_meta(
&provider_config_meta_key(provider),
serde_json::to_string(config)?,
)
}
/// Gets the stored configuration data for a given provider or None if none
/// is stored.
pub fn get_provider_config(
&self,
provider: SuggestionProvider,
) -> Result<Option<SuggestProviderConfig>> {
self.get_meta::<String>(&provider_config_meta_key(provider))?
.map_or_else(|| Ok(None), |json| Ok(serde_json::from_str(&json)?))
}
} }
/// Formats a slice of [`SuggestionProvider`]s as a SQL list. fn provider_config_meta_key(provider: SuggestionProvider) -> String {
fn providers_to_sql_list(providers: &[SuggestionProvider]) -> String { format!("{}{}", PROVIDER_CONFIG_META_KEY_PREFIX, provider as u8)
providers
.iter()
.map(|&provider| (provider as u8).to_string())
.collect::<Vec<_>>()
.join(",")
} }

View file

@ -3,6 +3,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
use error_support::{ErrorHandling, GetErrorHandling};
use remote_settings::RemoteSettingsError;
/// A list of errors that are internal to the component. This is the error /// A list of errors that are internal to the component. This is the error
/// type for private and crate-internal methods, and is never returned to the /// type for private and crate-internal methods, and is never returned to the
/// application. /// application.
@ -18,10 +21,13 @@ pub(crate) enum Error {
Json(#[from] serde_json::Error), Json(#[from] serde_json::Error),
#[error("Error from Remote Settings: {0}")] #[error("Error from Remote Settings: {0}")]
RemoteSettings(#[from] remote_settings::RemoteSettingsError), RemoteSettings(#[from] RemoteSettingsError),
#[error("Operation interrupted")] #[error("Operation interrupted")]
Interrupted(#[from] interrupt_support::Interrupted), Interrupted(#[from] interrupt_support::Interrupted),
#[error("SuggestStoreBuilder {0}")]
SuggestStoreBuilder(String),
} }
/// The error type for all Suggest component operations. These errors are /// The error type for all Suggest component operations. These errors are
@ -29,15 +35,45 @@ pub(crate) enum Error {
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
#[non_exhaustive] #[non_exhaustive]
pub enum SuggestApiError { pub enum SuggestApiError {
#[error("Network error: {reason}")]
Network { reason: String },
// The server requested a backoff after too many requests
#[error("Backoff")]
Backoff { seconds: u64 },
// The application interrupted a request
#[error("Interrupted")]
Interrupted,
#[error("Other error: {reason}")] #[error("Other error: {reason}")]
Other { reason: String }, Other { reason: String },
} }
impl From<Error> for SuggestApiError { // Define how our internal errors are handled and converted to external errors
/// Converts an internal component error to a public application error. // See `support/error/README.md` for how this works, especially the warning about PII.
fn from(error: Error) -> Self { impl GetErrorHandling for Error {
Self::Other { type ExternalError = SuggestApiError;
reason: error.to_string(),
fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
match self {
// Do nothing for interrupted errors, this is just normal operation.
Self::Interrupted(_) => ErrorHandling::convert(SuggestApiError::Interrupted),
// Network errors are expected to happen in practice. Let's log, but not report them.
Self::RemoteSettings(RemoteSettingsError::RequestError(
viaduct::Error::NetworkError(e),
)) => ErrorHandling::convert(SuggestApiError::Network {
reason: e.to_string(),
})
.log_warning(),
// Backoff error shouldn't happen in practice, so let's report them for now.
// If these do happen in practice and we decide that there is a valid reason for them,
// then consider switching from reporting to Sentry to counting in Glean.
Self::RemoteSettings(RemoteSettingsError::BackoffError(seconds)) => {
ErrorHandling::convert(SuggestApiError::Backoff { seconds: *seconds })
.report_error("suggest-backoff")
}
_ => ErrorHandling::convert(SuggestApiError::Other {
reason: self.to_string(),
})
.report_error("suggest-unexpected"),
} }
} }
} }

View file

@ -4,6 +4,7 @@
*/ */
use remote_settings::RemoteSettingsConfig; use remote_settings::RemoteSettingsConfig;
mod config;
mod db; mod db;
mod error; mod error;
mod keyword; mod keyword;
@ -15,9 +16,10 @@ mod store;
mod suggestion; mod suggestion;
mod yelp; mod yelp;
pub use config::{SuggestGlobalConfig, SuggestProviderConfig};
pub use error::SuggestApiError; pub use error::SuggestApiError;
pub use provider::SuggestionProvider; pub use provider::SuggestionProvider;
pub use store::{SuggestIngestionConstraints, SuggestStore}; pub use store::{SuggestIngestionConstraints, SuggestStore, SuggestStoreBuilder};
pub use suggestion::{raw_suggestion_url_matches, Suggestion}; pub use suggestion::{raw_suggestion_url_matches, Suggestion};
pub(crate) type Result<T> = std::result::Result<T, error::Error>; pub(crate) type Result<T> = std::result::Result<T, error::Error>;

View file

@ -17,6 +17,8 @@ pub enum SuggestionProvider {
Amo = 3, Amo = 3,
Pocket = 4, Pocket = 4,
Yelp = 5, Yelp = 5,
Mdn = 6,
Weather = 7,
} }
impl FromSql for SuggestionProvider { impl FromSql for SuggestionProvider {
@ -38,6 +40,8 @@ impl SuggestionProvider {
3 => Some(SuggestionProvider::Amo), 3 => Some(SuggestionProvider::Amo),
4 => Some(SuggestionProvider::Pocket), 4 => Some(SuggestionProvider::Pocket),
5 => Some(SuggestionProvider::Yelp), 5 => Some(SuggestionProvider::Yelp),
6 => Some(SuggestionProvider::Mdn),
7 => Some(SuggestionProvider::Weather),
_ => None, _ => None,
} }
} }

View file

@ -79,7 +79,7 @@ impl SuggestRemoteSettingsClient for remote_settings::Client {
/// ///
/// Except for the type, Suggest records don't carry additional fields. All /// Except for the type, Suggest records don't carry additional fields. All
/// suggestions are stored in each record's attachment. /// suggestions are stored in each record's attachment.
#[derive(Clone, Debug, Deserialize, PartialEq)] #[derive(Clone, Debug, Deserialize)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub(crate) enum SuggestRecord { pub(crate) enum SuggestRecord {
#[serde(rename = "icon")] #[serde(rename = "icon")]
@ -92,6 +92,12 @@ pub(crate) enum SuggestRecord {
Pocket, Pocket,
#[serde(rename = "yelp-suggestions")] #[serde(rename = "yelp-suggestions")]
Yelp, Yelp,
#[serde(rename = "mdn-suggestions")]
Mdn,
#[serde(rename = "weather")]
Weather(DownloadedWeatherData),
#[serde(rename = "configuration")]
GlobalConfig(DownloadedGlobalConfig),
} }
/// Represents either a single value, or a list of values. This is used to /// Represents either a single value, or a list of values. This is used to
@ -148,15 +154,16 @@ where
} }
/// Fields that are common to all downloaded suggestions. /// Fields that are common to all downloaded suggestions.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, Deserialize)]
pub(crate) struct DownloadedSuggestionCommonDetails { pub(crate) struct DownloadedSuggestionCommonDetails {
pub keywords: Vec<String>, pub keywords: Vec<String>,
pub title: String, pub title: String,
pub url: String, pub url: String,
pub score: Option<f64>,
} }
/// An AMP suggestion to ingest from an AMP-Wikipedia attachment. /// An AMP suggestion to ingest from an AMP-Wikipedia attachment.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, Deserialize)]
pub(crate) struct DownloadedAmpSuggestion { pub(crate) struct DownloadedAmpSuggestion {
#[serde(flatten)] #[serde(flatten)]
pub common_details: DownloadedSuggestionCommonDetails, pub common_details: DownloadedSuggestionCommonDetails,
@ -171,7 +178,7 @@ pub(crate) struct DownloadedAmpSuggestion {
} }
/// A Wikipedia suggestion to ingest from an AMP-Wikipedia attachment. /// A Wikipedia suggestion to ingest from an AMP-Wikipedia attachment.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, Deserialize)]
pub(crate) struct DownloadedWikipediaSuggestion { pub(crate) struct DownloadedWikipediaSuggestion {
#[serde(flatten)] #[serde(flatten)]
pub common_details: DownloadedSuggestionCommonDetails, pub common_details: DownloadedSuggestionCommonDetails,
@ -181,7 +188,7 @@ pub(crate) struct DownloadedWikipediaSuggestion {
/// A suggestion to ingest from an AMP-Wikipedia attachment downloaded from /// A suggestion to ingest from an AMP-Wikipedia attachment downloaded from
/// Remote Settings. /// Remote Settings.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug)]
pub(crate) enum DownloadedAmpWikipediaSuggestion { pub(crate) enum DownloadedAmpWikipediaSuggestion {
Amp(DownloadedAmpSuggestion), Amp(DownloadedAmpSuggestion),
Wikipedia(DownloadedWikipediaSuggestion), Wikipedia(DownloadedWikipediaSuggestion),
@ -288,3 +295,47 @@ pub(crate) struct DownloadedYelpSuggestion {
#[serde(rename = "yelpModifiers")] #[serde(rename = "yelpModifiers")]
pub yelp_modifiers: Vec<String>, pub yelp_modifiers: Vec<String>,
} }
/// 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<String>,
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<String>,
// 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<f64>,
}
/// 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<Option<f64>, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer).map(|s| s.parse().ok())
}

View file

@ -6,7 +6,7 @@
use rusqlite::{Connection, Transaction}; use rusqlite::{Connection, Transaction};
use sql_support::open_database::{self, ConnectionInitializer}; use sql_support::open_database::{self, ConnectionInitializer};
pub const VERSION: u32 = 10; pub const VERSION: u32 = 12;
pub const SQL: &str = " pub const SQL: &str = "
CREATE TABLE meta( CREATE TABLE meta(
@ -37,7 +37,8 @@ pub const SQL: &str = "
record_id TEXT NOT NULL, record_id TEXT NOT NULL,
provider INTEGER NOT NULL, provider INTEGER NOT NULL,
title TEXT NOT NULL, title TEXT NOT NULL,
url TEXT NOT NULL url TEXT NOT NULL,
score REAL NOT NULL
); );
CREATE TABLE amp_custom_details( CREATE TABLE amp_custom_details(
@ -51,11 +52,6 @@ pub const SQL: &str = "
FOREIGN KEY(suggestion_id) REFERENCES suggestions(id) ON DELETE CASCADE FOREIGN KEY(suggestion_id) REFERENCES suggestions(id) ON DELETE CASCADE
); );
CREATE TABLE pocket_custom_details(
suggestion_id INTEGER PRIMARY KEY REFERENCES suggestions(id) ON DELETE CASCADE,
score REAL NOT NULL
);
CREATE TABLE wikipedia_custom_details( CREATE TABLE wikipedia_custom_details(
suggestion_id INTEGER PRIMARY KEY REFERENCES suggestions(id) ON DELETE CASCADE, suggestion_id INTEGER PRIMARY KEY REFERENCES suggestions(id) ON DELETE CASCADE,
icon_id TEXT NOT NULL icon_id TEXT NOT NULL
@ -68,7 +64,6 @@ pub const SQL: &str = "
icon_url TEXT NOT NULL, icon_url TEXT NOT NULL,
rating TEXT, rating TEXT,
number_of_ratings INTEGER NOT NULL, number_of_ratings INTEGER NOT NULL,
score REAL NOT NULL,
FOREIGN KEY(suggestion_id) REFERENCES suggestions(id) ON DELETE CASCADE FOREIGN KEY(suggestion_id) REFERENCES suggestions(id) ON DELETE CASCADE
); );
@ -96,6 +91,12 @@ pub const SQL: &str = "
need_location INTEGER NOT NULL, need_location INTEGER NOT NULL,
record_id TEXT NOT NULL record_id TEXT NOT NULL
) WITHOUT ROWID; ) WITHOUT ROWID;
CREATE TABLE mdn_custom_details(
suggestion_id INTEGER PRIMARY KEY,
description TEXT NOT NULL,
FOREIGN KEY(suggestion_id) REFERENCES suggestions(id) ON DELETE CASCADE
);
"; ";
/// Initializes an SQLite connection to the Suggest database, performing /// Initializes an SQLite connection to the Suggest database, performing
@ -126,7 +127,7 @@ impl ConnectionInitializer for SuggestConnectionInitializer {
fn upgrade_from(&self, _db: &Transaction<'_>, version: u32) -> open_database::Result<()> { fn upgrade_from(&self, _db: &Transaction<'_>, version: u32) -> open_database::Result<()> {
match version { match version {
1..=9 => { 1..=11 => {
// These schema versions were used during development, and never // These schema versions were used during development, and never
// shipped in any applications. Treat these databases as // shipped in any applications. Treat these databases as
// corrupt, so that they'll be replaced. // corrupt, so that they'll be replaced.

File diff suppressed because it is too large Load diff

View file

@ -14,6 +14,11 @@ boolean raw_suggestion_url_matches([ByRef] string raw_url, [ByRef] string url);
[Error] [Error]
interface SuggestApiError { interface SuggestApiError {
// An operation was interrupted by calling `SuggestStore.interrupt()`
Interrupted();
// The server requested a backoff after too many requests
Backoff(u64 seconds);
Network(string reason);
Other(string reason); Other(string reason);
}; };
@ -22,7 +27,9 @@ enum SuggestionProvider {
"Pocket", "Pocket",
"Wikipedia", "Wikipedia",
"Amo", "Amo",
"Yelp" "Yelp",
"Mdn",
"Weather",
}; };
[Enum] [Enum]
@ -38,7 +45,8 @@ interface Suggestion {
string iab_category, string iab_category,
string impression_url, string impression_url,
string click_url, string click_url,
string raw_click_url string raw_click_url,
f64 score
); );
Pocket( Pocket(
string title, string title,
@ -64,7 +72,17 @@ interface Suggestion {
); );
Yelp( Yelp(
string url, string url,
string title string title,
boolean is_top_pick
);
Mdn(
string title,
string url,
string description,
f64 score
);
Weather(
f64 score
); );
}; };
@ -78,6 +96,17 @@ dictionary SuggestIngestionConstraints {
u64? max_suggestions = null; u64? max_suggestions = null;
}; };
dictionary SuggestGlobalConfig {
i32 show_less_frequently_cap;
};
[Enum]
interface SuggestProviderConfig {
Weather(
i32 min_keyword_length
);
};
interface SuggestStore { interface SuggestStore {
[Throws=SuggestApiError] [Throws=SuggestApiError]
constructor([ByRef] string path, optional RemoteSettingsConfig? settings_config = null); constructor([ByRef] string path, optional RemoteSettingsConfig? settings_config = null);
@ -92,4 +121,26 @@ interface SuggestStore {
[Throws=SuggestApiError] [Throws=SuggestApiError]
void clear(); void clear();
[Throws=SuggestApiError]
SuggestGlobalConfig fetch_global_config();
[Throws=SuggestApiError]
SuggestProviderConfig? fetch_provider_config(SuggestionProvider provider);
};
interface SuggestStoreBuilder {
constructor();
[Self=ByArc]
SuggestStoreBuilder data_path(string path);
[Self=ByArc]
SuggestStoreBuilder cache_path(string path);
[Self=ByArc]
SuggestStoreBuilder remote_settings_config(RemoteSettingsConfig config);
[Throws=SuggestApiError]
SuggestStore build();
}; };

View file

@ -5,6 +5,8 @@
use chrono::Local; use chrono::Local;
use crate::db::DEFAULT_SUGGESTION_SCORE;
/// The template parameter for a timestamp in a "raw" sponsored suggestion URL. /// The template parameter for a timestamp in a "raw" sponsored suggestion URL.
const TIMESTAMP_TEMPLATE: &str = "%YYYYMMDDHH%"; const TIMESTAMP_TEMPLATE: &str = "%YYYYMMDDHH%";
@ -29,6 +31,7 @@ pub enum Suggestion {
impression_url: String, impression_url: String,
click_url: String, click_url: String,
raw_click_url: String, raw_click_url: String,
score: f64,
}, },
Pocket { Pocket {
title: String, title: String,
@ -55,9 +58,46 @@ pub enum Suggestion {
Yelp { Yelp {
url: String, url: String,
title: String, title: String,
is_top_pick: bool,
},
Mdn {
title: String,
url: String,
description: String,
score: f64,
},
Weather {
score: f64,
}, },
} }
impl PartialOrd for Suggestion {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Suggestion {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let a_score = match self {
Suggestion::Amp { score, .. }
| Suggestion::Pocket { score, .. }
| Suggestion::Amo { score, .. } => score,
_ => &DEFAULT_SUGGESTION_SCORE,
};
let b_score = match other {
Suggestion::Amp { score, .. }
| Suggestion::Pocket { score, .. }
| Suggestion::Amo { score, .. } => score,
_ => &DEFAULT_SUGGESTION_SCORE,
};
b_score
.partial_cmp(a_score)
.unwrap_or(std::cmp::Ordering::Equal)
}
}
impl Eq for Suggestion {}
/// Replaces all template parameters in a "raw" sponsored suggestion URL, /// Replaces all template parameters in a "raw" sponsored suggestion URL,
/// producing a "cooked" URL with real values. /// producing a "cooked" URL with real values.
pub(crate) fn cook_raw_suggestion_url(raw_url: &str) -> String { pub(crate) fn cook_raw_suggestion_url(raw_url: &str) -> String {

View file

@ -47,6 +47,9 @@ const MAX_QUERY_LENGTH: usize = 150;
/// "keyword=:modifier" (please see is_modifier()), define this how many words we should check. /// "keyword=:modifier" (please see is_modifier()), define this how many words we should check.
const MAX_MODIFIER_WORDS_NUMBER: usize = 2; const MAX_MODIFIER_WORDS_NUMBER: usize = 2;
/// The threshold that enables prefix-match.
const PREFIX_MATCH_THRESHOLD: usize = 6;
impl<'a> SuggestDao<'a> { impl<'a> SuggestDao<'a> {
/// Inserts the suggestions for Yelp attachment into the database. /// Inserts the suggestions for Yelp attachment into the database.
pub fn insert_yelp_suggestions( pub fn insert_yelp_suggestions(
@ -117,35 +120,36 @@ impl<'a> SuggestDao<'a> {
} }
/// Fetch Yelp suggestion from given user's query. /// Fetch Yelp suggestion from given user's query.
pub fn fetch_yelp_suggestion(&self, query: &SuggestionQuery) -> Result<Option<Suggestion>> { pub fn fetch_yelp_suggestions(&self, query: &SuggestionQuery) -> Result<Vec<Suggestion>> {
if !query.providers.contains(&SuggestionProvider::Yelp) { if !query.providers.contains(&SuggestionProvider::Yelp) {
return Ok(None); return Ok(vec![]);
} }
if query.keyword.len() > MAX_QUERY_LENGTH { if query.keyword.len() > MAX_QUERY_LENGTH {
return Ok(None); return Ok(vec![]);
} }
let query_string = &query.keyword.trim(); let query_string = &query.keyword.trim();
if !query_string.contains(' ') { if !query_string.contains(' ') {
if !self.is_subject(query_string)? { let Some((subject, subject_exact_match)) = self.find_subject(query_string)? else {
return Ok(None); return Ok(vec![]);
} };
let builder = SuggestionBuilder { let builder = SuggestionBuilder {
query, subject: &subject,
subject: query_string, subject_exact_match,
pre_modifier: None, pre_modifier: None,
post_modifier: None, post_modifier: None,
location_sign: None, location_sign: None,
location: None, location: None,
need_location: false, need_location: false,
pre_yelp_modifier: None,
post_yelp_modifier: None,
}; };
return Ok(Some(builder.into())); return Ok(vec![builder.into()]);
} }
// Find the yelp keyword modifier and remove them from the query. // Find the yelp keyword modifier and remove them from the query.
let (query_without_yelp_modifiers, _, _) = let (query_without_yelp_modifiers, pre_yelp_modifier, post_yelp_modifier) =
self.find_modifiers(query_string, Modifier::Yelp, Modifier::Yelp)?; self.find_modifiers(query_string, Modifier::Yelp, Modifier::Yelp)?;
// Find the location sign and the location. // Find the location sign and the location.
@ -154,32 +158,33 @@ impl<'a> SuggestDao<'a> {
if let (Some(_), false) = (&location, need_location) { if let (Some(_), false) = (&location, need_location) {
// The location sign does not need the specific location, but user is setting something. // The location sign does not need the specific location, but user is setting something.
return Ok(None); return Ok(vec![]);
} }
if query_without_location.is_empty() { if query_without_location.is_empty() {
// No remained query. // No remained query.
return Ok(None); return Ok(vec![]);
} }
// Find the modifiers. // Find the modifiers.
let (subject_candidate, pre_modifier, post_modifier) = let (subject_candidate, pre_modifier, post_modifier) =
self.find_modifiers(&query_without_location, Modifier::Pre, Modifier::Post)?; self.find_modifiers(&query_without_location, Modifier::Pre, Modifier::Post)?;
if !self.is_subject(&subject_candidate)? { let Some((subject, subject_exact_match)) = self.find_subject(&subject_candidate)? else {
return Ok(None); return Ok(vec![]);
} };
let builder = SuggestionBuilder { let builder = SuggestionBuilder {
query, subject: &subject,
subject: &subject_candidate, subject_exact_match,
pre_modifier, pre_modifier,
post_modifier, post_modifier,
location_sign, location_sign,
location, location,
need_location, need_location,
pre_yelp_modifier,
post_yelp_modifier,
}; };
Ok(Some(builder.into())) Ok(vec![builder.into()])
} }
/// Find the location information from the given query string. /// Find the location information from the given query string.
@ -296,6 +301,46 @@ impl<'a> SuggestDao<'a> {
)) ))
} }
/// Find the subject from the given string.
/// It returns the Option. If it is not none, it contains the tuple as follows:
/// (
/// String: Subject.
/// bool: Whether the subject matched exactly with the paramter.
/// )
fn find_subject(&self, candidate: &str) -> Result<Option<(String, bool)>> {
if candidate.is_empty() {
return Ok(None);
}
// If the length of subject candidate is less than PREFIX_MATCH_THRESHOLD,
// should exact match.
if candidate.len() < PREFIX_MATCH_THRESHOLD {
return Ok(if self.is_subject(candidate)? {
Some((candidate.to_string(), true))
} else {
None
});
}
// Otherwise, apply prefix-match.
Ok(
match self.conn.query_row_and_then_cachable(
"SELECT keyword FROM yelp_subjects WHERE keyword BETWEEN :candidate AND :candidate || x'FFFF' ORDER BY LENGTH(keyword) ASC LIMIT 1",
named_params! {
":candidate": candidate.to_lowercase(),
},
|row| row.get::<_, String>(0),
true,
) {
Ok(keyword) => {
debug_assert!(candidate.len() <= keyword.len());
Some((format!("{}{}", candidate, &keyword[candidate.len()..]), candidate.len() == keyword.len()))
},
Err(_) => None
}
)
}
fn is_modifier(&self, word: &str, modifier_type: Modifier) -> Result<bool> { fn is_modifier(&self, word: &str, modifier_type: Modifier) -> Result<bool> {
let result = self.conn.query_row_and_then_cachable( let result = self.conn.query_row_and_then_cachable(
" "
@ -315,10 +360,6 @@ impl<'a> SuggestDao<'a> {
} }
fn is_subject(&self, word: &str) -> Result<bool> { fn is_subject(&self, word: &str) -> Result<bool> {
if word.is_empty() {
return Ok(false);
}
let result = self.conn.query_row_and_then_cachable( let result = self.conn.query_row_and_then_cachable(
" "
SELECT EXISTS ( SELECT EXISTS (
@ -337,32 +378,34 @@ impl<'a> SuggestDao<'a> {
} }
struct SuggestionBuilder<'a> { struct SuggestionBuilder<'a> {
query: &'a SuggestionQuery,
subject: &'a str, subject: &'a str,
subject_exact_match: bool,
pre_modifier: Option<String>, pre_modifier: Option<String>,
post_modifier: Option<String>, post_modifier: Option<String>,
location_sign: Option<String>, location_sign: Option<String>,
location: Option<String>, location: Option<String>,
need_location: bool, need_location: bool,
pre_yelp_modifier: Option<String>,
post_yelp_modifier: Option<String>,
} }
impl<'a> From<SuggestionBuilder<'a>> for Suggestion { impl<'a> From<SuggestionBuilder<'a>> for Suggestion {
fn from(builder: SuggestionBuilder<'a>) -> Suggestion { fn from(builder: SuggestionBuilder<'a>) -> Suggestion {
// This location sign such the 'near by' needs to add as a description parameter. // This location sign such the 'near by' needs to add as a description parameter.
let location_modifier = if !builder.need_location { let location_modifier = if !builder.need_location {
builder.location_sign builder.location_sign.as_deref()
} else { } else {
None None
}; };
let description = [ let description = [
builder.pre_modifier, builder.pre_modifier.as_deref(),
Some(builder.subject.to_string()), Some(builder.subject),
builder.post_modifier, builder.post_modifier.as_deref(),
location_modifier, location_modifier,
] ]
.iter() .iter()
.flatten() .flatten()
.map(|s| s.as_str()) .copied()
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" "); .join(" ");
@ -375,10 +418,25 @@ impl<'a> From<SuggestionBuilder<'a>> for Suggestion {
} }
url.push_str(&parameters.finish()); url.push_str(&parameters.finish());
let title = [
builder.pre_yelp_modifier.as_deref(),
builder.pre_modifier.as_deref(),
Some(builder.subject),
builder.post_modifier.as_deref(),
builder.location_sign.as_deref(),
builder.location.as_deref(),
builder.post_yelp_modifier.as_deref(),
]
.iter()
.flatten()
.copied()
.collect::<Vec<_>>()
.join(" ");
Suggestion::Yelp { Suggestion::Yelp {
url, url,
// Use users query as title as it is. title,
title: builder.query.keyword.clone(), is_top_pick: builder.subject_exact_match,
} }
} }
} }

View file

@ -1 +1 @@
{"files":{"Cargo.toml":"f1c393a0b8b62e476bdde3e01e69087ec1a25597ec1c4a2d996d4f5514a39768","README.md":"6d4ff5b079ac5340d18fa127f583e7ad793c5a2328b8ecd12c3fc723939804f2","build.rs":"aa971160d67ce8626b26e15c04c34b730f594c45c817aae34cfc9f3ea14ae284","src/bso/content.rs":"92935258745bdf0c3915a555cb6884a7fa69faa1290ec2c1815f6e2f3c0f0562","src/bso/crypto.rs":"27602dcccb37d3a55620ee4e16b705da455d49af575de115c7c79c0178eb1d6d","src/bso/mod.rs":"09e723dc7e99295ecafdcadffaf604d66ea27cf2b7f1fd9ab3cac4f4698ff6a7","src/bso/test_utils.rs":"4ec5a2df5e1c0ec14dc770681e959bdcef6ef04f6fde435999197f46a8ae4831","src/client/coll_state.rs":"13e6ef55273baf5536acc369be522e34a803a32cabf19cce43e426aea9b6223e","src/client/coll_update.rs":"dac04a90c29dd969f8b4250414609c9b6d61daf2dfa4ae77d1c4a165ba970b05","src/client/collection_keys.rs":"c27b2277a3a52033b58ab01490fc2ea7007494195dd5e6dc2c6931a4ca96795a","src/client/mod.rs":"8f588d4a035cf79d96f2500f06d5651c1a7c566127c456ffa5429811ddce3fd6","src/client/request.rs":"8841524e37d8195867bdf6ba98c75f610cf47a4644adeebd6372cc6713f2260a","src/client/state.rs":"4e31193ef2471c1dfabf1c6a391bcb95e14ddb45855786a4194ff187d5c9347c","src/client/status.rs":"f445a8765dac9789444e23b5145148413407bb1d18a15ef56682243997f591bf","src/client/storage_client.rs":"8de72d4ba3ca4f68c8e1898466de83a2b543545a18679800cb4f7fbda2dc3183","src/client/sync.rs":"b29abb512ec9d163f7883b71f78c9202802dcb17cad1fc5dc08087fb0bb66704","src/client/sync_multiple.rs":"6e92571132f89744b553190c596be8aff9b2d031d8f79d82c94cdf78b1683f4a","src/client/token.rs":"b268759d31e0fe17e0e2a428694cd9a317fcfbdd52f023d5d8c7cc6f00f1a102","src/client/util.rs":"71cc70ee41f821f53078675e636e9fad9c6046fa1a989e37f5487e340a2277d6","src/client_types.rs":"3c3cac1540b92482f43660d9e43bdde8481c4cc1a98253a68c80e791231f5976","src/clients_engine/engine.rs":"9e11b47be81fc63214f31879af74075674aa50a8f8989afe20fefa7990fa99b9","src/clients_engine/mod.rs":"461729e6f89b66b2cbd89b041a03d4d6a8ba582284ed4f3015cb13e1a0c6da97","src/clients_engine/record.rs":"b0d84bf420743d7638a45e4836633a45e50257d5548fe7ecd04bff4d724439b8","src/clients_engine/ser.rs":"ef12daeb11faf618fe3cafe91f20a031fe5bb6751369b6ee5aee03f196efe88c","src/device_type.rs":"dc2d4296d25e31471c8e68488f1043ff239b902036cd6aea8a686cf79b4ed335","src/enc_payload.rs":"aa3eea7df49b24cd59831680a47c417b73a3e36e6b0f3f4baf14ca66bd68be6b","src/engine/bridged_engine.rs":"f70f1bfce6e0c04b0c72ec9cbfbb12c82d4009a23fb9768792107d41b2865a4f","src/engine/mod.rs":"90f1f9760f5f712a337aebb04e59c736e4b6fbd89d6a188d969210c7f3f321ae","src/engine/request.rs":"5923025fb9550178339f880a1bf8526d8e853e7a0b2bce6d9d687cc808ac0085","src/engine/sync_engine.rs":"531b35d72ce9e04c3e543c0468c1e450fba2c0dc3d33d68d9b1c0a5c1ad7dd34","src/error.rs":"a45cfe02e6301f473c34678b694943c1a04308b8c292c6e0448bf495194c3b5e","src/key_bundle.rs":"abd0781f3be8c8e7c691f18bb71f3433b633803c48da9794e15ac6301ed60d6c","src/lib.rs":"f59f8817978d943518dfa03ab31fc0f6b1fc72ee9943a97aef1537e2769649f5","src/record_types.rs":"02bb3d352fb808131d298f9b90d9c95b7e9e0138b97c5401f3b9fdacc5562f44","src/server_timestamp.rs":"6272299c92b05b9ec9dc2e18402ebe927b07ccf1dcab5082301a09e0ee56ce24","src/sync15.udl":"005b2b056b93c959a04670f6f489afecb8e17093d8e4be34765a3a4cc0faeb8c","src/telemetry.rs":"e3a7e13e85f5e336526ebf07db04c81b8f1ba89ae1db4159a3a570826cb8cfd2","uniffi.toml":"34488f947497a9b05007445dd816024ef02e6b1696f1056ee868f039722828ee"},"package":null} {"files":{"Cargo.toml":"739abc68b38e8468c5d1eb3f7a66f01e638765f9d8714080e07817f0939a2b66","README.md":"6d4ff5b079ac5340d18fa127f583e7ad793c5a2328b8ecd12c3fc723939804f2","build.rs":"aa971160d67ce8626b26e15c04c34b730f594c45c817aae34cfc9f3ea14ae284","src/bso/content.rs":"92935258745bdf0c3915a555cb6884a7fa69faa1290ec2c1815f6e2f3c0f0562","src/bso/crypto.rs":"27602dcccb37d3a55620ee4e16b705da455d49af575de115c7c79c0178eb1d6d","src/bso/mod.rs":"09e723dc7e99295ecafdcadffaf604d66ea27cf2b7f1fd9ab3cac4f4698ff6a7","src/bso/test_utils.rs":"4ec5a2df5e1c0ec14dc770681e959bdcef6ef04f6fde435999197f46a8ae4831","src/client/coll_state.rs":"13e6ef55273baf5536acc369be522e34a803a32cabf19cce43e426aea9b6223e","src/client/coll_update.rs":"dac04a90c29dd969f8b4250414609c9b6d61daf2dfa4ae77d1c4a165ba970b05","src/client/collection_keys.rs":"c27b2277a3a52033b58ab01490fc2ea7007494195dd5e6dc2c6931a4ca96795a","src/client/mod.rs":"8f588d4a035cf79d96f2500f06d5651c1a7c566127c456ffa5429811ddce3fd6","src/client/request.rs":"8841524e37d8195867bdf6ba98c75f610cf47a4644adeebd6372cc6713f2260a","src/client/state.rs":"4e31193ef2471c1dfabf1c6a391bcb95e14ddb45855786a4194ff187d5c9347c","src/client/status.rs":"f445a8765dac9789444e23b5145148413407bb1d18a15ef56682243997f591bf","src/client/storage_client.rs":"8de72d4ba3ca4f68c8e1898466de83a2b543545a18679800cb4f7fbda2dc3183","src/client/sync.rs":"b29abb512ec9d163f7883b71f78c9202802dcb17cad1fc5dc08087fb0bb66704","src/client/sync_multiple.rs":"6e92571132f89744b553190c596be8aff9b2d031d8f79d82c94cdf78b1683f4a","src/client/token.rs":"b268759d31e0fe17e0e2a428694cd9a317fcfbdd52f023d5d8c7cc6f00f1a102","src/client/util.rs":"71cc70ee41f821f53078675e636e9fad9c6046fa1a989e37f5487e340a2277d6","src/client_types.rs":"3c3cac1540b92482f43660d9e43bdde8481c4cc1a98253a68c80e791231f5976","src/clients_engine/engine.rs":"9e11b47be81fc63214f31879af74075674aa50a8f8989afe20fefa7990fa99b9","src/clients_engine/mod.rs":"461729e6f89b66b2cbd89b041a03d4d6a8ba582284ed4f3015cb13e1a0c6da97","src/clients_engine/record.rs":"b0d84bf420743d7638a45e4836633a45e50257d5548fe7ecd04bff4d724439b8","src/clients_engine/ser.rs":"ef12daeb11faf618fe3cafe91f20a031fe5bb6751369b6ee5aee03f196efe88c","src/device_type.rs":"dc2d4296d25e31471c8e68488f1043ff239b902036cd6aea8a686cf79b4ed335","src/enc_payload.rs":"aa3eea7df49b24cd59831680a47c417b73a3e36e6b0f3f4baf14ca66bd68be6b","src/engine/bridged_engine.rs":"f70f1bfce6e0c04b0c72ec9cbfbb12c82d4009a23fb9768792107d41b2865a4f","src/engine/mod.rs":"90f1f9760f5f712a337aebb04e59c736e4b6fbd89d6a188d969210c7f3f321ae","src/engine/request.rs":"5923025fb9550178339f880a1bf8526d8e853e7a0b2bce6d9d687cc808ac0085","src/engine/sync_engine.rs":"531b35d72ce9e04c3e543c0468c1e450fba2c0dc3d33d68d9b1c0a5c1ad7dd34","src/error.rs":"a45cfe02e6301f473c34678b694943c1a04308b8c292c6e0448bf495194c3b5e","src/key_bundle.rs":"abd0781f3be8c8e7c691f18bb71f3433b633803c48da9794e15ac6301ed60d6c","src/lib.rs":"f59f8817978d943518dfa03ab31fc0f6b1fc72ee9943a97aef1537e2769649f5","src/record_types.rs":"02bb3d352fb808131d298f9b90d9c95b7e9e0138b97c5401f3b9fdacc5562f44","src/server_timestamp.rs":"6272299c92b05b9ec9dc2e18402ebe927b07ccf1dcab5082301a09e0ee56ce24","src/sync15.udl":"005b2b056b93c959a04670f6f489afecb8e17093d8e4be34765a3a4cc0faeb8c","src/telemetry.rs":"e3a7e13e85f5e336526ebf07db04c81b8f1ba89ae1db4159a3a570826cb8cfd2","uniffi.toml":"34488f947497a9b05007445dd816024ef02e6b1696f1056ee868f039722828ee"},"package":null}

View file

@ -68,7 +68,7 @@ path = "../viaduct"
optional = true optional = true
[dev-dependencies.env_logger] [dev-dependencies.env_logger]
version = "0.7" version = "0.10"
default-features = false default-features = false
[build-dependencies.uniffi] [build-dependencies.uniffi]

View file

@ -1 +1 @@
{"files":{"Cargo.toml":"b1aebb4475665781d03e81608afa113aee8343c3e9707fd91591244f0c08c8c7","README.md":"c48b8f391ef822c4f3971b5f453a1e7b43bea232752d520460d2f04803aead1a","build.rs":"33e61b811b19ed2b58e319cc65d5988bed258d2c4fea2d706301184c59847a0f","src/error.rs":"2694657aeb12f99c4b2fe102ad2b08b79955d209201831b3e071129f0b7d7eda","src/lib.rs":"7208f78955e015ef8bab7916307e551cd3c1bd56d7fe14f8b53cd53bc4b38555","src/schema.rs":"2b7b51f3c2edc0ca603495c10b917603fd9ac791c4a366080e40d090b13b91f2","src/storage.rs":"18f449b6daf1641dc351be451311495b7c05e16c4e2d4eaf12c1fa02fa750b67","src/store.rs":"ab0b6214b30b0f0fa7c6a89098ff3db1a8f76264f6711c4481c0be460afe522b","src/sync/bridge.rs":"18d3a7913a030b598d4b6cbd5b7e2ab4cef4cc7ea964f5bc84d7fb2f28787529","src/sync/engine.rs":"2d14d899a38ac72b9141d505babd94ef7b6fbc5a95be70f324a40bf01935793d","src/sync/mod.rs":"09ba3c87f1174a243bf5aaa481effd18929d54359ceb9b23ccb2c32ee3482f34","src/sync/record.rs":"eef6751c209d039958afbe245ddb006cfdf6b8b6b47f925f69c552b832b87922","src/tabs.udl":"2cefc7f6a27b5619bc536d4a19608cf24153d745199fbeaf192e24b4381dedfb","uniffi.toml":"f9125e8d55b109e86076ee88bfd640372f06b142b7db557e41816c7227dd445c"},"package":null} {"files":{"Cargo.toml":"c06aa10f3dfa7be35c4fb54cb629704826da5bd9d08eaf09211343bb2b62bf74","README.md":"c48b8f391ef822c4f3971b5f453a1e7b43bea232752d520460d2f04803aead1a","build.rs":"33e61b811b19ed2b58e319cc65d5988bed258d2c4fea2d706301184c59847a0f","src/error.rs":"2694657aeb12f99c4b2fe102ad2b08b79955d209201831b3e071129f0b7d7eda","src/lib.rs":"7208f78955e015ef8bab7916307e551cd3c1bd56d7fe14f8b53cd53bc4b38555","src/schema.rs":"2b7b51f3c2edc0ca603495c10b917603fd9ac791c4a366080e40d090b13b91f2","src/storage.rs":"18f449b6daf1641dc351be451311495b7c05e16c4e2d4eaf12c1fa02fa750b67","src/store.rs":"ab0b6214b30b0f0fa7c6a89098ff3db1a8f76264f6711c4481c0be460afe522b","src/sync/bridge.rs":"18d3a7913a030b598d4b6cbd5b7e2ab4cef4cc7ea964f5bc84d7fb2f28787529","src/sync/engine.rs":"2d14d899a38ac72b9141d505babd94ef7b6fbc5a95be70f324a40bf01935793d","src/sync/mod.rs":"09ba3c87f1174a243bf5aaa481effd18929d54359ceb9b23ccb2c32ee3482f34","src/sync/record.rs":"eef6751c209d039958afbe245ddb006cfdf6b8b6b47f925f69c552b832b87922","src/tabs.udl":"2cefc7f6a27b5619bc536d4a19608cf24153d745199fbeaf192e24b4381dedfb","uniffi.toml":"f9125e8d55b109e86076ee88bfd640372f06b142b7db557e41816c7227dd445c"},"package":null}

View file

@ -60,12 +60,8 @@ features = ["sync-engine"]
tempfile = "3.1" tempfile = "3.1"
[dev-dependencies.env_logger] [dev-dependencies.env_logger]
version = "0.8.0" version = "0.10.0"
features = [ features = ["humantime"]
"termcolor",
"atty",
"humantime",
]
default-features = false default-features = false
[build-dependencies.uniffi] [build-dependencies.uniffi]

View file

@ -1 +1 @@
{"files":{"Cargo.toml":"261161e1eea9bfa070c3025ce4398b5e53d9db44ca1539313e05b849723d625b","README.md":"1fd617294339930ee1ad5172377648b268cce0216fc3971facbfe7c6839e9ab1","android/build.gradle":"7c8ef7424dd3cc110cd96a0dca6fabef1b8479668bfc403902813efbf5187a83","android/src/main/AndroidManifest.xml":"0abfabd45a3a6415861c33532d4efcd658e9f78c30342e3e3e3570296a3cc8c2","android/src/test/java/mozilla/appservices/webextstorage/WebExtStorageTest.kt":"aff94a9798c7e91f8efd8e4329ead05ea3afa1d0579a78d86ab93a5d40a715b1","build.rs":"92f7d380f3d8fab1e6d80276915af57192e276321d132a5f800ea4520e9cb469","sql/create_schema.sql":"a17311a407ec10e033886b7125da4c8b84bc6d761f6b28edc9594de430e1d964","sql/create_sync_temp_tables.sql":"860ede362c94feb47d85522553fa2852f9bdb9f9b025d6438dd5dee3d4acd527","sql/tests/create_schema_v1.sql":"77cf0c90eaac3e1aea626537147e1b8ec349b68d6076c92fa7ae402aac613050","src/api.rs":"6fe362e4f437def2ad2249de385cca8f0d1d5d67679240351e9f57523fefe5e7","src/db.rs":"b95024c1d8f36a76a6f3098acea5a82bc49de144a24cdc280ed38e9bcc8e772b","src/error.rs":"6437e9a0edefac2707af85eef13bdbfcd53a84d7aa7859599155d10451d42361","src/ffi.rs":"f66a81393bebe7a4b7e7960cb426df106ff1f02bfebcaa6e335b4b8b56c5c936","src/lib.rs":"ab25e7c6ea67fb905fe6dad866c0d2c462b1e93bcff283db947513aeabbb2d73","src/migration.rs":"8d92f82b2ba38e1039fd054c8c75078a6b896a0d3cdc1a52571456b25a32c9c3","src/schema.rs":"d8dd8f66cad71e3e369722734e0d5d16fd9423d5f6a5abba1854a27e1e814724","src/store.rs":"d208689c46fb97cd2c60a0c610ba1998a7132fb50fffa2eefa1d6b169b7c34f0","src/sync/bridge.rs":"996de05beb2904f84b3cbfc9ef85c4844078fdb4867d9068390d496156bee614","src/sync/incoming.rs":"dd77c64e2ade4f39cba258decab6d3db8ad0b5f513aa018efbd56b9869a021d9","src/sync/mod.rs":"bd1bc5c428dfda6aee7efe53b6e74b8015da5129a303638a21ca8d63516e4061","src/sync/outgoing.rs":"dacb77b956f2546fd60a89367927a199d9b662b17201d0781145f7405b61fdce","src/sync/sync_tests.rs":"f3846ca7e463315ba9788826613b987ddcff7b21672ff257a98769ee94f4191a","src/webext-storage.udl":"0341d431ba837cf64ea210ef6157010c6664a0b5a194e89acb0414938636b391","uniffi.toml":"beeec89c2f877eb89be0090dc304dbc7c74e787385e7459bad78c6165bb66791"},"package":null} {"files":{"Cargo.toml":"a11f7fbc29c375034289e7bdd11da4aadac9cb2d939a4f2e5dc61aaea35cf465","README.md":"1fd617294339930ee1ad5172377648b268cce0216fc3971facbfe7c6839e9ab1","build.rs":"92f7d380f3d8fab1e6d80276915af57192e276321d132a5f800ea4520e9cb469","sql/create_schema.sql":"a17311a407ec10e033886b7125da4c8b84bc6d761f6b28edc9594de430e1d964","sql/create_sync_temp_tables.sql":"860ede362c94feb47d85522553fa2852f9bdb9f9b025d6438dd5dee3d4acd527","sql/tests/create_schema_v1.sql":"77cf0c90eaac3e1aea626537147e1b8ec349b68d6076c92fa7ae402aac613050","src/api.rs":"6fe362e4f437def2ad2249de385cca8f0d1d5d67679240351e9f57523fefe5e7","src/db.rs":"b95024c1d8f36a76a6f3098acea5a82bc49de144a24cdc280ed38e9bcc8e772b","src/error.rs":"6437e9a0edefac2707af85eef13bdbfcd53a84d7aa7859599155d10451d42361","src/ffi.rs":"f66a81393bebe7a4b7e7960cb426df106ff1f02bfebcaa6e335b4b8b56c5c936","src/lib.rs":"ab25e7c6ea67fb905fe6dad866c0d2c462b1e93bcff283db947513aeabbb2d73","src/migration.rs":"8d92f82b2ba38e1039fd054c8c75078a6b896a0d3cdc1a52571456b25a32c9c3","src/schema.rs":"d8dd8f66cad71e3e369722734e0d5d16fd9423d5f6a5abba1854a27e1e814724","src/store.rs":"d208689c46fb97cd2c60a0c610ba1998a7132fb50fffa2eefa1d6b169b7c34f0","src/sync/bridge.rs":"996de05beb2904f84b3cbfc9ef85c4844078fdb4867d9068390d496156bee614","src/sync/incoming.rs":"dd77c64e2ade4f39cba258decab6d3db8ad0b5f513aa018efbd56b9869a021d9","src/sync/mod.rs":"bd1bc5c428dfda6aee7efe53b6e74b8015da5129a303638a21ca8d63516e4061","src/sync/outgoing.rs":"dacb77b956f2546fd60a89367927a199d9b662b17201d0781145f7405b61fdce","src/sync/sync_tests.rs":"f3846ca7e463315ba9788826613b987ddcff7b21672ff257a98769ee94f4191a","src/webext-storage.udl":"0341d431ba837cf64ea210ef6157010c6664a0b5a194e89acb0414938636b391","uniffi.toml":"beeec89c2f877eb89be0090dc304dbc7c74e787385e7459bad78c6165bb66791"},"package":null}

View file

@ -14,6 +14,7 @@ edition = "2021"
name = "webext-storage" name = "webext-storage"
version = "0.1.0" version = "0.1.0"
authors = ["sync-team@mozilla.com"] authors = ["sync-team@mozilla.com"]
exclude = ["/android"]
readme = "README.md" readme = "README.md"
license = "MPL-2.0" license = "MPL-2.0"
@ -67,7 +68,7 @@ libsqlite3-sys = "0.27.0"
tempfile = "3" tempfile = "3"
[dev-dependencies.env_logger] [dev-dependencies.env_logger]
version = "0.7" version = "0.10"
default-features = false default-features = false
[dev-dependencies.sql-support] [dev-dependencies.sql-support]

View file

@ -1,22 +0,0 @@
// TODO: Uncomment this code when webext-storage component is integrated in android
// apply from: "$rootDir/build-scripts/component-common.gradle"
// apply from: "$rootDir/publish.gradle"
// dependencies {
// // Part of the public API.
// api project(':sync15')
// implementation "org.mozilla.telemetry:glean:$glean_version"
// implementation "androidx.core:core-ktx:$androidx_core_version"
// testImplementation "androidx.test:core-ktx:$androidx_test_version"
// testImplementation "androidx.test.ext:junit-ktx:$androidx_test_junit_version"
// testImplementation "androidx.work:work-testing:$androidx_work_testing_version"
// testImplementation "org.mozilla.telemetry:glean-native-forUnitTests:$glean_version"
// }
// ext.configureUniFFIBindgen("../src/webext-storage.udl")
// ext.dependsOnTheMegazord()
// ext.configurePublish()

View file

@ -1,2 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mozilla.appservices.webextstorage" />

View file

@ -1,76 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package mozilla.appservices.webextstorage
import mozilla.appservices.Megazord
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class WebExtStorageTest {
@Rule
@JvmField
val dbFolder = TemporaryFolder()
fun createTestStore(): WebExtStorageStore {
Megazord.init()
val dbPath = dbFolder.newFile()
return WebExtStorageStore(path = dbPath.absolutePath)
}
@Test
fun testSet() {
val store = createTestStore()
val extId = "ab"
val jsonString = """{"a":"a"}"""
store.set(extId, jsonString)
val data = store.get(extId, "null")
Assert.assertEquals(jsonString, data)
store.close()
}
@Test
fun testRemove() {
val store = createTestStore()
val extId = "ab"
val jsonString = """{"a":"a","b":"b"}"""
store.set(extId, jsonString)
val change = store.remove("ab", """["b"]""").changes[0]
Assert.assertEquals(change.key, "b")
Assert.assertEquals(change.oldValue, """"b"""")
Assert.assertNull(change.newValue)
store.close()
}
@Test
fun testClear() {
val store = createTestStore()
val extId = "ab"
val jsonString = """{"a":"a","b":"b"}"""
store.set(extId, jsonString)
val result = store.clear(extId)
val firstChange = result.changes[0]
Assert.assertEquals(firstChange.key, "a")
Assert.assertEquals(firstChange.oldValue, """"a"""")
Assert.assertNull(firstChange.newValue)
val secondChange = result.changes[1]
Assert.assertEquals(secondChange.key, "b")
Assert.assertEquals(secondChange.oldValue, """"b"""")
Assert.assertNull(secondChange.newValue)
store.close()
}
}