Skip to main content

reifydb_client/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3#![cfg_attr(not(debug_assertions), deny(clippy::disallowed_methods))]
4#![cfg_attr(debug_assertions, warn(clippy::disallowed_methods))]
5#![allow(clippy::tabs_in_doc_comments)]
6
7/// Wire format for client-server communication.
8#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
9pub enum WireFormat {
10	#[default]
11	Json,
12	Proto,
13	Rbcf,
14}
15
16#[cfg(any(feature = "ws", feature = "grpc"))]
17mod changes;
18#[cfg(all(feature = "dst", reifydb_single_threaded))]
19pub mod dst;
20#[cfg(feature = "grpc")]
21pub mod grpc;
22#[cfg(feature = "http")]
23pub mod http;
24#[cfg(any(feature = "http", feature = "ws"))]
25mod session;
26#[cfg(any(feature = "ws", feature = "grpc", all(feature = "dst", reifydb_single_threaded)))]
27pub mod subscription;
28#[cfg(feature = "ws")]
29mod utils;
30#[cfg(feature = "ws")]
31pub mod ws;
32
33// Re-export client types
34#[cfg(any(feature = "http", feature = "ws"))]
35use std::collections::HashMap;
36#[cfg(any(feature = "http", feature = "ws"))]
37use std::sync::Arc;
38
39#[cfg(all(feature = "dst", reifydb_single_threaded))]
40pub use dst::DstClient;
41#[cfg(feature = "grpc")]
42pub use grpc::{
43	BatchFramesEnvelope, BatchGrpcSubscription, BatchMemberHandle, BatchStreamEvent, GrpcChange, GrpcClient,
44	GrpcSubscription, RawChangePayload,
45};
46#[cfg(feature = "http")]
47pub use http::HttpClient;
48// Re-export derive macro
49pub use reifydb_client_derive::FromFrame;
50pub use reifydb_type as r#type;
51pub use reifydb_type::{
52	params::Params,
53	value,
54	value::{
55		Value,
56		frame::{
57			column::FrameColumn,
58			data::FrameColumnData,
59			extract::FrameError,
60			frame::Frame,
61			from_frame::FromFrameError,
62			row::{FrameRow, FrameRows},
63		},
64		ordered_f32::OrderedF32,
65		ordered_f64::OrderedF64,
66		try_from::{FromValueError, TryFromValue, TryFromValueCoerce},
67		r#type::Type,
68	},
69};
70#[cfg(any(feature = "http", feature = "ws"))]
71use serde::{Deserialize, Serialize};
72#[cfg(any(feature = "http", feature = "ws"))]
73use serde_json::Value as JsonValue;
74#[cfg(any(feature = "ws", feature = "grpc", all(feature = "dst", reifydb_single_threaded)))]
75pub use subscription::{BatchItem, HydrationConfig, SubscriptionConfig, build_subscription_rql};
76#[cfg(feature = "ws")]
77pub use ws::{BatchPushEvent, WsBatchSubscription, WsClient};
78
79/// Server-reported metadata about a single executed request.
80#[cfg_attr(any(feature = "http", feature = "ws"), derive(Serialize, Deserialize))]
81#[derive(Debug, Clone)]
82pub struct ResponseMeta {
83	pub fingerprint: String,
84	pub duration: String,
85}
86
87/// Result type for admin operations
88#[derive(Debug)]
89pub struct AdminResult {
90	pub frames: Vec<Frame>,
91	pub meta: Option<ResponseMeta>,
92}
93
94/// Result type for command operations
95#[derive(Debug)]
96pub struct CommandResult {
97	pub frames: Vec<Frame>,
98	pub meta: Option<ResponseMeta>,
99}
100
101/// Result type for query operations
102#[derive(Debug)]
103pub struct QueryResult {
104	pub frames: Vec<Frame>,
105	pub meta: Option<ResponseMeta>,
106}
107
108/// Result type for authentication login operations
109#[derive(Debug, Clone)]
110pub struct LoginResult {
111	/// Session token for subsequent requests
112	pub token: String,
113	/// Identity UUID of the authenticated user
114	pub identity: String,
115}
116
117#[cfg(any(feature = "http", feature = "ws"))]
118/// Wire format for a single typed value: `{"type": "Int2", "value": "1234"}`.
119#[derive(Debug, Serialize, Deserialize)]
120pub struct WireValue {
121	#[serde(rename = "type")]
122	pub type_name: String,
123	pub value: String,
124}
125
126#[cfg(any(feature = "http", feature = "ws"))]
127/// Wire format for query parameters.
128///
129/// Either positional or named:
130/// - Positional: `[{"type":"Int2","value":"1234"}, ...]`
131/// - Named: `{"key": {"type":"Int2","value":"1234"}, ...}`
132#[derive(Debug, Serialize, Deserialize)]
133#[serde(untagged)]
134pub enum WireParams {
135	Positional(Vec<WireValue>),
136	Named(HashMap<String, WireValue>),
137}
138
139#[cfg(any(feature = "http", feature = "ws"))]
140fn value_to_wire(value: Value) -> WireValue {
141	let (type_name, value_str): (&str, String) = match &value {
142		Value::None {
143			..
144		} => ("None", "\u{27EA}none\u{27EB}".to_string()),
145		Value::Boolean(b) => ("Boolean", b.to_string()),
146		Value::Float4(f) => ("Float4", f.to_string()),
147		Value::Float8(f) => ("Float8", f.to_string()),
148		Value::Int1(i) => ("Int1", i.to_string()),
149		Value::Int2(i) => ("Int2", i.to_string()),
150		Value::Int4(i) => ("Int4", i.to_string()),
151		Value::Int8(i) => ("Int8", i.to_string()),
152		Value::Int16(i) => ("Int16", i.to_string()),
153		Value::Utf8(s) => ("Utf8", s.clone()),
154		Value::Uint1(u) => ("Uint1", u.to_string()),
155		Value::Uint2(u) => ("Uint2", u.to_string()),
156		Value::Uint4(u) => ("Uint4", u.to_string()),
157		Value::Uint8(u) => ("Uint8", u.to_string()),
158		Value::Uint16(u) => ("Uint16", u.to_string()),
159		Value::Uuid4(u) => ("Uuid4", u.to_string()),
160		Value::Uuid7(u) => ("Uuid7", u.to_string()),
161		Value::Date(d) => ("Date", d.to_string()),
162		Value::DateTime(dt) => ("DateTime", dt.to_string()),
163		Value::Time(t) => ("Time", t.to_string()),
164		Value::Duration(d) => ("Duration", d.to_iso_string()),
165		Value::Blob(b) => ("Blob", b.to_hex()),
166		Value::IdentityId(id) => ("IdentityId", id.to_string()),
167		Value::Int(i) => ("Int", i.to_string()),
168		Value::Uint(u) => ("Uint", u.to_string()),
169		Value::Decimal(d) => ("Decimal", d.to_string()),
170		Value::Any(v) => return value_to_wire(*v.clone()),
171		Value::DictionaryId(id) => ("DictionaryId", id.to_string()),
172		Value::Type(t) => ("Type", t.to_string()),
173		Value::List(items) => ("List", format!("{}", Value::List(items.clone()))),
174		Value::Record(fields) => ("Record", format!("{}", Value::Record(fields.clone()))),
175		Value::Tuple(items) => ("Tuple", format!("{}", Value::Tuple(items.clone()))),
176	};
177	WireValue {
178		type_name: type_name.to_string(),
179		value: value_str,
180	}
181}
182
183#[cfg(any(feature = "http", feature = "ws"))]
184pub fn params_to_wire(params: Params) -> Option<WireParams> {
185	match params {
186		Params::None => None,
187		Params::Positional(values) => Some(WireParams::Positional(
188			Arc::unwrap_or_clone(values).into_iter().map(value_to_wire).collect(),
189		)),
190		Params::Named(map) => Some(WireParams::Named(
191			Arc::unwrap_or_clone(map).into_iter().map(|(k, v)| (k, value_to_wire(v))).collect(),
192		)),
193	}
194}
195
196#[cfg(any(feature = "http", feature = "ws"))]
197#[derive(Debug, Serialize, Deserialize)]
198pub struct Request {
199	pub id: String,
200	#[serde(flatten)]
201	pub payload: RequestPayload,
202}
203
204#[cfg(any(feature = "http", feature = "ws"))]
205#[derive(Debug, Serialize, Deserialize)]
206#[serde(tag = "type", content = "payload")]
207pub enum RequestPayload {
208	Auth(AuthRequest),
209	Admin(AdminRequest),
210	Command(CommandRequest),
211	Query(QueryRequest),
212	Subscribe(SubscribeRequest),
213	Unsubscribe(UnsubscribeRequest),
214	BatchSubscribe(BatchSubscribeRequest),
215	BatchUnsubscribe(BatchUnsubscribeRequest),
216	Call(CallRequest),
217	Logout,
218}
219
220#[cfg(any(feature = "http", feature = "ws"))]
221#[derive(Debug, Serialize, Deserialize)]
222pub struct AdminRequest {
223	pub rql: String,
224	pub params: Option<WireParams>,
225	#[serde(skip_serializing_if = "Option::is_none")]
226	pub format: Option<String>,
227}
228
229#[cfg(any(feature = "http", feature = "ws"))]
230#[derive(Debug, Serialize, Deserialize)]
231pub struct AuthRequest {
232	#[serde(skip_serializing_if = "Option::is_none")]
233	pub token: Option<String>,
234	#[serde(skip_serializing_if = "Option::is_none")]
235	pub method: Option<String>,
236	#[serde(skip_serializing_if = "Option::is_none")]
237	pub credentials: Option<HashMap<String, String>>,
238}
239
240#[cfg(any(feature = "http", feature = "ws"))]
241#[derive(Debug, Serialize, Deserialize)]
242pub struct CommandRequest {
243	pub rql: String,
244	pub params: Option<WireParams>,
245	#[serde(skip_serializing_if = "Option::is_none")]
246	pub format: Option<String>,
247}
248
249#[cfg(any(feature = "http", feature = "ws"))]
250#[derive(Debug, Serialize, Deserialize)]
251pub struct QueryRequest {
252	pub rql: String,
253	pub params: Option<WireParams>,
254	#[serde(skip_serializing_if = "Option::is_none")]
255	pub format: Option<String>,
256}
257
258#[cfg(any(feature = "http", feature = "ws"))]
259#[derive(Debug, Serialize, Deserialize)]
260pub struct SubscribeRequest {
261	pub rql: String,
262	#[serde(skip_serializing_if = "Option::is_none")]
263	pub format: Option<String>,
264}
265
266#[cfg(any(feature = "http", feature = "ws"))]
267#[derive(Debug, Serialize, Deserialize)]
268pub struct UnsubscribeRequest {
269	pub subscription_id: String,
270}
271
272#[cfg(any(feature = "http", feature = "ws"))]
273#[derive(Debug, Serialize, Deserialize)]
274pub struct BatchSubscribeRequest {
275	pub queries: Vec<String>,
276	#[serde(skip_serializing_if = "Option::is_none")]
277	pub format: Option<String>,
278}
279
280#[cfg(any(feature = "http", feature = "ws"))]
281#[derive(Debug, Serialize, Deserialize)]
282pub struct BatchUnsubscribeRequest {
283	pub batch_id: String,
284}
285
286#[cfg(any(feature = "http", feature = "ws"))]
287#[derive(Debug, Serialize, Deserialize)]
288pub struct CallRequest {
289	pub name: String,
290	pub params: Option<WireParams>,
291}
292
293#[cfg(any(feature = "http", feature = "ws"))]
294#[derive(Debug, Serialize, Deserialize)]
295pub struct Response {
296	pub id: String,
297	#[serde(flatten)]
298	pub payload: ResponsePayload,
299}
300
301#[cfg(any(feature = "http", feature = "ws"))]
302#[derive(Debug, Serialize, Deserialize)]
303#[serde(tag = "type", content = "payload")]
304pub enum ResponsePayload {
305	Auth(AuthResponse),
306	Err(ErrResponse),
307	Admin(AdminResponse),
308	Command(CommandResponse),
309	Query(QueryResponse),
310	Subscribed(SubscribedResponse),
311	Unsubscribed(UnsubscribedResponse),
312	BatchSubscribed(BatchSubscribedResponse),
313	BatchUnsubscribed(BatchUnsubscribedResponse),
314	Call(CallResponse),
315	Logout(LogoutResponsePayload),
316}
317
318#[cfg(any(feature = "http", feature = "ws"))]
319#[derive(Debug, Serialize, Deserialize)]
320pub struct AdminResponse {
321	pub content_type: String,
322	pub body: JsonValue,
323	#[serde(default)]
324	pub meta: Option<ResponseMeta>,
325}
326
327#[cfg(any(feature = "http", feature = "ws"))]
328use reifydb_type::error::Diagnostic;
329
330#[cfg(any(feature = "http", feature = "ws"))]
331#[derive(Debug, Serialize, Deserialize)]
332pub struct AuthResponse {
333	#[serde(skip_serializing_if = "Option::is_none")]
334	pub status: Option<String>,
335	#[serde(skip_serializing_if = "Option::is_none")]
336	pub token: Option<String>,
337	#[serde(skip_serializing_if = "Option::is_none")]
338	pub identity: Option<String>,
339}
340
341#[cfg(any(feature = "http", feature = "ws"))]
342#[derive(Debug, Serialize, Deserialize)]
343pub struct ErrResponse {
344	pub diagnostic: Diagnostic,
345}
346
347#[cfg(any(feature = "http", feature = "ws"))]
348#[derive(Debug, Serialize, Deserialize)]
349pub struct CommandResponse {
350	pub content_type: String,
351	pub body: JsonValue,
352	#[serde(default)]
353	pub meta: Option<ResponseMeta>,
354}
355
356#[cfg(any(feature = "http", feature = "ws"))]
357#[derive(Debug, Serialize, Deserialize)]
358pub struct QueryResponse {
359	pub content_type: String,
360	pub body: JsonValue,
361	#[serde(default)]
362	pub meta: Option<ResponseMeta>,
363}
364
365#[cfg(any(feature = "http", feature = "ws"))]
366#[derive(Debug, Serialize, Deserialize)]
367pub struct CallResponse {
368	pub content_type: String,
369	pub body: JsonValue,
370	#[serde(default)]
371	pub meta: Option<ResponseMeta>,
372}
373
374#[cfg(any(feature = "http", feature = "ws"))]
375#[derive(Debug, Serialize, Deserialize)]
376pub struct SubscribedResponse {
377	pub subscription_id: String,
378}
379
380#[cfg(any(feature = "http", feature = "ws"))]
381#[derive(Debug, Serialize, Deserialize)]
382pub struct UnsubscribedResponse {
383	pub subscription_id: String,
384}
385
386#[cfg(any(feature = "http", feature = "ws"))]
387#[derive(Debug, Serialize, Deserialize)]
388pub struct BatchSubscribedResponse {
389	pub batch_id: String,
390	pub members: Vec<BatchMemberInfo>,
391}
392
393#[cfg(any(feature = "http", feature = "ws"))]
394#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct BatchMemberInfo {
396	pub index: usize,
397	pub subscription_id: String,
398}
399
400#[cfg(any(feature = "http", feature = "ws"))]
401#[derive(Debug, Serialize, Deserialize)]
402pub struct BatchUnsubscribedResponse {
403	pub batch_id: String,
404}
405
406#[cfg(any(feature = "http", feature = "ws"))]
407#[derive(Debug, Serialize, Deserialize)]
408pub struct LogoutResponsePayload {
409	pub status: String,
410}
411
412#[cfg(any(feature = "http", feature = "ws"))]
413#[derive(Debug, Serialize, Deserialize)]
414#[serde(tag = "type", content = "payload")]
415pub enum ServerPush {
416	Change(WireChangePayload),
417	BatchChange(WireBatchChangePayload),
418	BatchMemberClosed(BatchMemberClosedPayload),
419	BatchClosed(BatchClosedPayload),
420}
421
422#[cfg(any(feature = "http", feature = "ws"))]
423#[derive(Debug, Clone, Serialize, Deserialize)]
424pub struct WireChangePayload {
425	pub subscription_id: String,
426	pub content_type: String,
427	pub body: JsonValue,
428}
429
430#[cfg(any(feature = "http", feature = "ws"))]
431#[derive(Debug, Clone, Serialize, Deserialize)]
432pub struct WireBatchChangePayload {
433	pub batch_id: String,
434	pub entries: Vec<WireBatchChangeEntry>,
435}
436
437#[cfg(any(feature = "http", feature = "ws"))]
438#[derive(Debug, Clone, Serialize, Deserialize)]
439pub struct WireBatchChangeEntry {
440	pub subscription_id: String,
441	pub content_type: String,
442	pub body: JsonValue,
443}
444
445#[cfg_attr(any(feature = "http", feature = "ws"), derive(Serialize, Deserialize))]
446#[derive(Debug, Clone, Copy, PartialEq, Eq)]
447pub enum ChangeKind {
448	Insert,
449	Update,
450	Remove,
451}
452
453#[cfg(any(feature = "http", feature = "ws"))]
454#[derive(Debug, Clone, Serialize, Deserialize)]
455pub struct ChangePayload {
456	pub subscription_id: String,
457	pub kind: ChangeKind,
458	pub content_type: String,
459	pub body: JsonValue,
460	#[serde(skip, default)]
461	pub frames: Option<Vec<Frame>>,
462}
463
464#[cfg(any(feature = "http", feature = "ws"))]
465#[derive(Debug, Clone, Serialize, Deserialize)]
466pub struct BatchChangePayload {
467	pub batch_id: String,
468	pub entries: Vec<BatchChangeEntry>,
469}
470
471#[cfg(any(feature = "http", feature = "ws"))]
472#[derive(Debug, Clone, Serialize, Deserialize)]
473pub struct BatchChangeEntry {
474	pub subscription_id: String,
475	pub kind: ChangeKind,
476	pub content_type: String,
477	pub body: JsonValue,
478	#[serde(skip, default)]
479	pub frames: Option<Vec<Frame>>,
480	#[serde(skip, default)]
481	pub decode_error: Option<String>,
482}
483
484#[cfg(any(feature = "http", feature = "ws"))]
485#[derive(Debug, Clone, Serialize, Deserialize)]
486pub struct BatchMemberClosedPayload {
487	pub batch_id: String,
488	pub subscription_id: String,
489}
490
491#[cfg(any(feature = "http", feature = "ws"))]
492#[derive(Debug, Clone, Serialize, Deserialize)]
493pub struct BatchClosedPayload {
494	pub batch_id: String,
495}