appcore_api/
query_contract.rs1use serde::{Deserialize, Serialize};
14use std::io::{self, Write};
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub struct QueryRequest {
19 pub query_name: String,
21 pub query_id: String,
23 pub payload: serde_json::Value,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29pub struct QueryResponse {
30 pub ok: bool,
32 pub message: Option<String>,
34 pub payload: serde_json::Value,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum QueryRequestValidationError {
41 EmptyQueryName,
43 EmptyQueryId,
45 InvalidQueryName,
47 InvalidQueryId,
49 PayloadTooLarge,
51}
52
53impl QueryRequest {
54 pub fn validate(&self, max_payload_bytes: usize) -> Result<(), QueryRequestValidationError> {
56 if self.query_name.trim().is_empty() {
57 return Err(QueryRequestValidationError::EmptyQueryName);
58 }
59 if self.query_id.trim().is_empty() {
60 return Err(QueryRequestValidationError::EmptyQueryId);
61 }
62 if self.query_name.len() > 128 || !is_valid_token(&self.query_name) {
63 return Err(QueryRequestValidationError::InvalidQueryName);
64 }
65 if self.query_id.len() > 128 || !is_valid_token(&self.query_id) {
66 return Err(QueryRequestValidationError::InvalidQueryId);
67 }
68 if !payload_fits(&self.payload, max_payload_bytes) {
69 return Err(QueryRequestValidationError::PayloadTooLarge);
70 }
71 Ok(())
72 }
73
74 pub fn payload_bytes(&self) -> Vec<u8> {
76 serde_json::to_vec(&self.payload).unwrap_or_default()
77 }
78}
79
80struct LimitedJsonCounter {
81 remaining: usize,
82 exceeded: bool,
83}
84
85impl LimitedJsonCounter {
86 const fn new(limit: usize) -> Self {
87 Self {
88 remaining: limit,
89 exceeded: false,
90 }
91 }
92}
93
94impl Write for LimitedJsonCounter {
95 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
96 if bytes.len() > self.remaining {
97 self.exceeded = true;
98 return Err(io::Error::other("query payload exceeds configured limit"));
99 }
100 self.remaining -= bytes.len();
101 Ok(bytes.len())
102 }
103
104 fn flush(&mut self) -> io::Result<()> {
105 Ok(())
106 }
107}
108
109fn payload_fits(payload: &serde_json::Value, limit: usize) -> bool {
110 let mut counter = LimitedJsonCounter::new(limit);
111 let result = serde_json::to_writer(&mut counter, payload);
112 debug_assert!(result.is_ok() || counter.exceeded);
113 !counter.exceeded
114}
115
116impl QueryResponse {
117 pub fn ok(payload: serde_json::Value) -> Self {
119 Self {
120 ok: true,
121 message: None,
122 payload,
123 }
124 }
125
126 pub fn rejected(message: impl Into<String>) -> Self {
128 Self {
129 ok: false,
130 message: Some(message.into()),
131 payload: serde_json::Value::Object(serde_json::Map::new()),
132 }
133 }
134}
135
136fn is_valid_token(value: &str) -> bool {
137 value
138 .bytes()
139 .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'.' | b'_' | b':' | b'-'))
140}
141
142#[cfg(test)]
143#[path = "query_contract_tests.rs"]
144mod tests;