qcs_api_client_openapi/apis/
configuration.rs1use qcs_api_client_common::backoff;
26use qcs_dependencies_client::http;
27use qcs_dependencies_client::reqwest;
28use qcs_dependencies_client::reqwest_middleware;
29use qcs_dependencies_client::reqwest_tracing;
30use qcs_dependencies_client::tracing_opentelemetry;
31#[cfg(feature = "tracing-opentelemetry")]
32use {
33 qcs_api_client_common::tracing_configuration::HeaderAttributesFilter,
34 reqwest_middleware::ClientBuilder,
35 reqwest_tracing::{TracingMiddleware, reqwest_otel_span},
36 tracing::Span,
37};
38
39#[derive(Debug, Clone)]
40pub struct Configuration {
41 #[cfg(not(feature = "tracing-opentelemetry"))]
42 pub client: reqwest::Client,
43 #[cfg(feature = "tracing-opentelemetry")]
44 pub client: reqwest_middleware::ClientWithMiddleware,
45 pub qcs_config: crate::common::ClientConfiguration,
46 pub backoff: backoff::ExponentialBackoff,
47}
48
49pub type BasicAuth = (String, Option<String>);
50
51#[derive(Debug, Clone)]
52pub struct ApiKey {
53 pub prefix: Option<String>,
54 pub key: String,
55}
56
57static USER_AGENT: &str = "QCS OpenAPI Client (Rust)/2020-07-31";
58
59impl Configuration {
60 pub async fn new() -> Result<Self, crate::common::configuration::LoadError> {
61 crate::common::ClientConfiguration::load_default().map(Self::with_qcs_config)
62 }
63
64 pub fn with_qcs_config(qcs_config: crate::common::ClientConfiguration) -> Configuration {
65 let client = reqwest::Client::builder()
66 .user_agent(USER_AGENT)
67 .build()
68 .expect("failed to add User-Agent to HTTP client");
69
70 Self::with_client_and_qcs_config(client, qcs_config)
71 }
72
73 pub fn with_client_and_qcs_config(
74 client: reqwest::Client,
75 qcs_config: crate::common::ClientConfiguration,
76 ) -> Self {
77 #[cfg(feature = "tracing-opentelemetry")]
78 let client = {
79 use reqwest_middleware::Extension;
80
81 let mut client_builder = ClientBuilder::new(client);
82 if let Some(tracing_configuration) = qcs_config.tracing_configuration() {
83 client_builder = client_builder.with_init(Extension(tracing_configuration.clone()));
84 let middleware = TracingMiddleware::<FilteredSpanBackend>::new();
85 client_builder = client_builder.with(middleware);
86 }
87 client_builder.build()
88 };
89
90 Self {
91 qcs_config,
92 client,
93 backoff: backoff::default_backoff(),
94 }
95 }
96}
97
98#[cfg(feature = "tracing-opentelemetry")]
99struct FilteredSpanBackend;
100
101#[cfg(feature = "tracing-opentelemetry")]
102#[derive(Debug, Clone, Copy)]
103enum MetadataAttributeType {
104 Request,
105 Response,
106}
107
108#[cfg(feature = "tracing-opentelemetry")]
109impl std::fmt::Display for MetadataAttributeType {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 match self {
112 Self::Request => write!(f, "request"),
113 Self::Response => write!(f, "response"),
114 }
115 }
116}
117
118#[cfg(feature = "tracing-opentelemetry")]
119impl FilteredSpanBackend {
120 fn is_enabled(req: &reqwest::Request, extensions: &mut http::Extensions) -> bool {
121 if let Some(filter) = extensions
122 .get::<qcs_api_client_common::tracing_configuration::TracingConfiguration>()
123 .and_then(|tracing_configuration| tracing_configuration.filter())
124 {
125 let input = urlpattern::UrlPatternMatchInput::Url(req.url().clone());
126 return filter.is_enabled(&input);
127 }
128 true
129 }
130
131 fn add_header_metadata(
132 span: &Span,
133 header_map: &http::HeaderMap,
134 extensions: &http::Extensions,
135 metadata_attribute_type: MetadataAttributeType,
136 ) {
137 if let Some(tracing_configuration) =
138 extensions.get::<qcs_api_client_common::tracing_configuration::TracingConfiguration>()
139 {
140 let request_headers_to_trace = tracing_configuration
141 .request_headers()
142 .get_header_attributes(header_map);
143 for (key, value) in request_headers_to_trace {
144 tracing_opentelemetry::OpenTelemetrySpanExt::set_attribute(
145 span,
146 format!("http.{metadata_attribute_type}.header.{key}"),
147 value,
148 );
149 }
150 }
151 }
152}
153
154#[cfg(feature = "tracing-opentelemetry")]
155impl reqwest_tracing::ReqwestOtelSpanBackend for FilteredSpanBackend {
156 fn on_request_start(
161 req: &reqwest::Request,
162 extensions: &mut http::Extensions,
163 ) -> tracing::Span {
164 if !Self::is_enabled(req, extensions) {
165 return tracing::Span::none();
166 }
167 let uri = req.url().to_string();
168 let http_target = req.url().path();
169 let user_agent = req
170 .headers()
171 .get("User-Agent")
172 .and_then(|ua| ua.to_str().ok())
173 .unwrap_or("");
174 let span = reqwest_otel_span!(
175 name = "HTTP request",
176 req,
177 http.url = uri,
178 http.target = http_target,
179 http.user_agent = user_agent
180 );
181 Self::add_header_metadata(
182 &span,
183 req.headers(),
184 extensions,
185 MetadataAttributeType::Request,
186 );
187
188 span
189 }
190
191 fn on_request_end(
192 span: &tracing::Span,
193 outcome: &reqwest_middleware::Result<reqwest::Response>,
194 extension: &mut http::Extensions,
195 ) {
196 if let Ok(response) = outcome {
197 Self::add_header_metadata(
198 span,
199 response.headers(),
200 extension,
201 MetadataAttributeType::Response,
202 );
203 }
204
205 reqwest_tracing::default_on_request_end(span, outcome)
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212 #[cfg(feature = "tracing-opentelemetry")]
213 use rstest::rstest;
214
215 #[cfg(feature = "tracing-opentelemetry")]
223 #[rstest]
224 fn test_tracing_enabled_no_filter() {
225 use crate::apis::configuration::FilteredSpanBackend;
226
227 let request = reqwest::Request::new(
228 reqwest::Method::GET,
229 "https://api.qcs.rigetti.com"
230 .parse()
231 .expect("test url should be valid"),
232 );
233 let mut extensions = http::Extensions::new();
234 assert!(FilteredSpanBackend::is_enabled(&request, &mut extensions));
235 }
236
237 #[cfg(feature = "tracing-opentelemetry")]
239 #[rstest]
240 #[ignore]
242 #[case("https://api.qcs.rigetti.com/v1/path", true)]
243 #[ignore]
244 #[case("https://api.qcs.rigetti.com/v1/other", false)]
245 #[ignore]
246 #[case("https://other.qcs.rigetti.com/v1/path", false)]
247 fn test_tracing_enabled_filter_not_passed(#[case] url: &str, #[case] expected: bool) {
248 use qcs_api_client_common::tracing_configuration::TracingFilterBuilder;
249
250 use crate::apis::configuration::FilteredSpanBackend;
251
252 let mut tracing_filter =
253 qcs_api_client_common::tracing_configuration::TracingFilter::builder()
254 .parse_strs_and_set_paths(&["https://api.qcs.rigetti.com/v1/path"])
255 .expect("test pattern should be valid")
256 .build();
257
258 let url = url.parse().expect("test url should be valid");
259 let request = reqwest::Request::new(reqwest::Method::GET, url);
260 let mut extensions = http::Extensions::new();
261 extensions.insert(tracing_filter.clone());
262 assert_eq!(
263 expected,
264 FilteredSpanBackend::is_enabled(&request, &mut extensions)
265 );
266
267 tracing_filter = TracingFilterBuilder::from(tracing_filter)
268 .set_is_negated(true)
269 .build();
270 extensions.insert(tracing_filter);
271 assert_ne!(
272 expected,
273 FilteredSpanBackend::is_enabled(&request, &mut extensions)
274 );
275 }
276}