percas_client/
client.rs

1// Copyright 2025 ScopeDB <contact@scopedb.io>
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use reqwest::IntoUrl;
16use reqwest::StatusCode;
17use reqwest::Url;
18
19use crate::Error;
20
21#[derive(Debug, Clone)]
22pub struct ClientFactory {
23    client: reqwest::Client,
24}
25
26impl ClientFactory {
27    pub fn new() -> Result<Self, Error> {
28        let client = reqwest::ClientBuilder::new()
29            .no_proxy()
30            .build()
31            .map_err(Error::Http)?;
32        Ok(Self { client })
33    }
34
35    pub fn make_client(&self, endpoint: String) -> Result<Client, Error> {
36        Client::new(endpoint, self.client.clone())
37    }
38}
39
40pub struct Client {
41    client: reqwest::Client,
42    base_url: Url,
43}
44
45impl Client {
46    pub async fn get(&self, key: &str) -> Result<Option<Vec<u8>>, Error> {
47        do_get(self, key).await
48    }
49
50    pub async fn put(&self, key: &str, value: &[u8]) -> Result<(), Error> {
51        do_put(self, key, value).await
52    }
53
54    pub async fn delete(&self, key: &str) -> Result<(), Error> {
55        do_delete(self, key).await
56    }
57
58    fn new(base_url: impl IntoUrl, client: reqwest::Client) -> Result<Self, Error> {
59        let base_url = base_url.into_url().map_err(Error::Http)?;
60        Ok(Client { client, base_url })
61    }
62}
63
64async fn do_get(client: &Client, key: &str) -> Result<Option<Vec<u8>>, Error> {
65    let url = client
66        .base_url
67        .join(key)
68        .map_err(|e| Error::Other(e.to_string()))?;
69
70    let resp = client.client.get(url).send().await.map_err(Error::Http)?;
71
72    match resp.status() {
73        StatusCode::NOT_FOUND => Ok(None),
74        StatusCode::OK => {
75            let body = resp.bytes().await.map_err(Error::Http)?;
76            Ok(Some(body.to_vec()))
77        }
78        StatusCode::TOO_MANY_REQUESTS => Err(Error::TooManyRequests),
79        _ => Err(Error::Other(resp.status().to_string())),
80    }
81}
82
83async fn do_put(client: &Client, key: &str, value: &[u8]) -> Result<(), Error> {
84    let url = client
85        .base_url
86        .join(key)
87        .map_err(|e| Error::Other(e.to_string()))?;
88
89    let resp = client
90        .client
91        .put(url)
92        .body(value.to_vec())
93        .send()
94        .await
95        .map_err(Error::Http)?;
96
97    match resp.status() {
98        StatusCode::OK | StatusCode::CREATED => Ok(()),
99        StatusCode::TOO_MANY_REQUESTS => Err(Error::TooManyRequests),
100        status => Err(Error::Other(status.to_string())),
101    }
102}
103
104async fn do_delete(client: &Client, key: &str) -> Result<(), Error> {
105    let url = client
106        .base_url
107        .join(key)
108        .map_err(|e| Error::Other(e.to_string()))?;
109
110    let resp = client
111        .client
112        .delete(url)
113        .send()
114        .await
115        .map_err(Error::Http)?;
116
117    match resp.status() {
118        StatusCode::OK | StatusCode::NO_CONTENT => Ok(()),
119        StatusCode::TOO_MANY_REQUESTS => Err(Error::TooManyRequests),
120        status => Err(Error::Other(status.to_string())),
121    }
122}