tencent_sdk/client/
config.rs1use crate::Error;
2use std::{fmt, time::Duration};
3use url::Url;
4
5pub(crate) const DEFAULT_USER_AGENT: &str = concat!("tencent-sdk/", env!("CARGO_PKG_VERSION"));
6pub(crate) const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
7pub(crate) const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
8pub(crate) const DEFAULT_BODY_SNIPPET_MAX_BYTES: usize = 4096;
9pub(crate) const DEFAULT_RETRY_BASE_DELAY: Duration = Duration::from_millis(50);
10
11#[derive(Debug, Clone, Copy, Eq, PartialEq)]
12#[non_exhaustive]
13pub enum EndpointMode {
14 ServiceSubdomain,
16 FixedHost,
18}
19
20#[derive(Debug, Clone, Default)]
21pub struct RequestOptions {
22 pub(crate) timeout: Option<Duration>,
23 pub(crate) capture_body_snippet: Option<bool>,
24 pub(crate) idempotency_key: Option<IdempotencyKey>,
25}
26
27impl RequestOptions {
28 pub fn new() -> Self {
29 Self::default()
30 }
31
32 pub fn timeout(mut self, timeout: Duration) -> Self {
33 self.timeout = Some(timeout);
34 self
35 }
36
37 pub fn capture_body_snippet(mut self, enabled: bool) -> Self {
38 self.capture_body_snippet = Some(enabled);
39 self
40 }
41
42 pub fn idempotency_key(mut self, key: impl Into<IdempotencyKey>) -> Self {
43 self.idempotency_key = Some(key.into());
44 self
45 }
46}
47
48#[derive(Clone, Eq, PartialEq)]
49pub struct IdempotencyKey(String);
50
51impl IdempotencyKey {
52 pub fn new(value: impl Into<String>) -> Self {
53 Self(value.into())
54 }
55
56 pub fn as_str(&self) -> &str {
57 &self.0
58 }
59}
60
61impl fmt::Debug for IdempotencyKey {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 f.write_str("IdempotencyKey([redacted])")
64 }
65}
66
67impl From<String> for IdempotencyKey {
68 fn from(value: String) -> Self {
69 Self(value)
70 }
71}
72
73impl From<&str> for IdempotencyKey {
74 fn from(value: &str) -> Self {
75 Self(value.to_string())
76 }
77}
78
79#[derive(Debug, Clone)]
80pub(crate) struct RetryConfig {
81 pub(crate) max_retries: usize,
82 pub(crate) base_delay: Duration,
83}
84
85#[derive(Debug, Clone)]
86pub(crate) struct RequestDefaults {
87 pub(crate) timeout: Duration,
88 pub(crate) capture_body_snippet: bool,
89 pub(crate) body_snippet_max_bytes: usize,
90}
91
92#[derive(Debug, Clone)]
93pub(crate) struct EndpointConfig {
94 pub(crate) scheme: String,
95 pub(crate) host: String,
96 pub(crate) port: Option<u16>,
97 pub(crate) mode: EndpointMode,
98}
99
100impl EndpointConfig {
101 pub(crate) fn from_base_url(base_url: &str, mode: EndpointMode) -> Result<Self, Error> {
102 let url = Url::parse(base_url)
103 .map_err(|source| Error::invalid_base_url(base_url.to_string(), Box::new(source)))?;
104
105 let scheme = url.scheme();
106 if scheme != "http" && scheme != "https" {
107 let source = std::io::Error::new(
108 std::io::ErrorKind::InvalidInput,
109 "base url scheme must be http or https",
110 );
111 return Err(Error::invalid_base_url(
112 base_url.to_string(),
113 Box::new(source),
114 ));
115 }
116
117 if !url.username().is_empty() || url.password().is_some() {
118 let source = std::io::Error::new(
119 std::io::ErrorKind::InvalidInput,
120 "base url must not include credentials",
121 );
122 return Err(Error::invalid_base_url(
123 base_url.to_string(),
124 Box::new(source),
125 ));
126 }
127
128 if url.fragment().is_some() {
129 let source = std::io::Error::new(
130 std::io::ErrorKind::InvalidInput,
131 "base url must not include a fragment",
132 );
133 return Err(Error::invalid_base_url(
134 base_url.to_string(),
135 Box::new(source),
136 ));
137 }
138
139 let host = url.host_str().ok_or_else(|| {
140 let source = std::io::Error::new(
141 std::io::ErrorKind::InvalidInput,
142 "base url must include a host",
143 );
144 Error::invalid_base_url(base_url.to_string(), Box::new(source))
145 })?;
146
147 if !(url.path().is_empty() || url.path() == "/") || url.query().is_some() {
148 let source = std::io::Error::new(
149 std::io::ErrorKind::InvalidInput,
150 "base url must not include a path or query",
151 );
152 return Err(Error::invalid_base_url(
153 base_url.to_string(),
154 Box::new(source),
155 ));
156 }
157
158 Ok(Self {
159 scheme: scheme.to_string(),
160 host: host.to_string(),
161 port: url.port(),
162 mode,
163 })
164 }
165
166 pub(crate) fn authority_for_service(&self, service: &str) -> String {
167 let base_host = match self.mode {
168 EndpointMode::ServiceSubdomain => format!("{service}.{}", self.host),
169 EndpointMode::FixedHost => self.host.clone(),
170 };
171
172 match self.port {
173 Some(port) => format!("{base_host}:{port}"),
174 None => base_host,
175 }
176 }
177}