1use super::{CountResult, MissingFilterError, QueryFailedError, Uint64, schema};
2use crate::{
3 api::v1::{ChainAddress, ServiceSelector, ServiceTypeId},
4 errors::{BlokliClientError, ErrorKind},
5};
6
7pub(crate) fn service_type_to_filter(service_type: ServiceTypeId) -> String {
13 format!("0x{}", hex::encode(service_type))
14}
15
16#[cfg(any(test, feature = "testing"))]
27pub(crate) fn service_type_name(service_type: &ServiceTypeId) -> Option<&str> {
28 let len = service_type.iter().rposition(|byte| *byte != 0)? + 1;
29 let name = std::str::from_utf8(&service_type[..len]).ok()?;
30
31 name.bytes().all(|byte| byte.is_ascii_graphic()).then_some(name)
32}
33
34fn node_to_filter(node: ChainAddress) -> String {
39 hex::encode(node)
40}
41
42#[derive(cynic::QueryVariables, Debug, Default)]
43pub struct ServiceVariables {
44 pub service_type: Option<String>,
45 pub node: Option<String>,
46}
47
48#[derive(cynic::QueryVariables, Debug)]
49pub struct ServicePageVariables {
50 pub service_type: Option<String>,
51 pub node: Option<String>,
52 pub first: i32,
53 pub after: Option<Uint64>,
54 pub watermark: Option<Uint64>,
55 pub live_only: bool,
56}
57
58impl ServicePageVariables {
59 pub fn new(selector: ServiceSelector, after: Option<Uint64>, watermark: Option<Uint64>, live_only: bool) -> Self {
60 let filters = ServiceVariables::from(selector);
61 Self {
62 service_type: filters.service_type,
63 node: filters.node,
64 first: 1000,
65 after,
66 watermark,
67 live_only,
68 }
69 }
70}
71
72impl From<ServiceSelector> for ServiceVariables {
73 fn from(value: ServiceSelector) -> Self {
74 match value {
75 ServiceSelector::ServiceType(service_type) => ServiceVariables {
76 service_type: Some(service_type_to_filter(service_type)),
77 node: None,
78 },
79 ServiceSelector::Node(node) => ServiceVariables {
80 service_type: None,
81 node: Some(node_to_filter(node)),
82 },
83 ServiceSelector::ServiceTypeAndNode { service_type, node } => ServiceVariables {
84 service_type: Some(service_type_to_filter(service_type)),
85 node: Some(node_to_filter(node)),
86 },
87 ServiceSelector::Any => ServiceVariables::default(),
88 }
89 }
90}
91
92#[derive(cynic::QueryVariables, Debug, Default)]
93pub struct ServiceTypeVariables {
94 pub service_type: Option<String>,
95}
96
97impl From<Option<ServiceTypeId>> for ServiceTypeVariables {
98 fn from(value: Option<ServiceTypeId>) -> Self {
99 ServiceTypeVariables {
100 service_type: value.map(service_type_to_filter),
101 }
102 }
103}
104
105#[derive(cynic::QueryFragment, Debug)]
106#[cynic(graphql_type = "QueryRoot", variables = "ServicePageVariables")]
107pub struct QueryServices {
108 #[arguments(serviceType: $service_type, node: $node, first: $first, after: $after, watermark: $watermark, liveOnly: $live_only)]
109 pub services: ServicesResult,
110}
111
112#[derive(cynic::QueryFragment, Debug)]
113#[cynic(graphql_type = "QueryRoot", variables = "ServiceVariables")]
114pub struct QueryServiceCount {
115 #[arguments(serviceType: $service_type, node: $node)]
116 pub service_count: CountResult,
117}
118
119#[derive(cynic::QueryFragment, Debug)]
120#[cynic(graphql_type = "QueryRoot", variables = "ServiceTypeVariables")]
121pub struct QueryServiceTypes {
122 #[arguments(serviceType: $service_type)]
123 pub service_types: ServiceTypesResult,
124}
125
126#[derive(cynic::QueryFragment, Debug)]
127#[cynic(graphql_type = "QueryRoot")]
128pub struct QueryServiceRegistryConfig {
129 pub service_registry_config: ServiceRegistryConfigResult,
130}
131
132#[derive(cynic::QueryFragment, Debug)]
133#[cynic(graphql_type = "SubscriptionRoot", variables = "ServiceVariables")]
134pub struct SubscribeServices {
135 #[arguments(serviceType: $service_type, node: $node)]
136 pub service_updated: ServiceUpdate,
137}
138
139#[derive(cynic::QueryFragment, Debug)]
140#[cynic(graphql_type = "SubscriptionRoot", variables = "ServiceTypeVariables")]
141pub struct SubscribeServiceTypes {
142 #[arguments(serviceType: $service_type)]
143 pub service_type_updated: ServiceTypeUpdate,
144}
145
146#[derive(cynic::QueryFragment, Debug)]
147#[cynic(graphql_type = "SubscriptionRoot")]
148pub struct SubscribeServiceRegistryConfig {
149 pub service_registry_config_updated: ServiceRegistryConfig,
150}
151
152#[derive(cynic::QueryFragment, Debug, Clone, PartialEq, Eq)]
154#[cfg_attr(feature = "serde", derive(serde::Serialize))]
155pub struct ServiceEntry {
156 pub service_type: String,
159 pub node: String,
161 pub safe: String,
163 pub metadata: String,
165 pub registered_at: Uint64,
167 pub updated_at: Uint64,
169}
170
171#[derive(cynic::QueryFragment, Debug, Clone, PartialEq, Eq)]
173#[cfg_attr(feature = "serde", derive(serde::Serialize))]
174pub struct ServiceTypeInfo {
175 pub service_type: String,
177 pub owner: Option<String>,
179 pub requirement: Option<String>,
181 pub registration_burn: String,
183 pub update_burn: String,
185}
186
187#[derive(cynic::QueryFragment, Debug, Clone, PartialEq, Eq)]
189#[cfg_attr(feature = "serde", derive(serde::Serialize))]
190pub struct ServiceRegistryConfig {
191 pub type_registration_fee: String,
193 pub node_safe_registry: String,
195}
196
197#[derive(cynic::Enum, Clone, Copy, Debug, PartialEq, Eq)]
199pub enum ServiceUpdateKind {
200 #[cynic(rename = "REGISTERED")]
202 Registered,
203 #[cynic(rename = "UPDATED")]
205 Updated,
206 #[cynic(rename = "DEREGISTERED")]
208 Deregistered,
209}
210
211#[derive(cynic::Enum, Clone, Copy, Debug, PartialEq, Eq)]
213pub enum ServiceTypeUpdateKind {
214 #[cynic(rename = "REGISTERED")]
216 Registered,
217 #[cynic(rename = "OWNER_CHANGED")]
219 OwnerChanged,
220 #[cynic(rename = "REQUIREMENT_CHANGED")]
222 RequirementChanged,
223 #[cynic(rename = "REGISTRATION_BURN_CHANGED")]
225 RegistrationBurnChanged,
226 #[cynic(rename = "UPDATE_BURN_CHANGED")]
228 UpdateBurnChanged,
229 #[cynic(rename = "REGISTRATION_FEE_CHANGED")]
231 RegistrationFeeChanged,
232 #[cynic(rename = "REGISTRY_POINTER_CHANGED")]
234 RegistryPointerChanged,
235}
236
237#[derive(cynic::QueryFragment, Debug, Clone, PartialEq, Eq)]
239#[cfg_attr(feature = "serde", derive(serde::Serialize))]
240pub struct ServiceUpdate {
241 pub kind: ServiceUpdateKind,
243 pub service_type: String,
245 pub node: String,
247 pub entry: Option<ServiceEntry>,
250}
251
252#[derive(cynic::QueryFragment, Debug, Clone, PartialEq, Eq)]
254#[cfg_attr(feature = "serde", derive(serde::Serialize))]
255pub struct ServiceTypeUpdate {
256 pub kind: ServiceTypeUpdateKind,
258 pub service_type: Option<String>,
260 pub config: Option<ServiceTypeInfo>,
262 pub registry_config: Option<ServiceRegistryConfig>,
264}
265
266#[derive(cynic::QueryFragment, Debug, Clone, PartialEq, Eq)]
268#[cfg_attr(feature = "serde", derive(serde::Serialize))]
269pub struct ServicesList {
270 pub services: Vec<ServiceEntry>,
272 pub watermark: Uint64,
274 pub next_cursor: Option<Uint64>,
276}
277
278#[derive(Debug)]
279pub struct ServicePage {
280 pub services: Vec<ServiceEntry>,
281 pub watermark: Uint64,
282 pub next_cursor: Option<Uint64>,
283}
284
285#[derive(cynic::QueryFragment, Debug, Clone, PartialEq, Eq)]
287#[cfg_attr(feature = "serde", derive(serde::Serialize))]
288pub struct ServiceTypesList {
289 pub service_types: Vec<ServiceTypeInfo>,
291}
292
293#[derive(cynic::InlineFragments, Debug)]
294pub enum ServicesResult {
295 ServicesList(ServicesList),
296 MissingFilterError(MissingFilterError),
297 QueryFailedError(QueryFailedError),
298 #[cynic(fallback)]
299 Unknown,
300}
301
302impl From<ServicesResult> for Result<ServicePage, BlokliClientError> {
303 fn from(value: ServicesResult) -> Self {
304 match value {
305 ServicesResult::ServicesList(list) => Ok(ServicePage {
306 services: list.services,
307 watermark: list.watermark,
308 next_cursor: list.next_cursor,
309 }),
310 ServicesResult::MissingFilterError(e) => Err(e.into()),
311 ServicesResult::QueryFailedError(e) => Err(e.into()),
312 ServicesResult::Unknown => Err(ErrorKind::NoData.into()),
313 }
314 }
315}
316
317#[derive(cynic::InlineFragments, Debug)]
318pub enum ServiceTypesResult {
319 ServiceTypesList(ServiceTypesList),
320 QueryFailedError(QueryFailedError),
321 #[cynic(fallback)]
322 Unknown,
323}
324
325#[derive(cynic::InlineFragments, Debug)]
326pub enum ServiceRegistryConfigResult {
327 ServiceRegistryConfig(ServiceRegistryConfig),
328 QueryFailedError(QueryFailedError),
329 #[cynic(fallback)]
330 Unknown,
331}
332
333impl From<ServiceRegistryConfigResult> for Result<ServiceRegistryConfig, BlokliClientError> {
334 fn from(value: ServiceRegistryConfigResult) -> Self {
335 match value {
336 ServiceRegistryConfigResult::ServiceRegistryConfig(config) => Ok(config),
337 ServiceRegistryConfigResult::QueryFailedError(e) => Err(e.into()),
338 ServiceRegistryConfigResult::Unknown => Err(ErrorKind::NoData.into()),
339 }
340 }
341}
342
343impl From<ServiceTypesResult> for Result<Vec<ServiceTypeInfo>, BlokliClientError> {
344 fn from(value: ServiceTypesResult) -> Self {
345 match value {
346 ServiceTypesResult::ServiceTypesList(list) => Ok(list.service_types),
347 ServiceTypesResult::QueryFailedError(e) => Err(e.into()),
348 ServiceTypesResult::Unknown => Err(ErrorKind::NoData.into()),
349 }
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::{ServiceVariables, service_type_name, service_type_to_filter};
356 use crate::api::v1::ServiceSelector;
357
358 const GVPN_EXIT: [u8; 32] = [
359 0x67, 0x76, 0x70, 0x6e, 0x3a, 0x65, 0x78, 0x69, 0x74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
360 0, 0, 0, 0,
361 ];
362
363 #[test]
364 fn service_type_filter_is_zero_padded_prefixed_hex() {
365 assert_eq!(
366 service_type_to_filter(GVPN_EXIT),
367 "0x6776706e3a657869740000000000000000000000000000000000000000000000"
368 );
369 }
370
371 #[test]
372 fn ascii_name_is_decoded_from_a_right_padded_id() {
373 assert_eq!(service_type_name(&GVPN_EXIT), Some("gvpn:exit"));
374 }
375
376 #[test]
377 fn ascii_name_rejects_ids_outside_the_convention() {
378 let mut interior_nul = GVPN_EXIT;
380 interior_nul[31] = b'x';
381 assert_eq!(service_type_name(&interior_nul), None);
382
383 let mut with_space = [0u8; 32];
385 with_space[..3].copy_from_slice(b"a b");
386 assert_eq!(service_type_name(&with_space), None);
387
388 assert_eq!(service_type_name(&[0u8; 32]), None);
390 }
391
392 #[test]
393 fn any_selector_sends_no_filters() {
394 let variables = ServiceVariables::from(ServiceSelector::Any);
395
396 assert!(variables.service_type.is_none());
397 assert!(variables.node.is_none());
398 }
399
400 #[test]
401 fn combined_selector_sends_both_filters() {
402 let variables = ServiceVariables::from(ServiceSelector::ServiceTypeAndNode {
403 service_type: GVPN_EXIT,
404 node: [0x11; 20],
405 });
406
407 assert_eq!(
408 variables.node.as_deref(),
409 Some("1111111111111111111111111111111111111111")
410 );
411 assert_eq!(
412 variables.service_type.as_deref(),
413 Some("0x6776706e3a657869740000000000000000000000000000000000000000000000")
414 );
415 }
416}