use crate::error::Error;
use crate::CliResult;
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE};
use std::path::PathBuf;
use std::str::FromStr;
use crate::introspection_queries::{
introspection_query, introspection_query_with_is_one_of,
introspection_query_with_is_one_of_specified_by_url, introspection_query_with_specified_by,
};
pub fn introspect_schema(
location: &str,
output: Option<PathBuf>,
authorization: Option<String>,
headers: Vec<Header>,
no_ssl: bool,
is_one_of: bool,
specify_by_url: bool,
) -> CliResult<()> {
use std::io::Write;
let out: Box<dyn Write> = match output {
Some(path) => Box::new(::std::fs::File::create(path)?),
None => Box::new(std::io::stdout()),
};
let mut request_body: graphql_client::QueryBody<()> = graphql_client::QueryBody {
variables: (),
query: introspection_query::QUERY,
operation_name: introspection_query::OPERATION_NAME,
};
if is_one_of {
request_body = graphql_client::QueryBody {
variables: (),
query: introspection_query_with_is_one_of::QUERY,
operation_name: introspection_query_with_is_one_of::OPERATION_NAME,
}
}
if specify_by_url {
request_body = graphql_client::QueryBody {
variables: (),
query: introspection_query_with_specified_by::QUERY,
operation_name: introspection_query_with_specified_by::OPERATION_NAME,
}
}
if is_one_of && specify_by_url {
request_body = graphql_client::QueryBody {
variables: (),
query: introspection_query_with_is_one_of_specified_by_url::QUERY,
operation_name: introspection_query_with_is_one_of_specified_by_url::OPERATION_NAME,
}
}
let client = reqwest::blocking::Client::builder()
.danger_accept_invalid_certs(no_ssl)
.build()?;
let mut req_builder = client.post(location).headers(construct_headers());
for custom_header in headers {
req_builder = req_builder.header(custom_header.name.as_str(), custom_header.value.as_str());
}
if let Some(token) = authorization {
req_builder = req_builder.bearer_auth(token.as_str());
};
let res = req_builder.json(&request_body).send()?;
if res.status().is_success() {
} else if res.status().is_server_error() {
return Err(Error::message("server error!".into()));
} else {
let status = res.status();
let error_message = match res.text() {
Ok(msg) => match serde_json::from_str::<serde_json::Value>(&msg) {
Ok(json) => format!("HTTP {}\n{}", status, serde_json::to_string_pretty(&json)?),
Err(_) => format!("HTTP {}: {}", status, msg),
},
Err(_) => format!("HTTP {}", status),
};
return Err(Error::message(error_message));
}
let json: serde_json::Value = res.json()?;
serde_json::to_writer_pretty(out, &json)?;
Ok(())
}
fn construct_headers() -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
headers
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Header {
name: String,
value: String,
}
impl FromStr for Header {
type Err = String;
fn from_str(input: &str) -> Result<Self, Self::Err> {
if !input.contains(':') {
return Err(format!(
"Invalid header input. A colon is required to separate the name and value. [{}]",
input
));
}
let name_value: Vec<&str> = input.splitn(2, ':').collect();
let name = name_value[0].trim();
let value = name_value[1].trim();
if name.is_empty() {
return Err(format!(
"Invalid header input. Field name is required before colon. [{}]",
input
));
}
if name.split_whitespace().count() > 1 {
return Err(format!(
"Invalid header input. Whitespace not allowed in field name. [{}]",
input
));
}
Ok(Self {
name: name.to_string(),
value: value.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_errors_invalid_headers() {
for input in [
"X-Name Value", ": Value", "X Name: Value", "X\tName: Value", ]
.iter()
{
let header = Header::from_str(input);
assert!(header.is_err(), "Expected error: [{}]", input);
}
}
#[test]
fn it_parses_valid_headers() {
let expected1 = Header {
name: "X-Name".to_string(),
value: "Value".to_string(),
};
let expected2 = Header {
name: "X-Name".to_string(),
value: "Value:".to_string(),
};
for (input, expected) in [
("X-Name: Value", &expected1), ("X-Name:Value", &expected1), ("X-Name: Value ", &expected1), ("X-Name:\tValue", &expected1), ("X-Name: Value:", &expected2), ("X-Name : Value", &expected1),
(" X-Name: Value", &expected1),
]
.iter()
{
let header = Header::from_str(input);
assert!(header.is_ok(), "Expected ok: [{}]", input);
assert_eq!(
header.unwrap(),
**expected,
"Expected equality: [{}]",
input
);
}
}
}