#![feature(custom_attribute)]
#![feature(associated_type_defaults)]
#![feature(pattern)]
#![allow(dead_code)]
extern crate mio_httpc;
#[macro_use]
pub extern crate serde_derive;
extern crate serde_json;
mod test;
use mio_httpc::CallBuilder;
pub use serde_json::Value;
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use std::str::pattern::Pattern;
use mio_httpc::Error as MioError;
#[derive(Debug, PartialEq, Eq)]
pub enum QlType {
Subscribe,
Query,
Mutation,
}
#[derive(Debug, Deserialize)]
pub struct Locations {
line: u32,
column: u32,
}
#[derive(Deserialize, Debug)]
pub struct QlErrorInternal {
message: String,
locations: Vec<Locations>,
}
#[derive(Debug)]
pub enum QlError {
NotPrepared,
EmptyHashmap,
BadUrl,
CannotExec(&'static str),
Internal(Vec<QlErrorInternal>),
}
#[derive(Debug, PartialEq, Eq)]
pub struct Request {
data: String,
ql_type: QlType,
prepared: bool,
method: String
}
#[derive(Deserialize, Debug)]
struct QlResponse<T> {
data: Option<T>,
errors: Option<Vec<QlErrorInternal>>,
#[serde(flatten)]
extra: HashMap<String, Value>,
}
#[derive(Debug)]
pub struct QlRequestTyped<T> {
t: std::marker::PhantomData<T>,
request: Request,
}
impl<T> QlRequestTyped<T> {
pub fn new(query: &str) -> Self {
Self {
t: std::marker::PhantomData,
request: Request::new(query),
}
}
pub fn from_path(name: &str) -> Result<Self, std::io::Error> {
let request = Request::from_path(name)?;
Ok(Self {
t: std::marker::PhantomData,
request,
})
}
pub fn prepare(mut self, opt_map: HashMap<String, String>) -> Result<Self, QlError> {
self.request = self.request.prepare(opt_map)?;
Ok(self)
}
pub fn force_prepare(mut self) -> Self {
self.request.prepared = true;
self
}
pub fn send(&self, uri: &str) -> Result<T, QlError>
where
T: serde::de::DeserializeOwned,
{
self.request.send::<T>(uri)
}
}
impl Request {
pub fn new(query: &str) -> Request {
Request {
data: String::from(query),
ql_type: QlType::Query,
prepared: false,
method: "POST".to_string()
}
}
pub fn get(mut self) -> Request {
self.method = "GET".to_string();
self
}
pub fn post(mut self) -> Request {
self.method = "POST".to_string();
self
}
pub fn ql_type(mut self, ql_type: QlType) -> Request {
self.ql_type = ql_type;
self
}
pub fn from_path(name: &str) -> Result<Request, std::io::Error> {
let request = std::fs::read_to_string(name)?;
let ql_type = if name.is_suffix_of(".mutation") {
QlType::Mutation
} else if name.is_suffix_of(".subscription") {
QlType::Subscribe
} else {
QlType::Query
};
Ok(Request {
data: request,
ql_type,
prepared: false,
method: "POST".to_string()
})
}
pub fn prepare(mut self, opt_map: HashMap<String, String>) -> Result<Self, QlError> {
for (key, data) in opt_map {
self.data = self.data.replace(&key, &data);
}
self.prepared = true;
Ok(self)
}
pub fn force_prepare(mut self) -> Self {
self.prepared = true;
self
}
pub fn send<T>(&self, uri: &str) -> Result<T, QlError>
where T: serde::de::DeserializeOwned,
{
if !self.prepared && self.param_needed() > 0 {
return Err(QlError::NotPrepared);
}
let res = CallBuilder::new()
.method(self.method.as_str())
.body(self.data.clone().into_bytes())
.header("content-type", "application/graphql")
.url(uri)?
.exec()?;
let string = String::from_utf8(res.1).expect("Cannot stringify");
let res: QlResponse<T> = serde_json::from_str(&string).expect("Cannot Jsonify");
if let Some(err) = res.errors {
Err(QlError::Internal(err))
} else {
Ok(res.data.unwrap())
}
}
pub fn param_needed(&self) -> i32 {
let mut output = 0;
for elem in self.data.chars() {
if elem == ':' {
output += 1;
}
}
output
}
}
pub trait FromQl: serde::de::DeserializeOwned + Sized {
fn request(_uri: &str, _request: &str, _identifier: &str) -> Option<Self> {
unimplemented!("TODO implement a derive");
}
}
impl From<MioError> for QlError {
fn from(error: MioError) -> QlError {
match error {
MioError::Httparse(_) => QlError::BadUrl,
MioError::TimeOut => QlError::CannotExec("Time out"),
MioError::Url(_) => QlError::BadUrl,
_ => QlError::CannotExec("Cannot exec")
}
}
}
impl fmt::Display for QlError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}", self.description())
}
}
impl Error for QlError {
fn description(&self) -> &str {
match self {
QlError::NotPrepared => "You should prepare your request before sending it.",
QlError::EmptyHashmap => "You should replace needed params",
QlError::Internal(_) => "Internal Error: {}",
QlError::BadUrl => "Bad url, cannot use it",
QlError::CannotExec(_) => "Cannot exec this request {}"
}
}
}