1use std::sync::Arc;
2
3use serde_json::Value;
4
5use crate::adapters::{
6 AdminAdapter, ArtifactsAdapter, AuditAdapter, BlobsAdapter, ContextAdapter, ExecAdapter,
7 FederationAdapter, FederationInternalAdapter, FilesAdapter, GraphAdapter, MigrationsAdapter,
8 MirrorsAdapter, ObservabilityAdapter, PolicyAdapter, QueryAdapter, RegistryAdapter,
9 RepositoriesAdapter, SearchIndexAdapter, SnapshotsAdapter, WorkspacesAdapter,
10};
11use crate::config::TreeDxConfig;
12use crate::error::TreeDxResult;
13use crate::generated::openapi_types::TREEDX_OPENAPI_OPERATIONS;
14use crate::transport::{ReqwestTransport, Transport, TreeDxHttpMethod};
15
16#[derive(Clone)]
17pub struct TreeDxClient {
18 config: TreeDxConfig,
19 transport: Arc<dyn Transport>,
20 repositories: RepositoriesAdapter,
21 workspaces: WorkspacesAdapter,
22 files: FilesAdapter,
23 blobs: BlobsAdapter,
24 query: QueryAdapter,
25 graph: GraphAdapter,
26 context: ContextAdapter,
27 federation: FederationAdapter,
28 registry: RegistryAdapter,
29 snapshots: SnapshotsAdapter,
30 artifacts: ArtifactsAdapter,
31 mirrors: MirrorsAdapter,
32 migrations: MigrationsAdapter,
33 exec: ExecAdapter,
34 observability: ObservabilityAdapter,
35 admin: AdminAdapter,
36 audit: AuditAdapter,
37 policy: PolicyAdapter,
38 search_index: SearchIndexAdapter,
39 federation_internal: FederationInternalAdapter,
40}
41
42impl TreeDxClient {
43 pub fn new(config: TreeDxConfig) -> Self {
44 let transport = Arc::new(ReqwestTransport::new(config.clone()));
45 Self::with_transport(config, transport)
46 }
47
48 pub fn with_transport(config: TreeDxConfig, transport: Arc<dyn Transport>) -> Self {
49 Self {
50 config,
51 repositories: RepositoriesAdapter::new(transport.clone()),
52 workspaces: WorkspacesAdapter::new(transport.clone()),
53 files: FilesAdapter::new(transport.clone()),
54 blobs: BlobsAdapter::new(transport.clone()),
55 query: QueryAdapter::new(transport.clone()),
56 graph: GraphAdapter::new(transport.clone()),
57 context: ContextAdapter::new(transport.clone()),
58 federation: FederationAdapter::new(transport.clone()),
59 registry: RegistryAdapter::new(transport.clone()),
60 snapshots: SnapshotsAdapter::new(transport.clone()),
61 artifacts: ArtifactsAdapter::new(transport.clone()),
62 mirrors: MirrorsAdapter::new(transport.clone()),
63 migrations: MigrationsAdapter::new(transport.clone()),
64 exec: ExecAdapter::new(transport.clone()),
65 observability: ObservabilityAdapter::new(transport.clone()),
66 admin: AdminAdapter::new(transport.clone()),
67 audit: AuditAdapter::new(transport.clone()),
68 policy: PolicyAdapter::new(transport.clone()),
69 search_index: SearchIndexAdapter::new(transport.clone()),
70 federation_internal: FederationInternalAdapter::new(transport.clone()),
71 transport,
72 }
73 }
74
75 pub fn config(&self) -> &TreeDxConfig {
76 &self.config
77 }
78 pub fn transport(&self) -> &Arc<dyn Transport> {
79 &self.transport
80 }
81 pub fn repositories(&self) -> &RepositoriesAdapter {
82 &self.repositories
83 }
84 pub fn workspaces(&self) -> &WorkspacesAdapter {
85 &self.workspaces
86 }
87 pub fn files(&self) -> &FilesAdapter {
88 &self.files
89 }
90 pub fn blobs(&self) -> &BlobsAdapter {
91 &self.blobs
92 }
93 pub fn query(&self) -> &QueryAdapter {
94 &self.query
95 }
96 pub fn graph(&self) -> &GraphAdapter {
97 &self.graph
98 }
99 pub fn context(&self) -> &ContextAdapter {
100 &self.context
101 }
102 pub fn federation(&self) -> &FederationAdapter {
103 &self.federation
104 }
105 pub fn registry(&self) -> &RegistryAdapter {
106 &self.registry
107 }
108 pub fn snapshots(&self) -> &SnapshotsAdapter {
109 &self.snapshots
110 }
111 pub fn artifacts(&self) -> &ArtifactsAdapter {
112 &self.artifacts
113 }
114 pub fn mirrors(&self) -> &MirrorsAdapter {
115 &self.mirrors
116 }
117 pub fn migrations(&self) -> &MigrationsAdapter {
118 &self.migrations
119 }
120 pub fn exec(&self) -> &ExecAdapter {
121 &self.exec
122 }
123 pub fn observability(&self) -> &ObservabilityAdapter {
124 &self.observability
125 }
126 pub fn admin(&self) -> &AdminAdapter {
127 &self.admin
128 }
129 pub fn audit(&self) -> &AuditAdapter {
130 &self.audit
131 }
132 pub fn policy(&self) -> &PolicyAdapter {
133 &self.policy
134 }
135 pub fn search_index(&self) -> &SearchIndexAdapter {
136 &self.search_index
137 }
138 pub fn federation_internal(&self) -> &FederationInternalAdapter {
139 &self.federation_internal
140 }
141
142 pub async fn health(&self) -> TreeDxResult<Value> {
143 self.observability.health().await
144 }
145 pub async fn version(&self) -> TreeDxResult<Value> {
146 crate::adapters::common::json_request(
147 &self.transport,
148 crate::transport::TreeDxHttpMethod::Get,
149 "/api/v1/version",
150 None,
151 None,
152 )
153 .await
154 }
155 pub async fn whoami(&self) -> TreeDxResult<Value> {
156 crate::adapters::common::json_request(
157 &self.transport,
158 crate::transport::TreeDxHttpMethod::Get,
159 "/api/v1/auth/whoami",
160 None,
161 None,
162 )
163 .await
164 }
165 pub async fn effective_scope(&self) -> TreeDxResult<Value> {
166 crate::adapters::common::json_request(
167 &self.transport,
168 crate::transport::TreeDxHttpMethod::Get,
169 "/api/v1/policy/effective-scope",
170 None,
171 None,
172 )
173 .await
174 }
175
176 pub async fn auth_mode(&self) -> TreeDxResult<Value> {
177 crate::adapters::common::json_request(
178 &self.transport,
179 TreeDxHttpMethod::Get,
180 "/api/v1/auth/mode",
181 None,
182 None,
183 )
184 .await
185 }
186
187 pub async fn create_dev_token(&self, body: Option<Value>) -> TreeDxResult<Value> {
188 crate::adapters::common::json_request(
189 &self.transport,
190 TreeDxHttpMethod::Post,
191 "/api/v1/auth/dev-token",
192 body,
193 None,
194 )
195 .await
196 }
197
198 pub async fn operation(
199 &self,
200 method: TreeDxHttpMethod,
201 path: &str,
202 options: OperationOptions,
203 ) -> TreeDxResult<Value> {
204 let method_name = method.as_str();
205 if !TREEDX_OPENAPI_OPERATIONS
206 .iter()
207 .any(|operation| operation.method == method_name && operation.path == path)
208 {
209 return Err(crate::error::TreeDxApiError::network(format!(
210 "unknown TreeDX OpenAPI operation: {method_name} {path}"
211 )));
212 }
213 let mut resolved = path.to_string();
214 for name in path.match_indices('{').filter_map(|(start, _)| {
215 let rest = &path[start + 1..];
216 rest.find('}').map(|end| &rest[..end])
217 }) {
218 let value = options.path_params.get(name).ok_or_else(|| {
219 crate::error::TreeDxApiError::network(format!(
220 "missing path parameter {name} for {method_name} {path}"
221 ))
222 })?;
223 resolved = resolved.replace(
224 &format!("{{{name}}}"),
225 &crate::adapters::common::segment(value),
226 );
227 }
228 crate::adapters::common::json_request(
229 &self.transport,
230 method,
231 resolved,
232 options.body,
233 Some(options.query),
234 )
235 .await
236 }
237}
238
239#[derive(Clone, Debug, Default)]
240pub struct OperationOptions {
241 pub path_params: std::collections::BTreeMap<String, String>,
242 pub query: std::collections::BTreeMap<String, String>,
243 pub body: Option<Value>,
244}
245
246#[derive(Clone)]
247pub struct TreeDxRegistryClient {
248 registry: RegistryAdapter,
249}
250
251impl TreeDxRegistryClient {
252 pub fn new(client: &TreeDxClient) -> Self {
253 Self {
254 registry: client.registry().clone(),
255 }
256 }
257
258 pub fn registry(&self) -> &RegistryAdapter {
259 &self.registry
260 }
261}
262
263#[derive(Clone)]
264pub struct TreeDxFederatedClient {
265 federation: FederationAdapter,
266}
267
268impl TreeDxFederatedClient {
269 pub fn new(client: &TreeDxClient) -> Self {
270 Self {
271 federation: client.federation().clone(),
272 }
273 }
274
275 pub fn federation(&self) -> &FederationAdapter {
276 &self.federation
277 }
278}