Skip to content

Use reqwest by default #563

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,28 @@ log = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing"] }
jsonwebtoken = { version = "9", default-features = false }
yaup = "0.2.0"
either = { version = "1.8.0", features = ["serde"] }
thiserror = "1.0.37"
meilisearch-index-setting-macro = { path = "meilisearch-index-setting-macro", version = "0.25.0" }
pin-project-lite = { version = "0.2.13", optional = true }
reqwest = { version = "0.12.3", optional = true, default-features = false, features = ["rustls-tls", "http2", "stream"] }
bytes = { version = "1.6", optional = true }
uuid = { version = "1.1.2", features = ["v4"] }
futures-io = "0.3.30"
futures = "0.3"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
futures = "0.3"
futures-io = "0.3.26"
isahc = { version = "1.0", features = ["http2", "text-decoding"], optional = true, default_features = false }
uuid = { version = "1.1.2", features = ["v4"] }
jsonwebtoken = { version = "9", default-features = false }

[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = "0.3.47"
web-sys = { version = "0.3", features = ["RequestInit", "Headers", "Window", "Response", "console"] }
wasm-bindgen = "0.2"
uuid = { version = "1.8.0", default-features = false, features = ["v4", "js"] }
web-sys = "0.3"
wasm-bindgen-futures = "0.4"

[features]
default = ["isahc", "isahc", "isahc-static-curl"]
isahc-static-curl = ["isahc", "isahc", "isahc/static-curl"]
isahc-static-ssl = ["isahc/static-ssl"]
default = ["reqwest"]
reqwest = ["dep:reqwest", "pin-project-lite", "bytes"]

[dev-dependencies]
futures-await-test = "0.3"
Expand All @@ -56,3 +56,4 @@ lazy_static = "1.4"
web-sys = "0.3"
console_error_panic_hook = "0.1"
big_s = "1.0.2"
insta = "1.38.0"
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,10 @@ struct Movie {
}


fn main() { block_on(async move {
#[tokio::main(flavor = "current_thread")]
async fn main() {
// Create a client (without sending any request so that can't fail)
let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY));
let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();

// An index is where the documents are stored.
let movies = client.index("movies");
Expand All @@ -124,7 +125,7 @@ fn main() { block_on(async move {
Movie { id: 5, title: String::from("Moana"), genres: vec!["Fantasy".to_string(), "Action".to_string()] },
Movie { id: 6, title: String::from("Philadelphia"), genres: vec!["Drama".to_string()] },
], Some("id")).await.unwrap();
})}
}
```

With the `uid`, you can check the status (`enqueued`, `canceled`, `processing`, `succeeded` or `failed`) of your documents addition using the [task](https://www.meilisearch.com/docs/reference/api/tasks#get-task).
Expand Down Expand Up @@ -238,11 +239,11 @@ Json output:
}
```

#### Using users customized HttpClient <!-- omit in TOC -->
#### Customize the `HttpClient` <!-- omit in TOC -->

If you want to change the `HttpClient` you can incorporate using the `Client::new_with_client` method.
To use it, you need to implement the `HttpClient Trait`(`isahc` is used by default).
There are [using-reqwest-example](./examples/cli-app-with-reqwest) of using `reqwest`.
By default, the SDK uses [`reqwest`](https://docs.rs/reqwest/latest/reqwest/) to make http calls.
The SDK lets you customize the http client by implementing the `HttpClient` trait yourself and
initializing the `Client` with the `new_with_client` method.

## 🌐 Running in the Browser with WASM <!-- omit in TOC -->

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "cli-app-with-reqwest"
name = "cli-app-with-awc"
version = "0.0.0"
edition = "2021"
publish = false
Expand All @@ -12,6 +12,9 @@ futures = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
lazy_static = "1.4.0"
reqwest = "0.11.16"
awc = "3.4"
async-trait = "0.1.51"
tokio = { version = "1.27.0", features = ["full"] }
tokio = { version = "1.27.0", features = ["full"] }
yaup = "0.2.0"
tokio-util = { version = "0.7.10", features = ["full"] }
actix-rt = "2.9.0"
257 changes: 257 additions & 0 deletions examples/cli-app-with-awc/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
use async_trait::async_trait;
use meilisearch_sdk::errors::Error;
use meilisearch_sdk::request::{parse_response, HttpClient, Method};
use meilisearch_sdk::{client::*, settings::Settings};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::io::stdin;

#[derive(Debug, Clone)]
pub struct AwcClient {
api_key: Option<String>,
}

impl AwcClient {
pub fn new(api_key: Option<&str>) -> Result<Self, Error> {
Ok(AwcClient {
api_key: api_key.map(|key| key.to_string()),
})
}
}

#[async_trait(?Send)]
impl HttpClient for AwcClient {
async fn stream_request<
Query: Serialize + Send + Sync,
Body: futures::AsyncRead + Send + Sync + 'static,
Output: DeserializeOwned + 'static,
>(
&self,
url: &str,
method: Method<Query, Body>,
content_type: &str,
expected_status_code: u16,
) -> Result<Output, Error> {
let mut builder = awc::ClientBuilder::new();
if let Some(ref api_key) = self.api_key {
builder = builder.bearer_auth(api_key);
}
builder = builder.add_default_header(("User-Agent", "Rust client with Awc"));
let client = builder.finish();

let query = method.query();
let query = yaup::to_string(query)?;

let url = if query.is_empty() {
url.to_string()
} else {
format!("{url}?{query}")
};

let url = add_query_parameters(&url, method.query())?;
let request = client.request(verb(&method), &url);

let mut response = if let Some(body) = method.into_body() {
let reader = tokio_util::compat::FuturesAsyncReadCompatExt::compat(body);
let stream = tokio_util::io::ReaderStream::new(reader);
request
.content_type(content_type)
.send_stream(stream)
.await
.map_err(|err| Error::Other(Box::new(err)))?
} else {
request
.send()
.await
.map_err(|err| Error::Other(Box::new(err)))?
};

let status = response.status().as_u16();
let mut body = String::from_utf8(
response
.body()
.await
.map_err(|err| Error::Other(Box::new(err)))?
.to_vec(),
)
.map_err(|err| Error::Other(Box::new(err)))?;

if body.is_empty() {
body = "null".to_string();
}

parse_response(status, expected_status_code, &body, url.to_string())
}
}

#[actix_rt::main]
async fn main() {
let http_client = AwcClient::new(Some("masterKey")).unwrap();
let client = Client::new_with_client("http://localhost:7700", Some("masterKey"), http_client);

// build the index
build_index(&client).await;

// enter in search queries or quit
loop {
println!("Enter a search query or type \"q\" or \"quit\" to quit:");
let mut input_string = String::new();
stdin()
.read_line(&mut input_string)
.expect("Failed to read line");
match input_string.trim() {
"quit" | "q" | "" => {
println!("exiting...");
break;
}
_ => {
search(&client, input_string.trim()).await;
}
}
}
// get rid of the index at the end, doing this only so users don't have the index without knowing
let _ = client.delete_index("clothes").await.unwrap();
}

async fn search(client: &Client<AwcClient>, query: &str) {
// make the search query, which excutes and serializes hits into the
// ClothesDisplay struct
let query_results = client
.index("clothes")
.search()
.with_query(query)
.execute::<ClothesDisplay>()
.await
.unwrap()
.hits;

// display the query results
if query_results.is_empty() {
println!("no results...");
} else {
for clothes in query_results {
let display = clothes.result;
println!("{}", format_args!("{}", display));
}
}
}

async fn build_index(client: &Client<AwcClient>) {
// reading and parsing the file
let content = include_str!("../assets/clothes.json");

// serialize the string to clothes objects
let clothes: Vec<Clothes> = serde_json::from_str(content).unwrap();

//create displayed attributes
let displayed_attributes = ["article", "cost", "size", "pattern"];

// Create ranking rules
let ranking_rules = ["words", "typo", "attribute", "exactness", "cost:asc"];

//create searchable attributes
let searchable_attributes = ["seaon", "article", "size", "pattern"];

// create the synonyms hashmap
let mut synonyms = std::collections::HashMap::new();
synonyms.insert("sweater", vec!["cardigan", "long-sleeve"]);
synonyms.insert("sweat pants", vec!["joggers", "gym pants"]);
synonyms.insert("t-shirt", vec!["tees", "tshirt"]);

//create the settings struct
let settings = Settings::new()
.with_ranking_rules(ranking_rules)
.with_searchable_attributes(searchable_attributes)
.with_displayed_attributes(displayed_attributes)
.with_synonyms(synonyms);

//add the settings to the index
let result = client
.index("clothes")
.set_settings(&settings)
.await
.unwrap()
.wait_for_completion(client, None, None)
.await
.unwrap();

if result.is_failure() {
panic!(
"Encountered an error while setting settings for index: {:?}",
result.unwrap_failure()
);
}

// add the documents
let result = client
.index("clothes")
.add_or_update(&clothes, Some("id"))
.await
.unwrap()
.wait_for_completion(client, None, None)
.await
.unwrap();

if result.is_failure() {
panic!(
"Encountered an error while sending the documents: {:?}",
result.unwrap_failure()
);
}
}

/// Base search object.
#[derive(Serialize, Deserialize, Debug)]
pub struct Clothes {
id: usize,
seaon: String,
article: String,
cost: f32,
size: String,
pattern: String,
}

/// Search results get serialized to this struct
#[derive(Serialize, Deserialize, Debug)]
pub struct ClothesDisplay {
article: String,
cost: f32,
size: String,
pattern: String,
}

impl fmt::Display for ClothesDisplay {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Write strictly the first element into the supplied output
// stream: `f`. Returns `fmt::Result` which indicates whether the
// operation succeeded or failed. Note that `write!` uses syntax which
// is very similar to `println!`.
write!(
f,
"result\n article: {},\n price: {},\n size: {},\n pattern: {}\n",
self.article, self.cost, self.size, self.pattern
)
}
}

fn add_query_parameters<Query: Serialize>(url: &str, query: &Query) -> Result<String, Error> {
let query = yaup::to_string(query)?;

if query.is_empty() {
Ok(url.to_string())
} else {
Ok(format!("{url}?{query}"))
}
}

fn verb<Q, B>(method: &Method<Q, B>) -> awc::http::Method {
match method {
Method::Get { .. } => awc::http::Method::GET,
Method::Delete { .. } => awc::http::Method::DELETE,
Method::Post { .. } => awc::http::Method::POST,
Method::Put { .. } => awc::http::Method::PUT,
Method::Patch { .. } => awc::http::Method::PATCH,
}
}
Loading