llm_registry_api/graphql/
query.rs1use async_graphql::{Context, Object, Result};
6use llm_registry_core::AssetId;
7use llm_registry_service::{SearchAssetsRequest, ServiceRegistry, SortField, SortOrder};
8use std::sync::Arc;
9
10use super::types::{GqlAsset, GqlAssetConnection, GqlAssetFilter, GqlDependencyNode};
11use crate::error::ApiError;
12
13pub struct Query;
15
16#[Object]
17impl Query {
18 async fn asset(&self, ctx: &Context<'_>, id: String) -> Result<Option<GqlAsset>> {
20 let services = ctx.data::<Arc<ServiceRegistry>>()?;
21
22 let asset_id = id
23 .parse::<AssetId>()
24 .map_err(|e| ApiError::bad_request(format!("Invalid asset ID: {}", e)))?;
25
26 let asset = services
27 .search()
28 .get_asset(&asset_id)
29 .await
30 .map_err(|e| ApiError::from(e))?;
31
32 Ok(asset.map(GqlAsset))
33 }
34
35 async fn assets(
37 &self,
38 ctx: &Context<'_>,
39 #[graphql(desc = "Filter criteria", default)] filter: Option<GqlAssetFilter>,
40 #[graphql(desc = "Number of items to return", default = 20)] limit: i64,
41 #[graphql(desc = "Number of items to skip", default = 0)] offset: i64,
42 ) -> Result<GqlAssetConnection> {
43 let services = ctx.data::<Arc<ServiceRegistry>>()?;
44
45 let mut search_request = SearchAssetsRequest {
47 text: None,
48 asset_types: vec![],
49 tags: vec![],
50 author: None,
51 storage_backend: None,
52 exclude_deprecated: true,
53 limit,
54 offset,
55 sort_by: SortField::CreatedAt,
56 sort_order: SortOrder::Descending,
57 };
58
59 if let Some(f) = filter {
61 if let Some(asset_type) = f.asset_type {
62 search_request.asset_types = vec![asset_type.to_core()];
63 }
64 if let Some(tags) = f.tags {
65 search_request.tags = tags;
66 }
67 search_request.text = f.name;
68 if let Some(status) = f.status {
71 use super::types::GqlAssetStatus;
72 search_request.exclude_deprecated = status != GqlAssetStatus::Deprecated;
73 }
74 }
75
76 let response = services
77 .search()
78 .search_assets(search_request)
79 .await
80 .map_err(|e| ApiError::from(e))?;
81
82 Ok(GqlAssetConnection {
83 nodes: response.assets.into_iter().map(GqlAsset).collect(),
84 total_count: response.total,
85 has_next_page: (response.offset + response.limit) < response.total,
86 })
87 }
88
89 async fn dependencies(
91 &self,
92 ctx: &Context<'_>,
93 #[graphql(desc = "Asset ID")] id: String,
94 #[graphql(desc = "Maximum depth to traverse (-1 for unlimited)", default = -1)]
95 max_depth: i32,
96 ) -> Result<Vec<GqlDependencyNode>> {
97 let services = ctx.data::<Arc<ServiceRegistry>>()?;
98
99 let asset_id = id
100 .parse::<AssetId>()
101 .map_err(|e| ApiError::bad_request(format!("Invalid asset ID: {}", e)))?;
102
103 let request = llm_registry_service::GetDependencyGraphRequest {
104 asset_id,
105 max_depth,
106 };
107
108 let response = services
109 .search()
110 .get_dependency_graph(request)
111 .await
112 .map_err(|e| ApiError::from(e))?;
113
114 Ok(response
115 .dependencies
116 .into_iter()
117 .map(GqlDependencyNode::from)
118 .collect())
119 }
120
121 async fn dependents(
123 &self,
124 ctx: &Context<'_>,
125 #[graphql(desc = "Asset ID")] id: String,
126 ) -> Result<Vec<GqlAsset>> {
127 let services = ctx.data::<Arc<ServiceRegistry>>()?;
128
129 let asset_id = id
130 .parse::<AssetId>()
131 .map_err(|e| ApiError::bad_request(format!("Invalid asset ID: {}", e)))?;
132
133 let dependents = services
134 .search()
135 .get_reverse_dependencies(&asset_id)
136 .await
137 .map_err(|e| ApiError::from(e))?;
138
139 Ok(dependents.into_iter().map(GqlAsset).collect())
140 }
141
142 async fn all_tags(&self, ctx: &Context<'_>) -> Result<Vec<String>> {
144 let services = ctx.data::<Arc<ServiceRegistry>>()?;
145
146 let tags = services
147 .search()
148 .list_all_tags()
149 .await
150 .map_err(|e| ApiError::from(e))?;
151
152 Ok(tags)
153 }
154
155 async fn health(&self, ctx: &Context<'_>) -> Result<bool> {
157 let services = ctx.data::<Arc<ServiceRegistry>>()?;
158
159 match services.search().list_all_tags().await {
161 Ok(_) => Ok(true),
162 Err(_) => Ok(false),
163 }
164 }
165
166 async fn version(&self) -> String {
168 env!("CARGO_PKG_VERSION").to_string()
169 }
170}