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