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