Skip to main content

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}