bonsaidb_core/keyvalue.rs
1use arc_bytes::serde::Bytes;
2use serde::{Deserialize, Serialize};
3
4mod timestamp;
5
6pub use self::timestamp::Timestamp;
7use crate::Error;
8
9mod implementation {
10 use arc_bytes::serde::Bytes;
11 use async_trait::async_trait;
12 use futures::future::BoxFuture;
13 use serde::Serialize;
14
15 use crate::keyvalue::{Command, KeyCheck, KeyOperation, KeyStatus, Output, Timestamp};
16 use crate::Error;
17
18 /// Types for executing get operations.
19 pub mod get;
20 /// Types for executing increment/decrement operations.
21 pub mod increment;
22 /// Types for handling key namespaces.
23 pub mod namespaced;
24 /// Types for executing set operations.
25 pub mod set;
26
27 use namespaced::Namespaced;
28
29 use super::{IncompatibleTypeError, Numeric, Value};
30 /// Key-Value store methods. The Key-Value store is designed to be a
31 /// high-performance, lightweight storage mechanism.
32 ///
33 /// When compared to Collections, the Key-Value store does not offer
34 /// ACID-compliant transactions. Instead, the Key-Value store is made more
35 /// efficient by periodically flushing the store to disk rather than during
36 /// each operation. As such, the Key-Value store is intended to be used as a
37 /// lightweight caching layer. However, because each of the operations it
38 /// supports are executed atomically, the Key-Value store can also be
39 /// utilized for synchronized locking.
40 ///
41 /// ## Floating Point Operations
42 ///
43 /// When using [`KeyValue::set_numeric_key()`] or any numeric operations, if
44 /// a [Not a Number (NaN) value][nan] is encountered, [`Error::NotANumber`]
45 /// will be returned without allowing the operation to succeed.
46 ///
47 /// Positive and negative infinity values are allowed, as they do not break
48 /// comparison operations.
49 ///
50 /// [nan]: https://en.wikipedia.org/wiki/NaN
51 pub trait KeyValue: Sized + Send + Sync {
52 /// Executes a single [`KeyOperation`].
53 fn execute_key_operation(&self, op: KeyOperation) -> Result<Output, Error>;
54
55 /// Sets `key` to `value`. This function returns a builder that is also a
56 /// Future. Awaiting the builder will execute [`Command::Set`] with the options
57 /// given.
58 fn set_key<'a, S: Into<String>, V: Serialize + Send + Sync>(
59 &'a self,
60 key: S,
61 value: &'a V,
62 ) -> set::Builder<'a, Self, V> {
63 set::Builder::new(
64 self,
65 self.key_namespace().map(Into::into),
66 key.into(),
67 PendingValue::Serializeable(value),
68 )
69 }
70
71 /// Sets `key` to `bytes`. This function returns a builder that is also
72 /// a Future. Awaiting the builder will execute [`Command::Set`] with
73 /// the options given.
74 fn set_binary_key<'a, S: Into<String>>(
75 &'a self,
76 key: S,
77 bytes: &'a [u8],
78 ) -> set::Builder<'a, Self, ()> {
79 set::Builder::new(
80 self,
81 self.key_namespace().map(Into::into),
82 key.into(),
83 PendingValue::Bytes(bytes),
84 )
85 }
86
87 /// Sets `key` to `value`. This stores the value as a `Numeric`,
88 /// enabling atomic math operations to be performed on this key. This
89 /// function returns a builder that is also a Future. Awaiting the
90 /// builder will execute [`Command::Set`] with the options given.
91 fn set_numeric_key<S: Into<String>, V: Into<Numeric>>(
92 &self,
93 key: S,
94 value: V,
95 ) -> set::Builder<'_, Self, ()> {
96 set::Builder::new(
97 self,
98 self.key_namespace().map(Into::into),
99 key.into(),
100 PendingValue::Numeric(value.into()),
101 )
102 }
103
104 /// Increments `key` by `value`. The value stored must be a `Numeric`,
105 /// otherwise an error will be returned. The result of the increment
106 /// will be the `value`'s type. For example, if the stored value is
107 /// currently a `u64`, but `value` is a `f64`, the current value will be
108 /// converted to an `f64`, and the stored value will be an `f64`.
109 fn increment_key_by<
110 S: Into<String> + Send + Sync,
111 V: Into<Numeric> + TryFrom<Numeric, Error = IncompatibleTypeError> + Send + Sync,
112 >(
113 &self,
114 key: S,
115 value: V,
116 ) -> increment::Builder<'_, Self, V> {
117 increment::Builder::new(
118 self,
119 self.key_namespace().map(Into::into),
120 true,
121 key.into(),
122 value.into(),
123 )
124 }
125
126 /// Decrements `key` by `value`. The value stored must be a `Numeric`,
127 /// otherwise an error will be returned. The result of the decrement
128 /// will be the `value`'s type. For example, if the stored value is
129 /// currently a `u64`, but `value` is a `f64`, the current value will be
130 /// converted to an `f64`, and the stored value will be an `f64`.
131 fn decrement_key_by<
132 S: Into<String> + Send + Sync,
133 V: Into<Numeric> + TryFrom<Numeric, Error = IncompatibleTypeError> + Send + Sync,
134 >(
135 &self,
136 key: S,
137 value: V,
138 ) -> increment::Builder<'_, Self, V> {
139 increment::Builder::new(
140 self,
141 self.key_namespace().map(Into::into),
142 false,
143 key.into(),
144 value.into(),
145 )
146 }
147
148 /// Gets the value stored at `key`. This function returns a builder that is also a
149 /// Future. Awaiting the builder will execute [`Command::Get`] with the options
150 /// given.
151 fn get_key<S: Into<String>>(&'_ self, key: S) -> get::Builder<'_, Self> {
152 get::Builder::new(self, self.key_namespace().map(Into::into), key.into())
153 }
154
155 /// Deletes the value stored at `key`.
156 fn delete_key<S: Into<String> + Send>(&'_ self, key: S) -> Result<KeyStatus, Error> {
157 match self.execute_key_operation(KeyOperation {
158 namespace: self.key_namespace().map(ToOwned::to_owned),
159 key: key.into(),
160 command: Command::Delete,
161 })? {
162 Output::Status(status) => Ok(status),
163 Output::Value(_) => unreachable!("invalid output from delete operation"),
164 }
165 }
166
167 /// The current namespace.
168 fn key_namespace(&self) -> Option<&'_ str> {
169 None
170 }
171
172 /// Access this Key-Value store within a namespace. When using the returned
173 /// [`Namespaced`] instance, all keys specified will be separated into their
174 /// own storage designated by `namespace`.
175 fn with_key_namespace(&'_ self, namespace: &str) -> Namespaced<'_, Self> {
176 Namespaced::new(namespace.to_string(), self)
177 }
178 }
179
180 /// Key-Value store methods. The Key-Value store is designed to be a
181 /// high-performance, lightweight storage mechanism.
182 ///
183 /// When compared to Collections, the Key-Value store does not offer
184 /// ACID-compliant transactions. Instead, the Key-Value store is made more
185 /// efficient by periodically flushing the store to disk rather than during
186 /// each operation. As such, the Key-Value store is intended to be used as a
187 /// lightweight caching layer. However, because each of the operations it
188 /// supports are executed atomically, the Key-Value store can also be
189 /// utilized for synchronized locking.
190 ///
191 /// ## Floating Point Operations
192 ///
193 /// When using [`KeyValue::set_numeric_key()`] or any numeric operations, if
194 /// a [Not a Number (NaN) value][nan] is encountered, [`Error::NotANumber`]
195 /// will be returned without allowing the operation to succeed.
196 ///
197 /// Positive and negative infinity values are allowed, as they do not break
198 /// comparison operations.
199 ///
200 /// [nan]: https://en.wikipedia.org/wiki/NaN
201 #[async_trait]
202 pub trait AsyncKeyValue: Sized + Send + Sync {
203 /// Executes a single [`KeyOperation`].
204 async fn execute_key_operation(&self, op: KeyOperation) -> Result<Output, Error>;
205
206 /// Sets `key` to `value`. This function returns a builder that is also a
207 /// Future. Awaiting the builder will execute [`Command::Set`] with the options
208 /// given.
209 fn set_key<'a, S: Into<String>, V: Serialize + Send + Sync>(
210 &'a self,
211 key: S,
212 value: &'a V,
213 ) -> set::AsyncBuilder<'a, Self, V> {
214 set::AsyncBuilder::new(
215 self,
216 self.key_namespace().map(Into::into),
217 key.into(),
218 PendingValue::Serializeable(value),
219 )
220 }
221
222 /// Sets `key` to `bytes`. This function returns a builder that is also
223 /// a Future. Awaiting the builder will execute [`Command::Set`] with
224 /// the options given.
225 fn set_binary_key<'a, S: Into<String>>(
226 &'a self,
227 key: S,
228 bytes: &'a [u8],
229 ) -> set::AsyncBuilder<'a, Self, ()> {
230 set::AsyncBuilder::new(
231 self,
232 self.key_namespace().map(Into::into),
233 key.into(),
234 PendingValue::Bytes(bytes),
235 )
236 }
237
238 /// Sets `key` to `value`. This stores the value as a `Numeric`,
239 /// enabling atomic math operations to be performed on this key. This
240 /// function returns a builder that is also a Future. Awaiting the
241 /// builder will execute [`Command::Set`] with the options given.
242 fn set_numeric_key<S: Into<String>, V: Into<Numeric>>(
243 &self,
244 key: S,
245 value: V,
246 ) -> set::AsyncBuilder<'_, Self, ()> {
247 set::AsyncBuilder::new(
248 self,
249 self.key_namespace().map(Into::into),
250 key.into(),
251 PendingValue::Numeric(value.into()),
252 )
253 }
254
255 /// Increments `key` by `value`. The value stored must be a `Numeric`,
256 /// otherwise an error will be returned. The result of the increment
257 /// will be the `value`'s type. For example, if the stored value is
258 /// currently a `u64`, but `value` is a `f64`, the current value will be
259 /// converted to an `f64`, and the stored value will be an `f64`.
260 fn increment_key_by<
261 S: Into<String> + Send + Sync,
262 V: Into<Numeric> + TryFrom<Numeric, Error = IncompatibleTypeError> + Send + Sync,
263 >(
264 &self,
265 key: S,
266 value: V,
267 ) -> increment::AsyncBuilder<'_, Self, V> {
268 increment::AsyncBuilder::new(
269 self,
270 self.key_namespace().map(Into::into),
271 true,
272 key.into(),
273 value.into(),
274 )
275 }
276
277 /// Decrements `key` by `value`. The value stored must be a `Numeric`,
278 /// otherwise an error will be returned. The result of the decrement
279 /// will be the `value`'s type. For example, if the stored value is
280 /// currently a `u64`, but `value` is a `f64`, the current value will be
281 /// converted to an `f64`, and the stored value will be an `f64`.
282 fn decrement_key_by<
283 S: Into<String> + Send + Sync,
284 V: Into<Numeric> + TryFrom<Numeric, Error = IncompatibleTypeError> + Send + Sync,
285 >(
286 &self,
287 key: S,
288 value: V,
289 ) -> increment::AsyncBuilder<'_, Self, V> {
290 increment::AsyncBuilder::new(
291 self,
292 self.key_namespace().map(Into::into),
293 false,
294 key.into(),
295 value.into(),
296 )
297 }
298
299 /// Gets the value stored at `key`. This function returns a builder that is also a
300 /// Future. Awaiting the builder will execute [`Command::Get`] with the options
301 /// given.
302 fn get_key<S: Into<String>>(&'_ self, key: S) -> get::AsyncBuilder<'_, Self> {
303 get::AsyncBuilder::new(self, self.key_namespace().map(Into::into), key.into())
304 }
305
306 /// Deletes the value stored at `key`.
307 async fn delete_key<S: Into<String> + Send>(&'_ self, key: S) -> Result<KeyStatus, Error> {
308 match self
309 .execute_key_operation(KeyOperation {
310 namespace: self.key_namespace().map(ToOwned::to_owned),
311 key: key.into(),
312 command: Command::Delete,
313 })
314 .await?
315 {
316 Output::Status(status) => Ok(status),
317 Output::Value(_) => unreachable!("invalid output from delete operation"),
318 }
319 }
320
321 /// The current namespace.
322 fn key_namespace(&self) -> Option<&'_ str> {
323 None
324 }
325
326 /// Access this Key-Value store within a namespace. When using the returned
327 /// [`Namespaced`] instance, all keys specified will be separated into their
328 /// own storage designated by `namespace`.
329 fn with_key_namespace(&'_ self, namespace: &str) -> Namespaced<'_, Self> {
330 Namespaced::new(namespace.to_string(), self)
331 }
332 }
333
334 enum BuilderState<'a, T, V> {
335 Pending(Option<T>),
336 Executing(BoxFuture<'a, V>),
337 }
338
339 #[allow(clippy::redundant_pub_crate)]
340 pub(crate) enum PendingValue<'a, V> {
341 Bytes(&'a [u8]),
342 Serializeable(&'a V),
343 Numeric(Numeric),
344 }
345
346 impl<'a, V> PendingValue<'a, V>
347 where
348 V: Serialize,
349 {
350 fn prepare(self) -> Result<Value, Error> {
351 match self {
352 Self::Bytes(bytes) => Ok(Value::Bytes(Bytes::from(bytes))),
353 Self::Serializeable(value) => Ok(Value::Bytes(Bytes::from(pot::to_vec(value)?))),
354 Self::Numeric(numeric) => Ok(Value::Numeric(numeric)),
355 }
356 }
357 }
358}
359
360pub use implementation::*;
361
362/// Checks for existing keys.
363#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
364pub enum KeyCheck {
365 /// Only allow the operation if an existing key is present.
366 OnlyIfPresent,
367 /// Only allow the opeartion if the key isn't present.
368 OnlyIfVacant,
369}
370
371#[derive(Clone, Serialize, Deserialize, Debug)]
372/// An operation performed on a key.
373pub struct KeyOperation {
374 /// The namespace for the key.
375 pub namespace: Option<String>,
376 /// The key to operate on.
377 pub key: String,
378 /// The command to execute.
379 pub command: Command,
380}
381
382/// Commands for a key-value store.
383#[derive(Clone, Serialize, Deserialize, Debug)]
384pub enum Command {
385 /// Set a key/value pair.
386 Set(SetCommand),
387 /// Get the value from a key.
388 Get {
389 /// Remove the key after retrieving the value.
390 delete: bool,
391 },
392 /// Increment a numeric key. Returns an error if the key cannot be
393 /// deserialized to the same numeric type as `amount`. If `saturating` is
394 /// true, overflows will be prevented and the value will remain within the
395 /// numeric bounds.
396 Increment {
397 /// The amount to increment by.
398 amount: Numeric,
399 /// If true, the result will be constrained to the numerical bounds of
400 /// the type of `amount`.
401 saturating: bool,
402 },
403 /// Decrement a numeric key. Returns an error if the key cannot be
404 /// deserialized to the same numeric type as `amount`. If `saturating` is
405 /// true, overflows will be prevented and the value will remain within the
406 /// numeric bounds.
407 Decrement {
408 /// The amount to increment by.
409 amount: Numeric,
410 /// If true, the result will be constrained to the numerical bounds of
411 /// the type of `amount`.
412 saturating: bool,
413 },
414 /// Delete a key.
415 Delete,
416}
417
418/// Set a key/value pair.
419#[derive(Clone, Serialize, Deserialize, Debug)]
420pub struct SetCommand {
421 /// The value.
422 pub value: Value,
423 /// If set, the key will be set to expire automatically.
424 pub expiration: Option<Timestamp>,
425 /// If true and the key already exists, the expiration will not be
426 /// updated. If false and an expiration is provided, the expiration will
427 /// be set.
428 pub keep_existing_expiration: bool,
429 /// Conditional checks for whether the key is already present or not.
430 pub check: Option<KeyCheck>,
431 /// If true and the key already exists, the existing key will be returned if overwritten.
432 pub return_previous_value: bool,
433}
434
435/// A value stored in a key.
436#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
437pub enum Value {
438 /// A value stored as a byte array.
439 Bytes(Bytes),
440 /// A numeric value.
441 Numeric(Numeric),
442}
443
444impl Value {
445 /// Validates this value to ensure it is safe to store.
446 pub fn validate(self) -> Result<Self, Error> {
447 match self {
448 Self::Numeric(numeric) => numeric.validate().map(Self::Numeric),
449 Self::Bytes(vec) => Ok(Self::Bytes(vec)),
450 }
451 }
452
453 /// Deserializes the bytes contained inside of this value. Returns an error
454 /// if this value doesn't contain bytes.
455 pub fn deserialize<V: for<'de> Deserialize<'de>>(&self) -> Result<V, Error> {
456 match self {
457 Self::Bytes(bytes) => Ok(pot::from_slice(bytes)?),
458 Self::Numeric(_) => Err(Error::other(
459 "key-value",
460 "key contains numeric value, not serialized data",
461 )),
462 }
463 }
464
465 /// Returns this value as an `i64`, allowing for precision to be lost if the type was not an `i64` originally. If saturating is true, the conversion will not allow overflows. Returns None if the value is bytes.
466 #[must_use]
467 pub fn as_i64_lossy(&self, saturating: bool) -> Option<i64> {
468 match self {
469 Self::Bytes(_) => None,
470 Self::Numeric(value) => Some(value.as_i64_lossy(saturating)),
471 }
472 }
473
474 /// Returns this value as an `u64`, allowing for precision to be lost if the type was not an `u64` originally. If saturating is true, the conversion will not allow overflows. Returns None if the value is bytes.
475 #[must_use]
476 pub fn as_u64_lossy(&self, saturating: bool) -> Option<u64> {
477 match self {
478 Self::Bytes(_) => None,
479 Self::Numeric(value) => Some(value.as_u64_lossy(saturating)),
480 }
481 }
482
483 /// Returns this value as an `f64`, allowing for precision to be lost if the type was not an `f64` originally. Returns None if the value is bytes.
484 #[must_use]
485 pub const fn as_f64_lossy(&self) -> Option<f64> {
486 match self {
487 Self::Bytes(_) => None,
488 Self::Numeric(value) => Some(value.as_f64_lossy()),
489 }
490 }
491
492 /// Returns this numeric as an `i64`, allowing for precision to be lost if the type was not an `i64` originally. Returns None if the value is bytes.
493 #[must_use]
494 pub fn as_i64(&self) -> Option<i64> {
495 match self {
496 Self::Bytes(_) => None,
497 Self::Numeric(value) => value.as_i64(),
498 }
499 }
500
501 /// Returns this numeric as an `u64`, allowing for precision to be lost if the type was not an `u64` originally. Returns None if the value is bytes.
502 #[must_use]
503 pub fn as_u64(&self) -> Option<u64> {
504 match self {
505 Self::Bytes(_) => None,
506 Self::Numeric(value) => value.as_u64(),
507 }
508 }
509
510 /// Returns this numeric as an `f64`, allowing for precision to be lost if the type was not an `f64` originally. Returns None if the value is bytes.
511 #[must_use]
512 pub const fn as_f64(&self) -> Option<f64> {
513 match self {
514 Self::Bytes(_) => None,
515 Self::Numeric(value) => value.as_f64(),
516 }
517 }
518}
519
520/// A numerical value.
521#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
522pub enum Numeric {
523 /// A 64-bit signed integer.
524 Integer(i64),
525 /// A 64-bit unsigned integer.
526 UnsignedInteger(u64),
527 /// A 64-bit floating point number.
528 Float(f64),
529}
530
531impl Numeric {
532 /// Ensures this value contains a valid value.
533 ///
534 /// # Errors
535 ///
536 /// [`Error::NotANumber`] is returned if this contains a NaN floating point
537 /// value.
538 pub fn validate(self) -> Result<Self, Error> {
539 if let Self::Float(float) = self {
540 if float.is_nan() {
541 return Err(Error::NotANumber);
542 }
543 }
544
545 Ok(self)
546 }
547
548 /// Returns this numeric as an `i64`. If this conversion cannot be done
549 /// without losing precision or overflowing, None will be returned.
550 #[must_use]
551 #[allow(clippy::cast_possible_truncation)]
552 pub fn as_i64(&self) -> Option<i64> {
553 match self {
554 Self::Integer(value) => Some(*value),
555 Self::UnsignedInteger(value) => (*value).try_into().ok(),
556 Self::Float(value) => {
557 if value.fract().abs() > 0. {
558 None
559 } else {
560 Some(*value as i64)
561 }
562 }
563 }
564 }
565
566 /// Returns this numeric as an `i64`, allowing for precision to be lost if
567 /// the type was not an `i64` originally. If saturating is true, the
568 /// conversion will not allow overflows.
569 #[must_use]
570 #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
571 pub fn as_i64_lossy(&self, saturating: bool) -> i64 {
572 match self {
573 Self::Integer(value) => *value,
574 Self::UnsignedInteger(value) => {
575 if saturating {
576 (*value).try_into().unwrap_or(i64::MAX)
577 } else {
578 *value as i64
579 }
580 }
581 Self::Float(value) => *value as i64,
582 }
583 }
584
585 /// Returns this numeric as an `u64`. If this conversion cannot be done
586 /// without losing precision or overflowing, None will be returned.
587 #[must_use]
588 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
589 pub fn as_u64(&self) -> Option<u64> {
590 match self {
591 Self::UnsignedInteger(value) => Some(*value),
592 Self::Integer(value) => (*value).try_into().ok(),
593 Self::Float(value) => {
594 if value.fract() < f64::EPSILON && value.is_sign_positive() {
595 Some(*value as u64)
596 } else {
597 None
598 }
599 }
600 }
601 }
602
603 /// Returns this numeric as an `u64`, allowing for precision to be lost if
604 /// the type was not an `i64` originally. If saturating is true, the
605 /// conversion will not allow overflows.
606 #[must_use]
607 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
608 pub fn as_u64_lossy(&self, saturating: bool) -> u64 {
609 match self {
610 Self::UnsignedInteger(value) => *value,
611 Self::Integer(value) => {
612 if saturating {
613 (*value).try_into().unwrap_or(0)
614 } else {
615 *value as u64
616 }
617 }
618 Self::Float(value) => *value as u64,
619 }
620 }
621
622 /// Returns this numeric as an `f64`. If this conversion cannot be done
623 /// without losing precision, None will be returned.
624 #[must_use]
625 #[allow(clippy::cast_precision_loss)]
626 pub const fn as_f64(&self) -> Option<f64> {
627 match self {
628 Self::UnsignedInteger(value) => {
629 if *value > 2_u64.pow(f64::MANTISSA_DIGITS) {
630 None
631 } else {
632 Some(*value as f64)
633 }
634 }
635 Self::Integer(value) => {
636 if *value > 2_i64.pow(f64::MANTISSA_DIGITS)
637 || *value < -(2_i64.pow(f64::MANTISSA_DIGITS))
638 {
639 None
640 } else {
641 Some(*value as f64)
642 }
643 }
644 Self::Float(value) => Some(*value),
645 }
646 }
647
648 /// Returns this numeric as an `f64`, allowing for precision to be lost if
649 /// the type was not an `f64` originally.
650 #[must_use]
651 #[allow(clippy::cast_precision_loss)]
652 pub const fn as_f64_lossy(&self) -> f64 {
653 match self {
654 Self::UnsignedInteger(value) => *value as f64,
655 Self::Integer(value) => *value as f64,
656 Self::Float(value) => *value,
657 }
658 }
659}
660
661/// A conversion between numeric types wasn't supported.
662#[derive(thiserror::Error, Debug)]
663#[error("incompatible numeric type")]
664pub struct IncompatibleTypeError;
665
666impl From<f64> for Numeric {
667 fn from(value: f64) -> Self {
668 Self::Float(value)
669 }
670}
671
672impl From<i64> for Numeric {
673 fn from(value: i64) -> Self {
674 Self::Integer(value)
675 }
676}
677
678impl From<u64> for Numeric {
679 fn from(value: u64) -> Self {
680 Self::UnsignedInteger(value)
681 }
682}
683
684#[allow(clippy::fallible_impl_from)]
685impl TryFrom<Numeric> for f64 {
686 type Error = IncompatibleTypeError;
687
688 fn try_from(value: Numeric) -> Result<Self, IncompatibleTypeError> {
689 if let Numeric::Float(value) = value {
690 Ok(value)
691 } else {
692 Err(IncompatibleTypeError)
693 }
694 }
695}
696
697#[allow(clippy::fallible_impl_from)]
698impl TryFrom<Numeric> for u64 {
699 type Error = IncompatibleTypeError;
700
701 fn try_from(value: Numeric) -> Result<Self, IncompatibleTypeError> {
702 if let Numeric::UnsignedInteger(value) = value {
703 Ok(value)
704 } else {
705 Err(IncompatibleTypeError)
706 }
707 }
708}
709
710#[allow(clippy::fallible_impl_from)]
711impl TryFrom<Numeric> for i64 {
712 type Error = IncompatibleTypeError;
713
714 fn try_from(value: Numeric) -> Result<Self, IncompatibleTypeError> {
715 if let Numeric::Integer(value) = value {
716 Ok(value)
717 } else {
718 Err(IncompatibleTypeError)
719 }
720 }
721}
722
723/// The result of a [`KeyOperation`].
724#[derive(Clone, Serialize, Deserialize, Debug)]
725pub enum Output {
726 /// A status was returned.
727 Status(KeyStatus),
728 /// A value was returned.
729 Value(Option<Value>),
730}
731/// The status of an operation on a Key.
732#[derive(Copy, Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
733pub enum KeyStatus {
734 /// A new key was inserted.
735 Inserted,
736 /// An existing key was updated with a new value.
737 Updated,
738 /// A key was deleted.
739 Deleted,
740 /// No changes were made.
741 NotChanged,
742}