use std::time::Duration;
use qcs_api_client_common::configuration::{ClientConfiguration, TokenError};
#[cfg(feature = "tracing")]
use qcs_api_client_grpc::tonic::wrap_channel_with_tracing;
#[cfg(feature = "tracing")]
use qcs_api_client_grpc::tonic::CustomTraceService;
#[cfg(feature = "grpc-web")]
use qcs_api_client_grpc::tonic::{wrap_channel_with_grpc_web, GrpcWebWrapperLayerService};
use qcs_api_client_grpc::{
services::translation::translation_client::TranslationClient,
tonic::{
get_channel, parse_uri, wrap_channel_with, wrap_channel_with_retry, RefreshService,
RetryService,
},
};
use qcs_api_client_openapi::apis::configuration::Configuration as OpenApiConfiguration;
use tokio_util::sync::CancellationToken;
#[cfg(not(any(feature = "grpc-web", feature = "tracing")))]
use tonic::transport::Channel;
use tonic::Status;
pub use qcs_api_client_common::configuration::LoadError;
pub use qcs_api_client_grpc::tonic::Error as GrpcError;
pub use qcs_api_client_openapi::apis::Error as OpenApiError;
#[cfg(feature = "stubs")]
use pyo3_stub_gen::derive::gen_stub_pyclass;
const MAX_TRANSLATION_OUTBOUND_REQUEST_SIZE: usize = 50 * 1024 * 1024;
#[cfg(not(any(feature = "grpc-web", feature = "tracing")))]
pub type GrpcConnection = RetryService<RefreshService<Channel, ClientConfiguration>>;
#[cfg(all(feature = "grpc-web", not(feature = "tracing")))]
pub type GrpcConnection =
GrpcWebWrapperLayerService<RetryService<RefreshService<Channel, ClientConfiguration>>>;
#[cfg(all(not(feature = "grpc-web"), feature = "tracing"))]
pub type GrpcConnection = RetryService<RefreshService<CustomTraceService, ClientConfiguration>>;
pub(crate) static DEFAULT_HTTP_API_TIMEOUT: Duration = Duration::from_secs(10);
#[derive(Debug, Clone)]
#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "qcs_sdk.client", name = "QCSClient", eq)
)]
pub struct Qcs {
config: ClientConfiguration,
}
impl Qcs {
#[must_use]
pub fn load() -> Self {
if let Ok(config) = ClientConfiguration::load_default() {
Self::with_config(config)
} else {
#[cfg(feature = "tracing")]
tracing::info!(
"No QCS client configuration found. QPU data and QCS will be inaccessible and only generic QVMs will be available for execution"
);
Self::default()
}
}
#[must_use]
pub fn with_config(config: ClientConfiguration) -> Self {
Self { config }
}
pub fn with_profile(profile: String) -> Result<Qcs, LoadError> {
ClientConfiguration::load_profile(profile).map(Self::with_config)
}
pub async fn with_login(
cancel_token: CancellationToken,
profile: Option<String>,
) -> Result<Qcs, LoadError> {
ClientConfiguration::load_with_login(cancel_token, profile)
.await
.map(Self::with_config)
}
#[must_use]
pub fn get_config(&self) -> &ClientConfiguration {
&self.config
}
pub(crate) fn get_openapi_client(&self) -> OpenApiConfiguration {
OpenApiConfiguration::with_qcs_config(self.get_config().clone())
}
#[expect(clippy::result_large_err)]
pub(crate) fn get_translation_client(
&self,
) -> Result<TranslationClient<GrpcConnection>, GrpcError<TokenError>> {
self.get_translation_client_with_endpoint(self.get_config().grpc_api_url())
}
#[expect(clippy::result_large_err)]
pub(crate) fn get_translation_client_with_endpoint(
&self,
translation_grpc_endpoint: &str,
) -> Result<TranslationClient<GrpcConnection>, GrpcError<TokenError>> {
let uri = parse_uri(translation_grpc_endpoint)?;
let channel = get_channel(uri)?;
#[cfg(feature = "tracing")]
let channel = wrap_channel_with_tracing(
channel,
translation_grpc_endpoint.to_string(),
self.get_config()
.tracing_configuration()
.cloned()
.unwrap_or_default(),
);
let channel = wrap_channel_with(channel, self.get_config().clone());
let channel = wrap_channel_with_retry(channel);
#[cfg(feature = "grpc-web")]
let channel = wrap_channel_with_grpc_web(service);
Ok(TranslationClient::new(channel)
.max_encoding_message_size(MAX_TRANSLATION_OUTBOUND_REQUEST_SIZE)
.max_decoding_message_size(u32::MAX as usize))
}
}
impl Default for Qcs {
fn default() -> Self {
Self::with_config(
ClientConfiguration::builder()
.build()
.expect("builder should be valid with all defaults"),
)
}
}
#[derive(Debug, thiserror::Error)]
pub enum GrpcClientError {
#[error("Call failed during gRPC request: {0}")]
RequestFailed(#[from] Status),
#[error("Response body had missing data: {0}")]
ResponseEmpty(String),
#[error("gRPC error: {0}")]
GrpcError(#[from] GrpcError<TokenError>),
}
#[derive(Debug, thiserror::Error)]
pub enum OpenApiClientError<T> {
#[error("Call failed during http request: {0}")]
RequestFailed(#[from] OpenApiError<T>),
#[error("Response value was empty: {0}")]
ResponseEmpty(String),
}