tiny_orm_model/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
use sqlx::decode::Decode;
use sqlx::encode::IsNull;
use sqlx::error::BoxDynError;
use sqlx::{Database, Encode, Type, ValueRef};

/// tiny_orm::SetOption is an enum that behave similarly to `Option` in the sense that there are only two variants.
/// The goal is to easily differentiate between an Option type and a SetOption type.
/// So that it is possible to have a struct like the following
/// ```rust
/// # use tiny_orm_model::SetOption;
///
/// struct Todo {
///     id: SetOption<i64>,
///     description: SetOption<Option<String>>,
/// }
/// ```
///
/// When the variant will be `SetOption::NotSet`, then tiny ORM will automatically skip the field during "write" operations
/// like `create()` or `update()`.
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SetOption<T> {
    Set(T),
    #[default]
    NotSet,
}

/// Implement `From` for `SetOption` to allow for easy conversion from a value to a `SetOption`.
/// ```rust
/// # use tiny_orm_model::SetOption;
/// let set_option: SetOption<i32> = 1.into();
/// assert_eq!(set_option, SetOption::Set(1));
/// ```
impl<T> From<T> for SetOption<T> {
    fn from(value: T) -> Self {
        SetOption::Set(value)
    }
}

/// Implement `From` for `Result` to allow for easy conversion from a `SetOption` to a `Result`.
/// This is useful when you want to handle the `NotSet` variant as an error case.
///
/// # Examples
/// ```rust
/// # use tiny_orm_model::SetOption;
/// let set_option: SetOption<i32> = 1.into();
/// let result: Result<i32, _> = set_option.into();
/// assert_eq!(result, Ok(1));
/// ```
/// ```rust
/// # use tiny_orm_model::SetOption;
/// let not_set: SetOption<i32> = SetOption::NotSet;
/// let result: Result<i32, _> = not_set.into();
/// assert_eq!(result, Err("Cannot convert NotSet variant to value"));
/// ```
impl<T> From<SetOption<T>> for Result<T, &'static str> {
    fn from(value: SetOption<T>) -> Self {
        match value {
            SetOption::Set(value) => Ok(value),
            SetOption::NotSet => Err("Cannot convert NotSet variant to value"),
        }
    }
}

impl<T> SetOption<T> {
    /// `inner()` is a method to get the inner value as an Option type.
    /// This return an Option<T> type wher Some<T> is corresponds when the value
    /// was using the `Set` variant,
    ///
    /// # Examples
    /// ```rust
    /// # use tiny_orm_model::SetOption;
    /// let set = SetOption::Set(1);
    /// let inner = set.inner();
    /// assert_eq!(inner, Some(1));
    /// ```
    ///
    /// ```rust
    /// # use tiny_orm_model::SetOption;
    /// let not_set: SetOption<i32> = SetOption::NotSet;
    /// let inner = not_set.inner();
    /// assert_eq!(inner, None);
    /// ```
    pub fn inner(self) -> Option<T> {
        match self {
            SetOption::NotSet => None,
            SetOption::Set(value) => Some(value),
        }
    }

    /// `is_set()` returns true if the variant is `Set`
    ///
    /// # Examples
    /// ```rust
    /// # use tiny_orm_model::SetOption;
    /// let set = SetOption::Set(1);
    /// assert!(set.is_set());
    /// ```
    ///
    /// ```rust
    /// # use tiny_orm_model::SetOption;
    /// let not_set: SetOption<i32> = SetOption::NotSet;
    /// assert!(!not_set.is_set());
    /// ```
    pub fn is_set(&self) -> bool {
        match self {
            SetOption::Set(_) => true,
            SetOption::NotSet => false,
        }
    }

    /// `is_not_set()` returns true if the variant is `NotSet`
    ///
    /// # Examples
    /// ```rust
    /// # use tiny_orm_model::SetOption;
    /// let set = SetOption::Set(1);
    /// assert!(!set.is_not_set());
    /// ```
    ///
    /// ```rust
    /// # use tiny_orm_model::SetOption;
    /// let not_set: SetOption<i32> = SetOption::NotSet;
    /// assert!(not_set.is_not_set());
    /// ```
    pub fn is_not_set(&self) -> bool {
        match self {
            SetOption::Set(_) => false,
            SetOption::NotSet => true,
        }
    }
}

/// Implements database decoding for SetOption<T>.
/// This allows automatic conversion from database values to SetOption<T>.
///
/// # Examples
/// ```rust
/// # use sqlx::SqlitePool;
/// # use tiny_orm_model::SetOption;
/// # use sqlx::FromRow;
/// #
/// #[derive(FromRow)]
/// struct TestRecord {
///     value: SetOption<i32>,
/// }
///
/// # tokio_test::block_on(async {
/// let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
///
/// # sqlx::query(
/// #    "CREATE TABLE test (value INTEGER)"
/// # ).execute(&pool).await.unwrap();
///
/// # sqlx::query(
/// #    "INSERT INTO test (value) VALUES (?)"
/// # )
/// # .bind(42)
/// # .execute(&pool).await.unwrap();
///
/// // Test with a value
/// let record: TestRecord = sqlx::query_as("SELECT value FROM test")
///     .fetch_one(&pool)
///     .await
///     .unwrap();
///
/// assert_eq!(record.value, SetOption::Set(42));
///
/// // Test NULL value
/// # sqlx::query("DELETE FROM test").execute(&pool).await.unwrap();
/// # sqlx::query(
/// #    "INSERT INTO test (value) VALUES (?)"
/// # )
/// # .bind(None::<i32>)
/// # .execute(&pool).await.unwrap();
///
/// let record: TestRecord = sqlx::query_as("SELECT value FROM test")
///     .fetch_one(&pool)
///     .await
///     .unwrap();
///
/// assert_eq!(record.value, SetOption::NotSet);
/// # });
/// ```
impl<'r, DB, T> Decode<'r, DB> for SetOption<T>
where
    DB: Database,
    T: Decode<'r, DB>,
{
    fn decode(value: <DB as Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
        if value.is_null() {
            return Ok(SetOption::NotSet);
        }

        Ok(SetOption::Set(T::decode(value)?))
    }
}

impl<DB, T> Type<DB> for SetOption<T>
where
    DB: Database,
    T: Type<DB>,
{
    fn type_info() -> <DB as Database>::TypeInfo {
        T::type_info()
    }

    fn compatible(ty: &<DB as Database>::TypeInfo) -> bool {
        T::compatible(ty)
    }
}

/// Implements database encoding for SetOption<T>.
/// This allows automatic conversion from SetOption<T> to database values.
///
/// # Examples
/// ```rust
/// # use sqlx::SqlitePool;
/// # use tiny_orm_model::SetOption;
/// # use sqlx::FromRow;
/// #
/// #[derive(FromRow)]
/// struct TestRecord {
///     value: SetOption<i32>,
/// }
///
/// # tokio_test::block_on(async {
/// let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
///
/// // Create test table
/// sqlx::query("CREATE TABLE test (value INTEGER)")
///     .execute(&pool)
///     .await
///     .unwrap();
///
/// // Test inserting a Set value
/// let set_value = SetOption::Set(42);
/// sqlx::query("INSERT INTO test (value) VALUES (?)")
///     .bind(set_value)
///     .execute(&pool)
///     .await
///     .unwrap();
///
/// let record: TestRecord = sqlx::query_as("SELECT value FROM test")
///     .fetch_one(&pool)
///     .await
///     .unwrap();
/// assert_eq!(record.value, SetOption::Set(42));
///
/// // Test inserting a NotSet value
/// sqlx::query("DELETE FROM test").execute(&pool).await.unwrap();
/// let not_set_value: SetOption<i32> = SetOption::NotSet;
/// sqlx::query("INSERT INTO test (value) VALUES (?)")
///     .bind(not_set_value)
///     .execute(&pool)
///     .await
///     .unwrap();
///
/// let record: TestRecord = sqlx::query_as("SELECT value FROM test")
///     .fetch_one(&pool)
///     .await
///     .unwrap();
/// assert_eq!(record.value, SetOption::NotSet);
/// # });
/// ```
impl<'q, T, DB: Database> Encode<'q, DB> for SetOption<T>
where
    T: Encode<'q, DB>,
{
    fn encode(self, buf: &mut <DB as Database>::ArgumentBuffer<'q>) -> Result<IsNull, BoxDynError> {
        match self {
            SetOption::Set(value) => value.encode(buf),
            SetOption::NotSet => Ok(IsNull::Yes),
        }
    }

    fn encode_by_ref(
        &self,
        buf: &mut <DB as Database>::ArgumentBuffer<'q>,
    ) -> Result<IsNull, BoxDynError> {
        match self {
            SetOption::Set(value) => value.encode_by_ref(buf),
            SetOption::NotSet => Ok(IsNull::Yes),
        }
    }

    fn produces(&self) -> Option<DB::TypeInfo> {
        match self {
            SetOption::Set(value) => value.produces(),
            SetOption::NotSet => None,
        }
    }

    fn size_hint(&self) -> usize {
        match self {
            SetOption::Set(value) => value.size_hint(),
            SetOption::NotSet => 0,
        }
    }
}