Skip to main content

fusion_common/ctx/
mod.rs

1use std::{ops::Deref, sync::Arc, time::Duration};
2
3use chrono::{DateTime, FixedOffset, Utc};
4use serde::{Deserialize, Serialize};
5use serde_json::{Map, Value};
6use thiserror::Error;
7
8use crate::time::now_offset;
9
10#[derive(Debug, Error)]
11pub enum CtxError {
12  #[error("Invalid payload, need a json object")]
13  InvalidPayload,
14
15  #[error("Unauthorized: {0}")]
16  Unauthorized(String),
17}
18
19#[derive(Debug, Clone, Default, Serialize, Deserialize)]
20#[cfg_attr(feature = "with-openapi", derive(utoipa::ToSchema))]
21#[serde(transparent)]
22pub struct CtxPayload(Map<String, Value>);
23
24impl CtxPayload {
25  pub fn new(payload: Map<String, Value>) -> Self {
26    Self(payload)
27  }
28
29  pub fn into_inner(self) -> Map<String, Value> {
30    self.0
31  }
32
33  pub fn set_subject(&mut self, value: impl Into<String>) {
34    self.set_string(Ctx::SUB, value);
35  }
36
37  /// Set the expiration time with `DateTime<Utc>`.
38  pub fn set_expires_at(&mut self, value: impl Into<DateTime<Utc>>) {
39    let utc: DateTime<Utc> = value.into();
40    self.set_exp(utc.timestamp());
41  }
42
43  /// Set the expiration time in seconds since the Unix epoch.
44  pub fn set_exp(&mut self, epoch_seconds: i64) {
45    self.set_i64(Ctx::EXP, epoch_seconds);
46  }
47
48  pub fn set_string(&mut self, key: &str, value: impl Into<String>) {
49    self.0.insert(key.to_string(), Value::String(value.into()));
50  }
51
52  pub fn set_i64(&mut self, key: &str, value: i64) {
53    self.0.insert(key.to_string(), Value::Number(value.into()));
54  }
55
56  pub fn set_i32(&mut self, key: &str, value: i32) {
57    self.0.insert(key.to_string(), Value::Number(value.into()));
58  }
59
60  pub fn set_datetime(&mut self, key: &str, value: impl Into<DateTime<Utc>>) {
61    let utc: DateTime<Utc> = value.into();
62    self.0.insert(key.to_string(), Value::Number(utc.timestamp().into()));
63  }
64
65  pub fn set_bool(&mut self, key: &str, value: bool) {
66    self.0.insert(key.to_string(), Value::Bool(value));
67  }
68
69  pub fn set_strings<I, S>(&mut self, key: &str, value: I)
70  where
71    I: IntoIterator<Item = S>,
72    S: Into<String>,
73  {
74    self
75      .0
76      .insert(key.to_string(), Value::Array(value.into_iter().map(|s| Value::String(s.into())).collect()));
77  }
78
79  pub fn get_subject(&self) -> Option<&str> {
80    self.get_str(Ctx::SUB)
81  }
82
83  pub fn get_expires_at(&self) -> Option<DateTime<Utc>> {
84    self.get_exp().and_then(DateTime::<Utc>::from_timestamp_secs)
85  }
86
87  pub fn get_exp(&self) -> Option<i64> {
88    self.get_i64(Ctx::EXP)
89  }
90
91  pub fn get_str(&self, key: &str) -> Option<&str> {
92    self.0.get(key).and_then(|s| s.as_str())
93  }
94
95  pub fn get_i64(&self, key: &str) -> Option<i64> {
96    self.0.get(key).and_then(|s| s.as_i64())
97  }
98
99  pub fn get_i32(&self, key: &str) -> Option<i32> {
100    self.0.get(key).and_then(|s| s.as_i64()).map(|v| v as i32)
101  }
102
103  pub fn get_strings(&self, key: &str) -> Option<Vec<&str>> {
104    self
105      .0
106      .get(key)
107      .and_then(|s| s.as_array())
108      .map(|v| v.iter().filter_map(|s| s.as_str()).map(|s| s.trim()).collect())
109  }
110
111  pub fn get_bool(&self, key: &str) -> Option<bool> {
112    self.0.get(key).and_then(|s| s.as_bool())
113  }
114}
115
116impl From<Map<String, Value>> for CtxPayload {
117  fn from(payload: Map<String, Value>) -> Self {
118    Self::new(payload)
119  }
120}
121
122#[derive(Debug, Serialize, Deserialize)]
123#[cfg_attr(feature = "with-openapi", derive(utoipa::ToSchema))]
124pub struct CtxInner {
125  payload: CtxPayload,
126  req_time: DateTime<FixedOffset>,
127  req_id: String,
128}
129
130/// 会话上下文。此处 clone 的成本很低
131#[derive(Clone, Debug, Serialize, Deserialize)]
132#[cfg_attr(feature = "with-openapi", derive(utoipa::ToSchema))]
133#[serde(transparent)]
134pub struct Ctx(Arc<CtxInner>);
135
136impl Ctx {
137  /// 常用于保存用户 ID
138  pub const SUB: &str = "sub";
139  /// 常用于保存用户过期时间,表示的是自 Unix 纪元(1970-01-01T00:00:00Z UTC)以来的秒数
140  pub const EXP: &str = "exp";
141  /// 租户 ID
142  pub const TENANT_ID: &str = "tenant_id";
143
144  pub(crate) fn new(payload: CtxPayload, req_time: DateTime<FixedOffset>, req_id: String) -> Self {
145    Self(Arc::new(CtxInner { payload, req_time, req_id }))
146  }
147
148  /// Create a new context
149  pub fn try_new(
150    mut payload: CtxPayload,
151    req_time: Option<DateTime<FixedOffset>>,
152    req_id: Option<String>,
153  ) -> Result<Self, CtxError> {
154    let now = now_offset();
155    if let Some(st) = payload.get_expires_at() {
156      if st < now {
157        return Err(CtxError::Unauthorized("The token expired".to_string()));
158      }
159    } else {
160      let exp = now + Duration::from_secs(60 * 60 * 24 * 60); // 60 days
161      payload.set_expires_at(exp.to_utc());
162    }
163
164    Ok(Self::new(payload, req_time.unwrap_or(now), req_id.unwrap_or_default()))
165  }
166
167  pub fn new_root() -> Self {
168    let req_time = now_offset();
169    let expires_at = req_time + Duration::from_secs(60 * 30); // 30 minutes
170    let mut payload = CtxPayload::default();
171    payload.set_string(Self::SUB, "0");
172    payload.set_expires_at(expires_at);
173    Self::new(payload, req_time, "".to_string())
174  }
175
176  pub fn new_super_admin() -> Self {
177    let req_time = now_offset();
178    let expires_at = req_time + Duration::from_secs(60 * 30); // 30 minutes
179    let mut payload = CtxPayload::default();
180    payload.set_string(Self::SUB, "1");
181    payload.set_expires_at(expires_at);
182    Self::new(payload, req_time, "".to_string())
183  }
184
185  pub fn get_user_id(&self) -> Option<i64> {
186    self.payload.get_i64(Self::SUB)
187  }
188
189  pub fn user_id(&self) -> i64 {
190    self.get_user_id().unwrap_or(0)
191  }
192
193  pub fn get_tenant_id(&self) -> Option<i64> {
194    self.payload.get_i64(Self::TENANT_ID)
195  }
196
197  pub fn tenant_id(&self) -> i64 {
198    self.get_tenant_id().unwrap_or(0)
199  }
200
201  pub fn req_time(&self) -> &DateTime<FixedOffset> {
202    &self.req_time
203  }
204
205  pub fn req_epoch_secs(&self) -> i64 {
206    self.req_time.timestamp()
207  }
208
209  pub fn req_epoch_millis(&self) -> i64 {
210    self.req_time.timestamp_millis()
211  }
212
213  pub fn req_id(&self) -> &str {
214    &self.req_id
215  }
216
217  pub fn expires_at(&self) -> Option<DateTime<Utc>> {
218    self.payload.get_expires_at()
219  }
220
221  pub fn payload(&self) -> &CtxPayload {
222    &self.payload
223  }
224}
225
226impl Deref for Ctx {
227  type Target = CtxInner;
228
229  fn deref(&self) -> &Self::Target {
230    &self.0
231  }
232}