Skip to main content

allsource_core/domain/entities/
query_usage.rs

1//! Canonical query-meter admission. Receipts confer no data or execution authority.
2use crate::error::{AllSourceError, Result};
3use serde::{Deserialize, Serialize};
4
5pub const QUERY_RETRY_SECONDS: i64 = 3_600;
6pub const MAX_QUERY_RECEIPTS: usize = 4_096;
7
8#[derive(Clone, Debug, Deserialize, Serialize)]
9#[serde(deny_unknown_fields)]
10pub struct QueryUsageRequest {
11    pub operation_id: String,
12    pub fingerprint: String,
13    pub count: u64,
14    pub expected_period: u64,
15}
16
17impl QueryUsageRequest {
18    pub fn expires_at(&self) -> Result<i64> {
19        let invalid = || AllSourceError::ValidationError("Invalid query admission request".into());
20        let (time, nonce) = self.operation_id.split_once(':').ok_or_else(invalid)?;
21        let issued: i64 = time.parse().map_err(|_| invalid())?;
22        let uuid = uuid::Uuid::parse_str(nonce).map_err(|_| invalid())?;
23        if issued < 0
24            || time != issued.to_string()
25            || nonce != uuid.hyphenated().to_string()
26            || !(1..=4).contains(&self.count)
27            || self.fingerprint.len() != 64
28            || !self
29                .fingerprint
30                .bytes()
31                .all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c))
32        {
33            return Err(invalid());
34        }
35        issued.checked_add(QUERY_RETRY_SECONDS).ok_or_else(invalid)
36    }
37}
38
39#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
40#[serde(deny_unknown_fields)]
41pub struct QueryUsageReceipt {
42    pub operation_id: String,
43    pub fingerprint: String,
44    pub count: u64,
45    pub period: u64,
46    pub used: u64,
47    pub expires_at: i64,
48}
49
50impl QueryUsageReceipt {
51    pub fn matches(&self, request: &QueryUsageRequest) -> bool {
52        self.fingerprint == request.fingerprint
53            && self.count == request.count
54            && self.period == request.expected_period
55    }
56}
57
58#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
59#[serde(rename_all = "snake_case")]
60pub enum QueryUsageDenial {
61    ExpiredOperation,
62    OperationConflict,
63    PeriodChanged,
64    QuotaExceeded,
65    ReceiptCapacity,
66    InactiveTenant,
67    InvalidQuotaMetadata,
68}
69
70#[derive(Debug, PartialEq)]
71pub enum QueryUsageDecision {
72    Admitted {
73        receipt: QueryUsageReceipt,
74        replayed: bool,
75    },
76    Denied(QueryUsageDenial),
77}
78
79#[derive(Debug, Deserialize)]
80#[serde(deny_unknown_fields)]
81pub struct QueryUsageReset {
82    pub expected_period: u64,
83}
84
85#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
86#[serde(deny_unknown_fields)]
87pub struct QueryUsageResetReceipt {
88    pub previous_period: u64,
89    pub period: u64,
90}
91
92#[derive(Debug, Serialize)]
93pub struct QueryUsageSnapshot {
94    pub period: u64,
95    pub used: u64,
96    pub quota: i64,
97    pub managed: bool,
98}
99
100#[derive(Debug, PartialEq)]
101pub enum QueryUsageResetDecision {
102    Reset {
103        receipt: QueryUsageResetReceipt,
104        replayed: bool,
105    },
106    Denied(QueryUsageDenial),
107}