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::{GrpcClient, GrpcSubscription};
39#[cfg(feature = "http")]
40pub use http::HttpClient;
41// Re-export derive macro
42pub use reifydb_client_derive::FromFrame;
43// Re-export commonly used types from reifydb-type
44pub use reifydb_type as r#type;
45pub use reifydb_type::{
46	params::Params,
47	value::{
48		Value,
49		frame::{
50			column::FrameColumn,
51			data::FrameColumnData,
52			extract::FrameError,
53			frame::Frame,
54			from_frame::FromFrameError,
55			row::{FrameRow, FrameRows},
56		},
57		ordered_f32::OrderedF32,
58		ordered_f64::OrderedF64,
59		try_from::{FromValueError, TryFromValue, TryFromValueCoerce},
60		r#type::Type,
61	},
62};
63#[cfg(any(feature = "http", feature = "ws"))]
64use serde::{Deserialize, Serialize};
65#[cfg(any(feature = "http", feature = "ws"))]
66use serde_json::Value as JsonValue;
67#[cfg(feature = "ws")]
68pub use ws::WsClient;
69
70/// Server-reported metadata about a single executed request.
71#[cfg_attr(any(feature = "http", feature = "ws"), derive(Serialize, Deserialize))]
72#[derive(Debug, Clone)]
73pub struct ResponseMeta {
74	pub fingerprint: String,
75	pub duration: String,
76}
77
78/// Result type for admin operations
79#[derive(Debug)]
80pub struct AdminResult {
81	pub frames: Vec<Frame>,
82	pub meta: Option<ResponseMeta>,
83}
84
85/// Result type for command operations
86#[derive(Debug)]
87pub struct CommandResult {
88	pub frames: Vec<Frame>,
89	pub meta: Option<ResponseMeta>,
90}
91
92/// Result type for query operations
93#[derive(Debug)]
94pub struct QueryResult {
95	pub frames: Vec<Frame>,
96	pub meta: Option<ResponseMeta>,
97}
98
99/// Result type for authentication login operations
100#[derive(Debug, Clone)]
101pub struct LoginResult {
102	/// Session token for subsequent requests
103	pub token: String,
104	/// Identity UUID of the authenticated user
105	pub identity: String,
106}
107
108#[cfg(any(feature = "http", feature = "ws"))]
109/// Wire format for a single typed value: `{"type": "Int2", "value": "1234"}`.
110#[derive(Debug, Serialize, Deserialize)]
111pub struct WireValue {
112	#[serde(rename = "type")]
113	pub type_name: String,
114	pub value: String,
115}
116
117#[cfg(any(feature = "http", feature = "ws"))]
118/// Wire format for query parameters.
119///
120/// Either positional or named:
121/// - Positional: `[{"type":"Int2","value":"1234"}, ...]`
122/// - Named: `{"key": {"type":"Int2","value":"1234"}, ...}`
123#[derive(Debug, Serialize, Deserialize)]
124#[serde(untagged)]
125pub enum WireParams {
126	Positional(Vec<WireValue>),
127	Named(HashMap<String, WireValue>),
128}
129
130#[cfg(any(feature = "http", feature = "ws"))]
131fn value_to_wire(value: Value) -> WireValue {
132	let (type_name, value_str): (&str, String) = match &value {
133		Value::None {
134			..
135		} => ("None", "\u{27EA}none\u{27EB}".to_string()),
136		Value::Boolean(b) => ("Boolean", b.to_string()),
137		Value::Float4(f) => ("Float4", f.to_string()),
138		Value::Float8(f) => ("Float8", f.to_string()),
139		Value::Int1(i) => ("Int1", i.to_string()),
140		Value::Int2(i) => ("Int2", i.to_string()),
141		Value::Int4(i) => ("Int4", i.to_string()),
142		Value::Int8(i) => ("Int8", i.to_string()),
143		Value::Int16(i) => ("Int16", i.to_string()),
144		Value::Utf8(s) => ("Utf8", s.clone()),
145		Value::Uint1(u) => ("Uint1", u.to_string()),
146		Value::Uint2(u) => ("Uint2", u.to_string()),
147		Value::Uint4(u) => ("Uint4", u.to_string()),
148		Value::Uint8(u) => ("Uint8", u.to_string()),
149		Value::Uint16(u) => ("Uint16", u.to_string()),
150		Value::Uuid4(u) => ("Uuid4", u.to_string()),
151		Value::Uuid7(u) => ("Uuid7", u.to_string()),
152		Value::Date(d) => ("Date", d.to_string()),
153		Value::DateTime(dt) => ("DateTime", dt.to_string()),
154		Value::Time(t) => ("Time", t.to_string()),
155		Value::Duration(d) => ("Duration", d.to_iso_string()),
156		Value::Blob(b) => ("Blob", b.to_hex()),
157		Value::IdentityId(id) => ("IdentityId", id.to_string()),
158		Value::Int(i) => ("Int", i.to_string()),
159		Value::Uint(u) => ("Uint", u.to_string()),
160		Value::Decimal(d) => ("Decimal", d.to_string()),
161		Value::Any(v) => return value_to_wire(*v.clone()),
162		Value::DictionaryId(id) => ("DictionaryId", id.to_string()),
163		Value::Type(t) => ("Type", t.to_string()),
164		Value::List(items) => ("List", format!("{}", Value::List(items.clone()))),
165		Value::Record(fields) => ("Record", format!("{}", Value::Record(fields.clone()))),
166		Value::Tuple(items) => ("Tuple", format!("{}", Value::Tuple(items.clone()))),
167	};
168	WireValue {
169		type_name: type_name.to_string(),
170		value: value_str,
171	}
172}
173
174#[cfg(any(feature = "http", feature = "ws"))]
175pub fn params_to_wire(params: Params) -> Option<WireParams> {
176	match params {
177		Params::None => None,
178		Params::Positional(values) => Some(WireParams::Positional(
179			Arc::unwrap_or_clone(values).into_iter().map(value_to_wire).collect(),
180		)),
181		Params::Named(map) => Some(WireParams::Named(
182			Arc::unwrap_or_clone(map).into_iter().map(|(k, v)| (k, value_to_wire(v))).collect(),
183		)),
184	}
185}
186
187#[cfg(any(feature = "http", feature = "ws"))]
188#[derive(Debug, Serialize, Deserialize)]
189pub struct Request {
190	pub id: String,
191	#[serde(flatten)]
192	pub payload: RequestPayload,
193}
194
195#[cfg(any(feature = "http", feature = "ws"))]
196#[derive(Debug, Serialize, Deserialize)]
197#[serde(tag = "type", content = "payload")]
198pub enum RequestPayload {
199	Auth(AuthRequest),
200	Admin(AdminRequest),
201	Command(CommandRequest),
202	Query(QueryRequest),
203	Subscribe(SubscribeRequest),
204	Unsubscribe(UnsubscribeRequest),
205	Logout,
206}
207
208#[cfg(any(feature = "http", feature = "ws"))]
209#[derive(Debug, Serialize, Deserialize)]
210pub struct AdminRequest {
211	pub rql: String,
212	pub params: Option<WireParams>,
213	#[serde(skip_serializing_if = "Option::is_none")]
214	pub format: Option<String>,
215}
216
217#[cfg(any(feature = "http", feature = "ws"))]
218#[derive(Debug, Serialize, Deserialize)]
219pub struct AuthRequest {
220	#[serde(skip_serializing_if = "Option::is_none")]
221	pub token: Option<String>,
222	#[serde(skip_serializing_if = "Option::is_none")]
223	pub method: Option<String>,
224	#[serde(skip_serializing_if = "Option::is_none")]
225	pub credentials: Option<HashMap<String, String>>,
226}
227
228#[cfg(any(feature = "http", feature = "ws"))]
229#[derive(Debug, Serialize, Deserialize)]
230pub struct CommandRequest {
231	pub rql: String,
232	pub params: Option<WireParams>,
233	#[serde(skip_serializing_if = "Option::is_none")]
234	pub format: Option<String>,
235}
236
237#[cfg(any(feature = "http", feature = "ws"))]
238#[derive(Debug, Serialize, Deserialize)]
239pub struct QueryRequest {
240	pub rql: String,
241	pub params: Option<WireParams>,
242	#[serde(skip_serializing_if = "Option::is_none")]
243	pub format: Option<String>,
244}
245
246#[cfg(any(feature = "http", feature = "ws"))]
247#[derive(Debug, Serialize, Deserialize)]
248pub struct SubscribeRequest {
249	pub rql: String,
250	#[serde(skip_serializing_if = "Option::is_none")]
251	pub format: Option<String>,
252}
253
254#[cfg(any(feature = "http", feature = "ws"))]
255#[derive(Debug, Serialize, Deserialize)]
256pub struct UnsubscribeRequest {
257	pub subscription_id: String,
258}
259
260#[cfg(any(feature = "http", feature = "ws"))]
261#[derive(Debug, Serialize, Deserialize)]
262pub struct Response {
263	pub id: String,
264	#[serde(flatten)]
265	pub payload: ResponsePayload,
266}
267
268#[cfg(any(feature = "http", feature = "ws"))]
269#[derive(Debug, Serialize, Deserialize)]
270#[serde(tag = "type", content = "payload")]
271pub enum ResponsePayload {
272	Auth(AuthResponse),
273	Err(ErrResponse),
274	Admin(AdminResponse),
275	Command(CommandResponse),
276	Query(QueryResponse),
277	Subscribed(SubscribedResponse),
278	Unsubscribed(UnsubscribedResponse),
279	Logout(LogoutResponsePayload),
280}
281
282#[cfg(any(feature = "http", feature = "ws"))]
283#[derive(Debug, Serialize, Deserialize)]
284pub struct AdminResponse {
285	pub content_type: String,
286	pub body: JsonValue,
287	#[serde(default)]
288	pub meta: Option<ResponseMeta>,
289}
290
291#[cfg(any(feature = "http", feature = "ws"))]
292use reifydb_type::error::Diagnostic;
293
294#[cfg(any(feature = "http", feature = "ws"))]
295#[derive(Debug, Serialize, Deserialize)]
296pub struct AuthResponse {
297	#[serde(skip_serializing_if = "Option::is_none")]
298	pub status: Option<String>,
299	#[serde(skip_serializing_if = "Option::is_none")]
300	pub token: Option<String>,
301	#[serde(skip_serializing_if = "Option::is_none")]
302	pub identity: Option<String>,
303}
304
305#[cfg(any(feature = "http", feature = "ws"))]
306#[derive(Debug, Serialize, Deserialize)]
307pub struct ErrResponse {
308	pub diagnostic: Diagnostic,
309}
310
311#[cfg(any(feature = "http", feature = "ws"))]
312#[derive(Debug, Serialize, Deserialize)]
313pub struct CommandResponse {
314	pub content_type: String,
315	pub body: JsonValue,
316	#[serde(default)]
317	pub meta: Option<ResponseMeta>,
318}
319
320#[cfg(any(feature = "http", feature = "ws"))]
321#[derive(Debug, Serialize, Deserialize)]
322pub struct QueryResponse {
323	pub content_type: String,
324	pub body: JsonValue,
325	#[serde(default)]
326	pub meta: Option<ResponseMeta>,
327}
328
329#[cfg(any(feature = "http", feature = "ws"))]
330#[derive(Debug, Serialize, Deserialize)]
331pub struct SubscribedResponse {
332	pub subscription_id: String,
333}
334
335#[cfg(any(feature = "http", feature = "ws"))]
336#[derive(Debug, Serialize, Deserialize)]
337pub struct UnsubscribedResponse {
338	pub subscription_id: String,
339}
340
341#[cfg(any(feature = "http", feature = "ws"))]
342#[derive(Debug, Serialize, Deserialize)]
343pub struct LogoutResponsePayload {
344	pub status: String,
345}
346
347#[cfg(any(feature = "http", feature = "ws"))]
348/// Server-initiated push message (no request id).
349#[derive(Debug, Serialize, Deserialize)]
350#[serde(tag = "type", content = "payload")]
351pub enum ServerPush {
352	Change(ChangePayload),
353}
354
355#[cfg(any(feature = "http", feature = "ws"))]
356/// Payload for subscription change notifications.
357///
358/// For JSON pushes, `body` holds the JSON frames body and `frames` is `None`.
359/// For RBCF pushes, the client decodes the binary envelope and populates
360/// `frames` directly; `body` is empty and `content_type` is `application/vnd.reifydb.rbcf`.
361#[derive(Debug, Clone, Serialize, Deserialize)]
362pub struct ChangePayload {
363	pub subscription_id: String,
364	pub content_type: String,
365	pub body: JsonValue,
366	#[serde(skip, default)]
367	pub frames: Option<Vec<Frame>>,
368}