/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ mod error; mod http; mod schema; #[cfg(test)] mod tests; use error_support::handle_error; use url::Url; pub use error::{ApiResult, Error, MerinoSuggestApiError, Result}; pub use schema::{SuggestConfig, SuggestOptions}; const DEFAULT_BASE_HOST: &str = "https://merino.services.mozilla.com"; /// A client for the merino suggest endpoint. /// /// Use [`SuggestClient::new`] to create an instance, then call /// [`SuggestClient::get_suggestions`] to fetch suggestions for a query. #[derive(uniffi::Object)] pub struct SuggestClient { inner: SuggestClientInner, endpoint_url: Url, } struct SuggestClientInner { http_client: T, } #[derive(Default)] pub struct SuggestClientBuilder { base_host: Option, } impl SuggestClientBuilder { pub fn new() -> Self { Self::default() } pub fn base_host(mut self, base_host: String) -> Self { self.base_host = Some(base_host); self } pub fn build(self) -> Result { let base_host = self .base_host .unwrap_or_else(|| DEFAULT_BASE_HOST.to_string()); let url = format!("{}/api/v1/suggest", base_host); let endpoint_url = Url::parse(&url)?; Ok(SuggestClient { inner: SuggestClientInner::new()?, endpoint_url, }) } } #[uniffi::export] impl SuggestClient { /// Creates a new `SuggestClient` from the given configuration. #[uniffi::constructor] #[handle_error(Error)] pub fn new(config: SuggestConfig) -> ApiResult { let mut builder = SuggestClientBuilder::new(); if let Some(base_host) = config.base_host { builder = builder.base_host(base_host); } builder.build() } /// Fetches suggestions from the merino suggest endpoint for the given query. /// /// Returns the raw JSON response body as a string, or `None` if the server /// returned HTTP 204 (no suggestions available for weather). #[handle_error(Error)] pub fn get_suggestions( &self, query: String, options: SuggestOptions, ) -> ApiResult> { let response = self .inner .get_suggestions(query.as_str(), options, &self.endpoint_url)?; Ok(response.map(|r| r.text().to_string())) } } impl SuggestClientInner { pub fn new() -> Result { Ok(Self { http_client: http::HttpClient, }) } } impl SuggestClientInner { pub fn get_suggestions( &self, query: &str, options: SuggestOptions, endpoint_url: &Url, ) -> Result> { let providers = options .providers .filter(|v| !v.is_empty()) .map(|v| v.join(",")); let client_variants = options .client_variants .filter(|v| !v.is_empty()) .map(|v| v.join(",")); self.http_client.make_suggest_request( query, http::SuggestQueryParams { providers: providers.as_deref(), source: options.source.as_deref(), country: options.country.as_deref(), region: options.region.as_deref(), city: options.city.as_deref(), client_variants: client_variants.as_deref(), request_type: options.request_type.as_deref(), accept_language: options.accept_language.as_deref(), }, endpoint_url.clone(), ) } } #[cfg(test)] impl SuggestClientInner { pub fn new_with_client(client: T) -> Self { Self { http_client: client, } } }