burn_central_core/client.rs
1//! This module provides the [BurnCentral] struct, which is used to interact with the Burn Central service.
2
3use crate::artifacts::ExperimentArtifactClient;
4use crate::experiment::{ExperimentRun, ExperimentTrackerError};
5use crate::models::ModelRegistry;
6use crate::schemas::ExperimentPath;
7use burn_central_client::{BurnCentralCredentials, Client, ClientError};
8use url::Url;
9
10/// Errors that can occur during the initialization of the [BurnCentral] client.
11#[derive(Debug, thiserror::Error)]
12pub enum InitError {
13 /// Represents an error related to the client.
14 #[error("Client error: {0}")]
15 Client(#[from] ClientError),
16 /// Represents an error when the endpoint URL is invalid.
17 #[error("Failed to parse endpoint URL: {0}")]
18 InvalidEndpointUrl(String),
19}
20
21#[derive(Debug, thiserror::Error)]
22/// Errors that can occur during when using the [BurnCentral] client.
23///
24/// Those errors should be handled by the user of this library. If he want to implement fallback behavior or retry logic.
25pub enum BurnCentralError {
26 /// Input validation errors
27 #[error("Invalid experiment path: {0}")]
28 InvalidExperimentPath(String),
29 /// This error occurs when the provided project path is invalid. It is a input validation
30 /// errors. No API call has been made yet.
31 #[error("Invalid project path: {0}")]
32 InvalidProjectPath(String),
33 /// This error occurs when the provided experiment number is invalid. It is a input validation
34 /// errors. No API call has been made yet.
35 #[error("Invalid experiment number: {0}")]
36 InvalidExperimentNumber(String),
37 /// This error occurs when the provided model path is invalid. It is a input validation
38 /// errors. No API call has been made yet.
39 #[error("Invalid model path: {0}")]
40 InvalidModelPath(String),
41
42 /// Represents an error related to client operations.
43 ///
44 /// This error variant is used to encapsulate client-specific errors along with additional context
45 /// and the underlying source error for more detailed debugging.
46 ///
47 /// # Fields
48 /// - `context` (String): A description or additional information about the client error context.
49 /// - `source` (ClientError): The underlying source of the client error, providing more details about the cause.
50 #[error("Client error: {context}\nSource: {source}")]
51 Client {
52 context: String,
53 source: ClientError,
54 },
55 /// Represents an error related to the experiment tracker. Those errors are coming from the
56 /// websocket connection that open when starting an experiment run.
57 #[error("Experiment error: {0}")]
58 ExperimentTracker(#[from] ExperimentTrackerError),
59
60 /// Error that should be used when the user is not logged in but tries to perform an operation that requires authentication.
61 #[error("The user is not authenticated.")]
62 Unauthenticated,
63
64 /// Error that should be used when the client performs operations that can fail due to IO issues.
65 #[error(transparent)]
66 Io(#[from] std::io::Error),
67
68 /// Error that should be used when the client encounters an error that is not specifically handled.
69 #[error("Internal error: {0}")]
70 Internal(String),
71}
72
73/// This builder struct is used to create a [BurnCentral] client. It should only be used by the
74/// burn_central generated crates.
75#[doc(hidden)]
76pub struct BurnCentralBuilder {
77 endpoint: Option<String>,
78 credentials: BurnCentralCredentials,
79}
80
81impl BurnCentralBuilder {
82 /// Creates a new [BurnCentralBuilder] with the given credentials.
83 pub fn new(credentials: impl Into<BurnCentralCredentials>) -> Self {
84 BurnCentralBuilder {
85 endpoint: None,
86 credentials: credentials.into(),
87 }
88 }
89
90 /// Sets the endpoint for the [BurnCentral] client.
91 pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
92 self.endpoint = Some(endpoint.into());
93 self
94 }
95
96 /// Builds the [BurnCentral] client.
97 pub fn build(self) -> Result<BurnCentral, InitError> {
98 let url = match self.endpoint {
99 Some(s) => s
100 .parse::<Url>()
101 .map_err(|e| InitError::InvalidEndpointUrl(e.to_string()))?,
102 None => {
103 Url::parse("https://central.burn.dev/api/").expect("Default URL should be valid")
104 }
105 };
106 #[allow(deprecated)]
107 let client = Client::from_url(url, &self.credentials)?;
108 Ok(BurnCentral::new(client))
109 }
110}
111
112/// This struct provides the main interface to interact with Burn Central.
113///
114/// It wrap the burn_central_client [Client] and provides higher level methods to interact with
115/// experiments, models, and artifacts.
116#[derive(Clone)]
117pub struct BurnCentral {
118 client: Client,
119}
120
121impl BurnCentral {
122 /// Creates a new [BurnCentral] instance with the given credentials.
123 ///
124 /// This is to be used by the burn central generated crates. It is internal tooling and user
125 /// should not seek to connect with it directly as they would expose their credentials in the
126 /// code.
127 #[doc(hidden)]
128 pub fn login(credentials: impl Into<BurnCentralCredentials>) -> Result<Self, InitError> {
129 let credentials = credentials.into();
130 BurnCentralBuilder::new(credentials).build()
131 }
132
133 /// Creates a new [BurnCentralBuilder] to configure the client.
134 ///
135 /// This is to be used by the burn central generated crates. It is internal tooling and user
136 /// should not seek to connect with it directly as they would expose their credentials in the
137 /// code.
138 #[doc(hidden)]
139 pub fn builder(credentials: impl Into<BurnCentralCredentials>) -> BurnCentralBuilder {
140 BurnCentralBuilder::new(credentials)
141 }
142
143 /// Creates a new instance of [BurnCentral] with the given [Client].
144 fn new(client: Client) -> Self {
145 BurnCentral { client }
146 }
147
148 /// Start a new experiment. This will create a new experiment on the Burn Central backend and start it.
149 pub fn start_experiment(
150 &self,
151 namespace: &str,
152 project_name: &str,
153 digest: String,
154 routine: String,
155 ) -> Result<ExperimentRun, BurnCentralError> {
156 let experiment = self
157 .client
158 .create_experiment(namespace, project_name, None, digest, routine)
159 .map_err(|e| BurnCentralError::Client {
160 context: format!("Failed to create experiment for {namespace}/{project_name}"),
161 source: e,
162 })?;
163
164 ExperimentRun::new(
165 self.client.clone(),
166 namespace,
167 project_name,
168 experiment.experiment_num,
169 )
170 .map_err(BurnCentralError::ExperimentTracker)
171 }
172
173 pub fn artifacts(
174 &self,
175 owner: &str,
176 project: &str,
177 exp_num: i32,
178 ) -> Result<ExperimentArtifactClient, BurnCentralError> {
179 let exp_path = ExperimentPath::new(owner, project, exp_num);
180 Ok(ExperimentArtifactClient::new(self.client.clone(), exp_path))
181 }
182
183 /// Create a model registry for downloading models from Burn Central.
184 /// Models are project-scoped and identified by namespace/project/model_name.
185 pub fn models(&self) -> ModelRegistry {
186 ModelRegistry::new(self.client.clone())
187 }
188}