roboplc_rpc/tools/
http.rs1use core::fmt;
2use std::collections::BTreeMap;
3
4use http::{header, StatusCode};
5use serde::{de::DeserializeOwned, Serialize};
6use serde_json::{json, Value};
7
8#[derive(thiserror::Error, Debug)]
10pub enum Error {
11 #[error("pack error: {0}")]
13 Serialization(#[from] serde_json::Error),
14 #[error("invalid data: {0}")]
16 InvalidData(String),
17}
18
19use crate::{request::Request, response::Response};
20
21#[derive(Debug)]
26pub struct QueryString(String);
27
28impl QueryString {
29 pub fn new(s: &str) -> Self {
31 QueryString(s.to_owned())
32 }
33}
34
35impl From<String> for QueryString {
36 fn from(s: String) -> Self {
37 QueryString(s)
38 }
39}
40
41impl fmt::Display for QueryString {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 write!(f, "{}", self.0)
44 }
45}
46
47impl From<QueryString> for String {
48 fn from(qs: QueryString) -> Self {
49 qs.0
50 }
51}
52
53impl AsRef<str> for QueryString {
54 fn as_ref(&self) -> &str {
55 &self.0
56 }
57}
58
59impl<M: Serialize> TryFrom<Request<M>> for QueryString {
60 type Error = Error;
61
62 fn try_from(req: Request<M>) -> Result<Self, Self::Error> {
63 request_into_query_string(&req).map(QueryString)
64 }
65}
66
67impl<M: DeserializeOwned + Serialize> TryFrom<QueryString> for Request<M> {
68 type Error = Error;
69
70 fn try_from(qs: QueryString) -> Result<Self, Self::Error> {
71 request_from_query_string(&qs.0)
72 }
73}
74
75fn parse_string(s: &str) -> Value {
76 if s == "true" {
77 Value::Bool(true)
78 } else if s == "false" {
79 Value::Bool(false)
80 } else if s == "null" {
81 Value::Null
82 } else if let Ok(n) = s.parse::<u64>() {
83 Value::Number(n.into())
84 } else if let Ok(n) = s.parse::<i64>() {
85 Value::Number(n.into())
86 } else if let Ok(n) = s.parse::<f64>() {
87 Value::Number(serde_json::value::Number::from_f64(n).unwrap())
88 } else {
89 Value::String(s.to_string())
90 }
91}
92
93fn request_from_query_string<M: DeserializeOwned + Serialize>(
94 qs: &str,
95) -> Result<Request<M>, Error> {
96 let mut id: Option<Value> = None;
97 let mut method: Option<String> = None;
98 let mut params: BTreeMap<String, Value> = BTreeMap::new();
99 for (i, (name, value)) in url::form_urlencoded::parse(qs.as_bytes())
100 .into_iter()
101 .enumerate()
102 {
103 match name.as_ref() {
104 "i" if i == 0 => {
105 id = Some(serde_json::from_str(&value)?);
106 }
107 "m" if method.is_none() => {
108 method = Some(value.to_string());
109 }
110 n => {
111 params.insert(n.to_string(), parse_string(&value));
112 }
113 }
114 }
115 let method_name = method.ok_or(Error::InvalidData("the method is missing".into()))?;
116 #[cfg(feature = "canonical")]
117 let method = serde_json::from_value(json!({
118 "method": method_name,
119 "params": params,
120 }))?;
121 #[cfg(not(feature = "canonical"))]
122 let method = serde_json::from_value(json!({
123 "m": method_name,
124 "p": params,
125 }))?;
126 if let Some(id) = id {
127 Ok(Request::new(id, method))
128 } else {
129 Ok(Request::new0(method))
130 }
131}
132
133fn value_to_string(field: &str, value: &Value) -> Result<String, Error> {
134 Ok(match value {
135 Value::Null => "null".to_string(),
136 Value::Bool(b) => b.to_string(),
137 Value::Number(n) => n.to_string(),
138 Value::String(s) => s.to_string(),
139 _ => {
140 return Err(Error::InvalidData(format!(
141 "unsupported value type for field '{}'",
142 field
143 )))
144 }
145 })
146}
147
148fn request_into_query_string<M: Serialize>(req: &Request<M>) -> Result<String, Error> {
149 let mut pairs = Vec::new();
150 if let Some(id) = &req.id {
151 pairs.push(("i", id.to_string()));
152 }
153 let req_value = serde_json::to_value(&req.method)?;
154 let req_map = req_value
155 .as_object()
156 .ok_or(Error::InvalidData("invalid request".into()))?;
157 let method = req_map
158 .get("method")
159 .ok_or(Error::InvalidData("method is missing".into()))?;
160 pairs.push((
161 "m",
162 method
163 .as_str()
164 .ok_or(Error::InvalidData("invalid method name".into()))?
165 .to_string(),
166 ));
167 if let Some(params) = req_map.get("params") {
168 let params = params
169 .as_object()
170 .ok_or(Error::InvalidData("params must be object".into()))?;
171 for (name, value) in params {
172 pairs.push((name, value_to_string(name, value)?));
173 }
174 }
175 Ok(url::form_urlencoded::Serializer::new(String::new())
176 .extend_pairs(pairs)
177 .finish())
178}
179
180#[derive(Debug)]
181#[allow(clippy::module_name_repetitions)]
182pub struct HttpResponse {
184 status: http::StatusCode,
185 headers: http::header::HeaderMap,
186 body: String,
187}
188
189impl HttpResponse {
190 pub fn status(&self) -> http::StatusCode {
192 self.status
193 }
194 pub fn headers(&self) -> &http::header::HeaderMap {
196 &self.headers
197 }
198 pub fn body(&self) -> &str {
200 &self.body
201 }
202 pub fn headers_mut(&mut self) -> &mut http::header::HeaderMap {
204 &mut self.headers
205 }
206 pub fn into_parts(self) -> (http::StatusCode, http::header::HeaderMap, String) {
208 (self.status, self.headers, self.body)
209 }
210}
211
212impl<R: Serialize> TryFrom<Response<R>> for HttpResponse {
213 type Error = Error;
214
215 fn try_from(response: Response<R>) -> Result<Self, Self::Error> {
216 let (id, res) = response.into_parts();
217 let status = if res.is_ok() {
218 StatusCode::OK
219 } else {
220 StatusCode::INTERNAL_SERVER_ERROR
221 };
222 let mut headers = header::HeaderMap::new();
223 headers.insert(
224 header::CONTENT_TYPE,
225 header::HeaderValue::from_static("application/json"),
226 );
227 headers.insert(
228 "X-JSONRPC-ID",
229 value_to_string("", &id)?.parse().map_err(|e| {
230 Error::InvalidData(format!("failed to parse id as http header: {}", e))
231 })?,
232 );
233 Ok(HttpResponse {
234 status,
235 headers,
236 body: serde_json::to_string(&res)?,
237 })
238 }
239}