use crate::{
convert::Convert,
http::{
json::{ConstantSizeId, Id, Version},
HttpRequest,
},
};
use http::{header::CONTENT_TYPE, HeaderValue};
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use thiserror::Error;
#[derive(Debug)]
pub struct JsonRequestConverter<T> {
_marker: PhantomData<T>,
}
impl<T> JsonRequestConverter<T> {
pub fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}
impl<T> Clone for JsonRequestConverter<T> {
fn clone(&self) -> Self {
Self {
_marker: self._marker,
}
}
}
impl<T> Default for JsonRequestConverter<T> {
fn default() -> Self {
Self::new()
}
}
#[derive(Error, Clone, Debug, Eq, PartialEq)]
pub enum JsonRequestConversionError {
#[error("Invalid JSON body: {0}")]
InvalidJson(String),
}
impl<T> Convert<http::Request<T>> for JsonRequestConverter<T>
where
T: Serialize,
{
type Output = HttpRequest;
type Error = JsonRequestConversionError;
fn try_convert(&mut self, request: http::Request<T>) -> Result<Self::Output, Self::Error> {
try_serialize_request(request).map(add_content_type_header_if_missing)
}
}
fn try_serialize_request<T>(
request: http::Request<T>,
) -> Result<HttpRequest, JsonRequestConversionError>
where
T: Serialize,
{
let (parts, body) = request.into_parts();
let json_body = serde_json::to_vec(&body)
.map_err(|e| JsonRequestConversionError::InvalidJson(e.to_string()))?;
Ok(HttpRequest::from_parts(parts, json_body))
}
fn add_content_type_header_if_missing(mut request: HttpRequest) -> HttpRequest {
if !request.headers().contains_key(CONTENT_TYPE) {
request
.headers_mut()
.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
}
request
}
pub type HttpBatchJsonRpcRequest<T> = http::Request<BatchJsonRpcRequest<T>>;
pub type HttpJsonRpcRequest<T> = http::Request<JsonRpcRequest<T>>;
pub type BatchJsonRpcRequest<T> = Vec<JsonRpcRequest<T>>;
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct JsonRpcRequest<T> {
jsonrpc: Version,
method: String,
id: Id,
params: Option<T>,
}
impl<T> JsonRpcRequest<T> {
pub fn new(method: impl Into<String>, params: T) -> Self {
Self {
jsonrpc: Version::V2,
method: method.into(),
id: ConstantSizeId::ZERO.into(),
params: Some(params),
}
}
pub fn with_id<I: Into<Id>>(self, id: I) -> Self {
Self {
id: id.into(),
..self
}
}
pub fn set_id(&mut self, id: Id) {
self.id = id;
}
pub fn id(&self) -> &Id {
&self.id
}
pub fn method(&self) -> &str {
&self.method
}
pub fn params(&self) -> Option<&T> {
self.params.as_ref()
}
}