use crate::js::Js;
use crate::{error::Error, planner::QueryPlannerConfig};
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use thiserror::Error;
#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct IntrospectionError {
pub message: Option<String>,
}
impl Display for IntrospectionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.message.as_deref().unwrap_or("UNKNOWN"))
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct IntrospectionResponse {
#[serde(default)]
data: Option<serde_json::Value>,
#[serde(default)]
errors: Option<Vec<IntrospectionError>>,
}
impl IntrospectionResponse {
pub fn data(&self) -> Option<&serde_json::Value> {
self.data.as_ref()
}
pub fn errors(&self) -> Option<&Vec<IntrospectionError>> {
self.errors.as_ref()
}
pub fn into_result(self) -> Result<serde_json::Value, Vec<IntrospectionError>> {
match (self.data, self.errors) {
(Some(_), Some(errors)) if !errors.is_empty() => Err(errors),
(Some(data), Some(errors)) if errors.is_empty() => Ok(data),
(Some(data), None) => Ok(data),
(None, Some(errors)) => Err(errors),
_ => Err(vec![IntrospectionError {
message: Some("neither data nor errors could be found".to_string()),
}]),
}
}
}
pub type IntrospectionResult = Result<Vec<IntrospectionResponse>, IntrospectionError>;
pub fn batch_introspect(
sdl: &str,
queries: Vec<String>,
config: QueryPlannerConfig,
) -> Result<IntrospectionResult, Error> {
Js::new()
.with_parameter("sdl", sdl)?
.with_parameter("queries", queries)?
.with_parameter("config", config)?
.execute::<IntrospectionResult>(
"do_introspect",
include_str!("../bundled/do_introspect.js"),
)
}
#[cfg(test)]
mod tests {
use crate::{
introspect::batch_introspect,
planner::{IncrementalDeliverySupport, QueryPlannerConfig},
};
#[test]
fn it_works() {
let raw_sdl = r#"schema
{
query: Query
}
type Query {
hello: String
}
"#;
let introspected = batch_introspect(
raw_sdl,
vec![DEFAULT_INTROSPECTION_QUERY.to_string()],
QueryPlannerConfig::default(),
)
.unwrap();
insta::assert_snapshot!(serde_json::to_string(&introspected).unwrap());
}
#[test]
fn invalid_sdl() {
use crate::introspect::IntrospectionError;
let expected_error = IntrospectionError {
message: Some(r#"Unknown type "Query"."#.to_string()),
};
let response = batch_introspect(
"schema {
query: Query
}",
vec![DEFAULT_INTROSPECTION_QUERY.to_string()],
QueryPlannerConfig::default(),
)
.expect("an uncaught deno error occured")
.expect("a javascript land error happened");
assert_eq!(vec![expected_error], response[0].clone().errors.unwrap());
}
#[test]
fn missing_introspection_query() {
use crate::introspect::IntrospectionError;
let expected_error = IntrospectionError {
message: Some(r#"Unknown type "Query"."#.to_string()),
};
let response = batch_introspect(
"schema {
query: Query
}",
vec![DEFAULT_INTROSPECTION_QUERY.to_string()],
QueryPlannerConfig::default(),
)
.expect("an uncaught deno error occured")
.expect("a javascript land error happened");
assert_eq!(expected_error, response[0].clone().errors.unwrap()[0]);
}
static DEFAULT_INTROSPECTION_QUERY: &str = r#"
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}
fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
description
type {
...TypeRef
}
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}
"#;
#[test]
fn defer_in_introspection() {
let raw_sdl = r#"schema
{
query: Query
}
type Query {
hello: String
}
"#;
let introspected = batch_introspect(
raw_sdl,
vec![r#"query {
__schema {
directives {
name
locations
}
}
}"#
.to_string()],
QueryPlannerConfig::default(),
)
.unwrap();
insta::assert_snapshot!(serde_json::to_string(&introspected).unwrap());
let introspected = batch_introspect(
raw_sdl,
vec![r#"query {
__schema {
directives {
name
locations
}
}
}"#
.to_string()],
QueryPlannerConfig {
incremental_delivery: Some(IncrementalDeliverySupport {
enable_defer: Some(true),
}),
},
)
.unwrap();
insta::assert_snapshot!(serde_json::to_string(&introspected).unwrap());
}
}