• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

maxcountryman / tower-sessions / 6266137216

21 Sep 2023 07:18PM UTC coverage: 68.783% (-1.3%) from 70.109%
6266137216

push

github

maxcountryman
provide postgres store

7 of 7 new or added lines in 2 files covered. (100.0%)

130 of 189 relevant lines covered (68.78%)

1.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

63.64
/src/session.rs
1
//! A session which allows HTTP applications to associate data with visitors.
2
use std::{collections::HashMap, fmt::Display, sync::Arc};
3

4
use parking_lot::Mutex;
5
use serde::{de::DeserializeOwned, Deserialize, Serialize};
6
use serde_json::Value;
7
use time::Duration;
8
use tower_cookies::cookie::time::OffsetDateTime;
9
use uuid::Uuid;
10

11
use crate::CookieConfig;
12

13
/// Session errors.
14
#[derive(thiserror::Error, Debug)]
15
pub enum SessionError {
16
    /// A variant to map `uuid` errors.
17
    #[error("Invalid UUID: {0}")]
18
    InvalidUuid(#[from] uuid::Error),
19

20
    /// A variant to map `serde_json` errors.
21
    #[error("JSON serialization/deserialization error: {0}")]
22
    SerdeJsonError(#[from] serde_json::Error),
23
}
24

25
type SessionResult<T> = Result<T, SessionError>;
26

27
/// A session which allows HTTP applications to associate data with visitors.
28
#[derive(Debug, Clone, Default)]
29
pub struct Session {
30
    pub(crate) id: SessionId,
31
    expiration_time: Option<OffsetDateTime>,
32
    inner: Arc<Mutex<Inner>>,
33
}
34

35
impl Session {
36
    /// Create a new session with defaults.
37
    ///
38
    /// Note that an `expiration_time` of none results in a cookie with
39
    /// expiration `"Session"`.
40
    ///
41
    /// # Examples
42
    ///
43
    ///```rust
44
    /// use tower_sessions::Session;
45
    /// let session = Session::new();
46
    /// ```
47
    pub fn new() -> Self {
×
48
        let inner = Inner {
49
            data: HashMap::new(),
×
50
            modified: false,
51
            deleted: None,
52
        };
53

54
        Self {
55
            id: SessionId::default(),
×
56
            expiration_time: None,
57
            inner: Arc::new(Mutex::new(inner)),
×
58
        }
59
    }
60

61
    /// A method for setting `expiration_time` in accordance with `max_age`.
62
    ///
63
    /// # Examples
64
    ///
65
    /// ```rust
66
    /// use tower_sessions::{time::Duration, Session};
67
    /// let session = Session::new().with_max_age(Duration::minutes(5));
68
    /// ```
69
    pub fn with_max_age(mut self, max_age: Duration) -> Self {
1✔
70
        let expiration_time = OffsetDateTime::now_utc().saturating_add(max_age);
2✔
71
        self.expiration_time = Some(expiration_time);
1✔
72
        self
1✔
73
    }
74

75
    /// Inserts a `impl Serialize` value into the session.
76
    ///
77
    /// # Examples
78
    ///
79
    /// ```rust
80
    /// use tower_sessions::Session;
81
    /// let session = Session::new();
82
    /// session.insert("foo", 42).expect("Serialization error.");
83
    /// ```
84
    ///
85
    /// # Errors
86
    ///
87
    /// This method can fail when [`serde_json::to_value`] fails.
88
    pub fn insert(&self, key: &str, value: impl Serialize) -> SessionResult<()> {
1✔
89
        self.insert_value(key, serde_json::to_value(&value)?);
2✔
90
        Ok(())
1✔
91
    }
92

93
    /// Inserts a `serde_json::Value` into the session.
94
    ///
95
    /// If the key was not present in the underlying map, `None` is returned and
96
    /// `modified` is set to `true`.
97
    ///
98
    /// If the underlying map did have the key and its value is the same as the
99
    /// provided value, `None` is returned and `modified` is not set.
100
    ///
101
    /// # Examples
102
    ///
103
    /// ```rust
104
    /// use tower_sessions::Session;
105
    /// let session = Session::new();
106
    /// let value = session.insert_value("foo", serde_json::json!(42));
107
    /// assert!(value.is_none());
108
    ///
109
    /// let value = session.insert_value("foo", serde_json::json!(42));
110
    /// assert!(value.is_none());
111
    ///
112
    /// let value = session.insert_value("foo", serde_json::json!("bar"));
113
    /// assert_eq!(value, Some(serde_json::json!(42)));
114
    /// ```
115
    pub fn insert_value(&self, key: &str, value: Value) -> Option<Value> {
1✔
116
        let mut inner = self.inner.lock();
2✔
117
        if inner.data.get(key) != Some(&value) {
2✔
118
            inner.modified = true;
2✔
119
            inner.data.insert(key.to_string(), value)
1✔
120
        } else {
121
            None
×
122
        }
123
    }
124

125
    /// Gets a value from the store.
126
    ///
127
    /// # Examples
128
    ///
129
    /// ```rust
130
    /// use tower_sessions::Session;
131
    /// let session = Session::new();
132
    /// session.insert("foo", 42).unwrap();
133
    /// let value = session.get::<usize>("foo").unwrap();
134
    /// assert_eq!(value, Some(42));
135
    /// ```
136
    ///
137
    /// # Errors
138
    ///
139
    /// This method can fail when [`serde_json::from_value`] fails.
140
    pub fn get<T: DeserializeOwned>(&self, key: &str) -> SessionResult<Option<T>> {
1✔
141
        Ok(self
1✔
142
            .get_value(key)
×
143
            .map(serde_json::from_value)
×
144
            .transpose()?)
×
145
    }
146

147
    /// Gets a `serde_json::Value` from the store.
148
    ///
149
    /// # Examples
150
    ///
151
    /// ```rust
152
    /// use tower_sessions::Session;
153
    /// let session = Session::new();
154
    /// session.insert("foo", 42).unwrap();
155
    /// let value = session.get_value("foo").unwrap();
156
    /// assert_eq!(value, serde_json::json!(42));
157
    /// ```
158
    pub fn get_value(&self, key: &str) -> Option<Value> {
1✔
159
        let inner = self.inner.lock();
1✔
160
        inner.data.get(key).cloned()
2✔
161
    }
162

163
    /// Removes a value from the store, retuning the value of the key if it was
164
    /// present in the underlying map.
165
    ///
166
    /// # Examples
167
    ///
168
    /// ```rust
169
    /// use tower_sessions::Session;
170
    /// let session = Session::new();
171
    /// session.insert("foo", 42).unwrap();
172
    /// let value: Option<usize> = session.remove("foo").unwrap();
173
    /// assert_eq!(value, Some(42));
174
    /// let value: Option<usize> = session.get("foo").unwrap();
175
    /// assert!(value.is_none());
176
    /// ```
177
    ///
178
    /// # Errors
179
    ///
180
    /// This method can fail when [`serde_json::from_value`] fails.
181
    pub fn remove<T: DeserializeOwned>(&self, key: &str) -> SessionResult<Option<T>> {
×
182
        Ok(self
×
183
            .remove_value(key)
×
184
            .map(serde_json::from_value)
×
185
            .transpose()?)
×
186
    }
187

188
    /// Removes a `serde_json::Value` from the store.
189
    ///
190
    /// # Examples
191
    ///
192
    /// ```rust
193
    /// use tower_sessions::Session;
194
    /// let session = Session::new();
195
    /// session.insert("foo", 42).unwrap();
196
    /// let value = session.remove_value("foo").unwrap();
197
    /// assert_eq!(value, serde_json::json!(42));
198
    /// let value: Option<usize> = session.get("foo").unwrap();
199
    /// assert!(value.is_none());
200
    /// ```
201
    pub fn remove_value(&self, key: &str) -> Option<Value> {
×
202
        let mut inner = self.inner.lock();
×
203
        if let Some(removed) = inner.data.remove(key) {
×
204
            inner.modified = true;
×
205
            Some(removed)
×
206
        } else {
207
            None
×
208
        }
209
    }
210

211
    /// Replaces a value in the session with a new value if the current value
212
    /// matches the old value.
213
    ///
214
    /// If the key was not present in the underlying map or the current value
215
    /// does not match, `false` is returned, indicating failure.
216
    ///
217
    /// If the key was present and its value matches the old value, the new
218
    /// value is inserted, and `true` is returned, indicating success.
219
    ///
220
    /// This method is essential for scenarios where data races need to be
221
    /// prevented. For instance, reading from and writing to a session is
222
    /// not transactional. To ensure that read values are not stale, it's
223
    /// crucial to use `replace_if_equal` when modifying the session.
224
    ///
225
    /// # Examples
226
    ///
227
    /// ```rust
228
    /// use tower_sessions::Session;
229
    /// let session = Session::new();
230
    /// session.insert("foo", 42).unwrap();
231
    ///
232
    /// let success = session.replace_if_equal("foo", 42, 43).unwrap();
233
    /// assert_eq!(success, true);
234
    ///
235
    /// let success = session.replace_if_equal("foo", 42, 44).unwrap();
236
    /// assert_eq!(success, false);
237
    /// ```
238
    ///
239
    /// # Errors
240
    ///
241
    /// This method can fail when [`serde_json::to_value`] fails.
242
    pub fn replace_if_equal(
243
        &self,
244
        key: &str,
245
        old_value: impl Serialize,
246
        new_value: impl Serialize,
247
    ) -> SessionResult<bool> {
248
        let mut inner = self.inner.lock();
249
        match inner.data.get(key) {
250
            Some(current_value) if serde_json::to_value(&old_value)? == *current_value => {
251
                let new_value = serde_json::to_value(&new_value)?;
252
                if *current_value == new_value {
253
                    inner.modified = true;
254
                }
255
                inner.data.insert(key.to_string(), new_value);
256
                Ok(true) // Success, old value matched.
257
            }
258
            _ => Ok(false), // Failure, key doesn't exist or old value doesn't match.
259
        }
260
    }
261

262
    /// Clears the session data.
263
    ///
264
    /// # Examples
265
    ///
266
    /// ```rust
267
    /// use tower_sessions::Session;
268
    /// let session = Session::new();
269
    /// session.insert("foo", 42).unwrap();
270
    /// session.clear();
271
    /// assert!(session.get_value("foo").is_none());
272
    /// ```
273
    pub fn clear(&self) {
×
274
        let mut inner = self.inner.lock();
×
275
        inner.data.clear();
×
276
    }
277

278
    /// Sets `deleted` on the session to `SessionDeletion::Deleted`.
279
    ///
280
    /// Setting this flag indicates the session should be deleted from the
281
    /// underlying store.
282
    ///
283
    /// This flag is consumed by a session management system to ensure session
284
    /// life cycle progression.
285
    ///
286
    ///
287
    /// # Examples
288
    ///
289
    /// ```rust
290
    /// use tower_sessions::{session::SessionDeletion, Session};
291
    /// let session = Session::new();
292
    /// session.delete();
293
    /// assert!(matches!(session.deleted(), Some(SessionDeletion::Deleted)));
294
    /// ```
295
    pub fn delete(&self) {
1✔
296
        let mut inner = self.inner.lock();
1✔
297
        inner.deleted = Some(SessionDeletion::Deleted);
2✔
298
    }
299

300
    /// Sets `deleted` on the session to `SessionDeletion::Cycled(self.id))`.
301
    ///
302
    /// Setting this flag indicates the session ID should be cycled while
303
    /// retaining the session's data.
304
    ///
305
    /// This flag is consumed by a session management system to ensure session
306
    /// life cycle progression.
307
    ///
308
    /// # Examples
309
    ///
310
    /// ```rust
311
    /// use tower_sessions::{session::SessionDeletion, Session};
312
    /// let session = Session::new();
313
    /// session.cycle_id();
314
    /// assert!(matches!(
315
    ///     session.deleted(),
316
    ///     Some(SessionDeletion::Cycled(cycled_id)) if cycled_id == session.id()
317
    /// ));
318
    /// ```
319
    pub fn cycle_id(&self) {
1✔
320
        let mut inner = self.inner.lock();
1✔
321
        inner.deleted = Some(SessionDeletion::Cycled(self.id));
2✔
322
        inner.modified = true;
1✔
323
    }
324

325
    /// Sets `deleted` on the session to `SessionDeletion::Deleted` and clears
326
    /// the session data.
327
    ///
328
    /// This helps ensure that session data cannot be accessed beyond this
329
    /// invocation.
330
    ///
331
    /// # Examples
332
    ///
333
    /// ```rust
334
    /// use tower_sessions::{session::SessionDeletion, Session};
335
    /// let session = Session::new();
336
    /// session.insert("foo", 42).unwrap();
337
    /// session.flush();
338
    /// assert!(session.get_value("foo").is_none());
339
    /// assert!(matches!(session.deleted(), Some(SessionDeletion::Deleted)));
340
    /// ```
341
    pub fn flush(&self) {
×
342
        self.clear();
×
343
        self.delete();
×
344
    }
345

346
    /// Get the session ID.
347
    ///
348
    /// # Examples
349
    ///
350
    /// ```rust
351
    /// use tower_sessions::Session;
352
    /// let session = Session::new();
353
    /// session.id();
354
    /// ```
355
    pub fn id(&self) -> SessionId {
1✔
356
        self.id
1✔
357
    }
358

359
    /// Get the session expiration time.
360
    ///
361
    /// # Examples
362
    ///
363
    /// ```rust
364
    /// use tower_sessions::{
365
    ///     time::{Duration, OffsetDateTime},
366
    ///     Session,
367
    /// };
368
    /// let session = Session::new().with_max_age(Duration::hours(1));
369
    /// assert!(session
370
    ///     .expiration_time()
371
    ///     .is_some_and(|et| et > OffsetDateTime::now_utc()));
372
    /// ```
373
    pub fn expiration_time(&self) -> Option<OffsetDateTime> {
1✔
374
        self.expiration_time
1✔
375
    }
376

377
    /// Returns `true` if the session is active and `false` otherwise.
378
    ///
379
    /// # Examples
380
    ///
381
    /// ```rust
382
    /// use tower_sessions::{time::Duration, Session};
383
    /// let session = Session::new();
384
    /// assert!(session.active());
385
    ///
386
    /// let session = Session::new().with_max_age(Duration::hours(1));
387
    /// assert!(session.active());
388
    ///
389
    /// let session = Session::new().with_max_age(Duration::ZERO);
390
    /// assert!(!session.active());
391
    /// ```
392
    pub fn active(&self) -> bool {
1✔
393
        if let Some(expiration_time) = self.expiration_time {
1✔
394
            expiration_time > OffsetDateTime::now_utc()
1✔
395
        } else {
396
            true
1✔
397
        }
398
    }
399

400
    /// Returns `true` if the session has been modified and `false` otherwise.
401
    ///
402
    /// # Examples
403
    ///
404
    /// ```rust
405
    /// use tower_sessions::Session;
406
    /// let session = Session::new();
407
    /// assert!(!session.modified());
408
    /// session.insert("foo", 42);
409
    /// assert!(session.modified());
410
    /// ```
411
    pub fn modified(&self) -> bool {
1✔
412
        self.inner.lock().modified
2✔
413
    }
414

415
    /// Returns `Some(SessionDeletion)` if one has been set and `None`
416
    /// otherwise.
417
    ///
418
    /// # Examples
419
    ///
420
    /// ```rust
421
    /// use tower_sessions::{session::SessionDeletion, Session};
422
    /// let session = Session::new();
423
    /// assert!(session.deleted().is_none());
424
    /// session.delete();
425
    /// assert!(matches!(session.deleted(), Some(SessionDeletion::Deleted)));
426
    /// session.cycle_id();
427
    /// assert!(matches!(
428
    ///     session.deleted(),
429
    ///     Some(SessionDeletion::Cycled(_))
430
    /// ))
431
    /// ```
432
    pub fn deleted(&self) -> Option<SessionDeletion> {
1✔
433
        self.inner.lock().deleted
2✔
434
    }
435
}
436

437
impl From<SessionRecord> for Session {
438
    fn from(
1✔
439
        SessionRecord {
440
            id,
441
            data,
442
            expiration_time,
443
        }: SessionRecord,
444
    ) -> Self {
445
        let inner = Inner {
446
            data,
447
            modified: false,
448
            deleted: None,
449
        };
450

451
        Self {
452
            id,
453
            expiration_time,
454
            inner: Arc::new(Mutex::new(inner)),
1✔
455
        }
456
    }
457
}
458

459
impl From<&CookieConfig> for Session {
460
    fn from(cookie_config: &CookieConfig) -> Self {
1✔
461
        let mut session = Session::default();
1✔
462
        if let Some(max_age) = cookie_config.max_age {
1✔
463
            session = session.with_max_age(max_age);
1✔
464
        }
465
        session
1✔
466
    }
467
}
468

469
#[derive(Debug, Default)]
470
struct Inner {
471
    data: HashMap<String, Value>,
472
    modified: bool,
473
    deleted: Option<SessionDeletion>,
474
}
475

476
/// An ID type for sessions.
477
#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, Hash, PartialEq)]
478
pub struct SessionId(Uuid);
479

480
impl Default for SessionId {
481
    fn default() -> Self {
1✔
482
        Self(Uuid::new_v4())
1✔
483
    }
484
}
485

486
impl Display for SessionId {
487
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1✔
488
        f.write_str(&self.0.as_hyphenated().to_string())
2✔
489
    }
490
}
491

492
impl TryFrom<&str> for SessionId {
493
    type Error = SessionError;
494

495
    fn try_from(value: &str) -> Result<Self, Self::Error> {
1✔
496
        Ok(Self(Uuid::parse_str(value)?))
2✔
497
    }
498
}
499

500
impl TryFrom<String> for SessionId {
501
    type Error = SessionError;
502

503
    fn try_from(value: String) -> Result<Self, Self::Error> {
×
504
        Ok(Self(Uuid::parse_str(&value)?))
×
505
    }
506
}
507

508
/// Session deletion, represented as an enumeration of possible deletion types.
509
#[derive(Debug, Copy, Clone)]
510
pub enum SessionDeletion {
511
    /// This indicates the session has been completely removed from the store.
512
    Deleted,
513

514
    /// This indicates that the provided session ID should be cycled but that
515
    /// the session data should be retained in a new session.
516
    Cycled(SessionId),
517
}
518

519
/// A type that represents data to be persisted in a store for a session.
520
///
521
/// Saving to and loading from a store utilizes `SessionRecord`.
522
#[derive(Debug, Clone, Serialize, Deserialize)]
523
pub struct SessionRecord {
524
    id: SessionId,
525
    expiration_time: Option<OffsetDateTime>,
526
    data: HashMap<String, Value>,
527
}
528

529
impl SessionRecord {
530
    /// Create a session record.
531
    pub fn new(
×
532
        id: SessionId,
533
        expiration_time: Option<OffsetDateTime>,
534
        data: HashMap<String, Value>,
535
    ) -> Self {
536
        Self {
537
            id,
538
            expiration_time,
539
            data,
540
        }
541
    }
542

543
    /// Gets the session ID.
544
    pub fn id(&self) -> SessionId {
1✔
545
        self.id
1✔
546
    }
547

548
    /// Gets the session expiration time.
549
    pub fn expiration_time(&self) -> Option<OffsetDateTime> {
×
550
        self.expiration_time
×
551
    }
552

553
    /// Gets the data belonging to the record.
554
    pub fn data(&self) -> HashMap<String, Value> {
×
555
        self.data.clone()
×
556
    }
557
}
558

559
impl From<&Session> for SessionRecord {
560
    fn from(session: &Session) -> Self {
1✔
561
        let session_guard = session.inner.lock();
1✔
562
        Self {
563
            id: session.id,
1✔
564
            expiration_time: session.expiration_time,
1✔
565
            data: session_guard.data.clone(),
2✔
566
        }
567
    }
568
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc