ve-tos-rust-sdk 2.6.0

volcengine offical tos rust sdk
Documentation
/*
 * Copyright (2024) Volcengine
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
use url::Url;

use super::constant::*;
use super::error::{GenericError, TosError};
use super::internal::url_encode_with_safe;

#[derive(Debug, Clone)]
pub(crate) struct ConfigHolder {
    pub(crate) user_agent: String,
    pub(crate) region: String,
    pub(crate) max_retry_count: isize,
    pub(crate) request_timeout: isize,
    pub(crate) connection_timeout: isize,
    pub(crate) max_connections: isize,
    pub(crate) idle_connection_time: isize,
    pub(crate) enable_crc: bool,
    pub(crate) enable_verify_ssl: bool,
    pub(crate) auto_recognize_content_type: bool,
    pub(crate) is_custom_domain: bool,
    // todo reqwest cannot support to customize dns resolving process right now
    pub(crate) dns_cache_time: isize,
    pub(crate) proxy_host: String,
    pub(crate) proxy_port: isize,
    pub(crate) proxy_username: String,
    pub(crate) proxy_password: String,
    pub(crate) schema: String,
    pub(crate) domain: String,
}

impl Default for ConfigHolder {
    fn default() -> Self {
        ConfigHolder {
            user_agent: String::from("ve-tos-rust-sdk/".to_string() + env!("CARGO_PKG_VERSION") +
                " (" + std::env::consts::OS + "/" + std::env::consts::ARCH + ")"),
            region: DEFAULT_REGION.to_string(),
            max_retry_count: DEFAULT_MAX_RETRY_COUNT,
            request_timeout: DEFAULT_REQUEST_TIMEOUT,
            connection_timeout: DEFAULT_CONNECTION_TIMEOUT,
            max_connections: DEFAULT_MAX_CONNECTIONS,
            idle_connection_time: DEFAULT_IDLE_CONNECTION_TIME,
            enable_crc: true,
            enable_verify_ssl: true,
            auto_recognize_content_type: true,
            is_custom_domain: false,
            dns_cache_time: DEFAULT_DNS_CACHE_TIME,
            proxy_host: "".to_string(),
            proxy_port: -1,
            proxy_username: "".to_string(),
            proxy_password: "".to_string(),
            schema: "".to_string(),
            domain: "".to_string(),
        }
    }
}


impl ConfigHolder {
    pub(crate) fn check(&mut self, endpoint: impl Into<String>, region: impl Into<String>) -> Result<(), TosError> {
        let region = region.into().trim().to_owned();
        if region == "" {
            return TosError::client_error_result("no region specified");
        }
        let mut endpoint = endpoint.into().trim().to_owned().to_lowercase();
        if endpoint == "" {
            if let Some(value) = REGION_ENDPOINTS.get(region.as_str()) {
                endpoint = (*value).to_string();
            }
        }

        if endpoint == "" {
            return TosError::client_error_result("no endpoint specified");
        }

        let (schema, domain) = self.split_endpoint(endpoint.as_str())?;
        if TOS_S3_ENDPOINTS.contains_key(domain.as_str()) {
            return TosError::client_error_result("invalid endpoint, please use TOS endpoint rather than S3 endpoint");
        }

        self.region = region.to_owned();
        self.schema = schema;
        self.domain = domain;
        Ok(())
    }

    pub(crate) fn split_endpoint(&self, endpoint: &str) -> Result<(String, String), TosError> {
        let mut endpoint = endpoint;
        while endpoint.len() > 0 && endpoint.ends_with("/") {
            endpoint = &endpoint[0..endpoint.len() - 1];
        }

        endpoint = endpoint.trim();
        if endpoint.len() == 0 {
            return Err(TosError::client_error("invalid endpoint"));
        }
        let mut schema = String::with_capacity(SCHEMA_HTTP.len());
        let domain;
        if endpoint.starts_with(SCHEMA_HTTP) {
            schema.push_str(SCHEMA_HTTP);
            (domain, _) = self.parse_domain(endpoint)?;
        } else if endpoint.starts_with(SCHEMA_HTTPS) {
            schema.push_str(SCHEMA_HTTPS);
            (domain, _) = self.parse_domain(endpoint)?;
        } else {
            schema.push_str(SCHEMA_HTTPS);
            (domain, _) = self.parse_domain((SCHEMA_HTTPS.to_owned() + endpoint).as_str())?;
        }

        Ok((schema, domain))
    }

    pub(crate) fn parse_domain(&self, input: &str) -> Result<(String, String), TosError> {
        let mut domain = String::with_capacity(input.len());
        match Url::parse(input) {
            Ok(u) => {
                if let Some(host) = u.host() {
                    if let Some(port) = u.port() {
                        domain.push_str(&format!("{}:{}", host, port));
                    } else {
                        domain.push_str(host.to_string().as_str());
                    }
                    Ok((domain, u.scheme().to_string()))
                } else {
                    Err(TosError::client_error("no host error"))
                }
            }
            Err(e) => {
                Err(TosError::client_error_with_cause("parse domain error", GenericError::UrlParseError(e)))
            }
        }
    }

    pub(crate) fn get_host(&self, bucket: &str) -> String {
        self.get_host_with_domain(bucket, "", self.is_custom_domain)
    }

    pub(crate) fn get_host_with_domain(&self, bucket: &str, domain: &str, is_custom_domain: bool) -> String {
        let mut domain = domain;
        if domain == "" {
            domain = &self.domain;
        }
        let mut host = String::with_capacity(bucket.len() + domain.len() + 1);
        if bucket != "" && !is_custom_domain {
            host += bucket;
            host += ".";
        }
        host += domain;
        host
    }

    pub(crate) fn get_endpoint(&self, bucket: &str, key: &str) -> String {
        self.get_endpoint_with_domain(bucket, key, "", "", true, self.is_custom_domain)
    }

    pub(crate) fn get_endpoint_with_domain(&self, bucket: &str, key: &str, schema: &str, domain: &str, must_add_key: bool, is_custom_domain: bool) -> String {
        let mut schema = schema;
        if schema == "" {
            schema = &self.schema;
        }
        let mut domain = domain;
        if domain == "" {
            domain = &self.domain;
        }
        let mut endpoint = String::with_capacity(schema.len() + domain.len() + bucket.len() + key.len() * 2 + 3);
        endpoint += schema;

        if bucket != "" && !is_custom_domain {
            endpoint += bucket;
            endpoint += ".";
        }
        endpoint += domain;

        if key != "" && must_add_key {
            endpoint += "/";
            endpoint += url_encode_with_safe(key, "/").as_str();
        }

        endpoint
    }
}