Add executor tests, fix a bunch of bugs
This commit is contained in:
parent
251f957a7f
commit
c555241978
6 changed files with 414 additions and 9 deletions
|
@ -1,6 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use ::GraphQLError;
|
||||||
use ast::{InputValue, ToInputValue, Document, Selection, Fragment, Definition, Type, FromInputValue, OperationType};
|
use ast::{InputValue, ToInputValue, Document, Selection, Fragment, Definition, Type, FromInputValue, OperationType};
|
||||||
use value::Value;
|
use value::Value;
|
||||||
use parser::SourcePosition;
|
use parser::SourcePosition;
|
||||||
|
@ -198,6 +199,15 @@ impl<'a> FieldPath<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutionError {
|
impl ExecutionError {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn new(location: SourcePosition, path: &[&str], message: &str) -> ExecutionError {
|
||||||
|
ExecutionError {
|
||||||
|
location: location,
|
||||||
|
path: path.iter().map(|s| (*s).to_owned()).collect(),
|
||||||
|
message: message.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The error message
|
/// The error message
|
||||||
pub fn message(&self) -> &str {
|
pub fn message(&self) -> &str {
|
||||||
&self.message
|
&self.message
|
||||||
|
@ -214,16 +224,16 @@ impl ExecutionError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute_validated_query<QueryT, MutationT, CtxT>(
|
pub fn execute_validated_query<'a, QueryT, MutationT, CtxT>(
|
||||||
document: Document,
|
document: Document,
|
||||||
operation_name: Option<&str>,
|
operation_name: Option<&str>,
|
||||||
root_node: &RootNode<CtxT, QueryT, MutationT>,
|
root_node: &RootNode<CtxT, QueryT, MutationT>,
|
||||||
variables: &HashMap<String, InputValue>,
|
variables: &HashMap<String, InputValue>,
|
||||||
context: &CtxT
|
context: &CtxT
|
||||||
)
|
)
|
||||||
-> (Value, Vec<ExecutionError>)
|
-> Result<(Value, Vec<ExecutionError>), GraphQLError<'a>>
|
||||||
where QueryT: GraphQLType<CtxT>,
|
where QueryT: GraphQLType<CtxT>,
|
||||||
MutationT: GraphQLType<CtxT>,
|
MutationT: GraphQLType<CtxT>
|
||||||
{
|
{
|
||||||
let mut fragments = vec![];
|
let mut fragments = vec![];
|
||||||
let mut operation = None;
|
let mut operation = None;
|
||||||
|
@ -232,7 +242,7 @@ pub fn execute_validated_query<QueryT, MutationT, CtxT>(
|
||||||
match def {
|
match def {
|
||||||
Definition::Operation(op) => {
|
Definition::Operation(op) => {
|
||||||
if operation_name.is_none() && operation.is_some() {
|
if operation_name.is_none() && operation.is_some() {
|
||||||
panic!("Must provide operation name if query contains multiple operations");
|
return Err(GraphQLError::MultipleOperationsProvided);
|
||||||
}
|
}
|
||||||
|
|
||||||
let move_op = operation_name.is_none()
|
let move_op = operation_name.is_none()
|
||||||
|
@ -246,7 +256,11 @@ pub fn execute_validated_query<QueryT, MutationT, CtxT>(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let op = operation.expect("Could not find operation to execute");
|
let op = match operation {
|
||||||
|
Some(op) => op,
|
||||||
|
None => return Err(GraphQLError::UnknownOperationName),
|
||||||
|
};
|
||||||
|
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
let value;
|
let value;
|
||||||
|
|
||||||
|
@ -269,7 +283,7 @@ pub fn execute_validated_query<QueryT, MutationT, CtxT>(
|
||||||
|
|
||||||
errors.sort();
|
errors.sort();
|
||||||
|
|
||||||
(value, errors)
|
Ok((value, errors))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<CtxT> Registry<CtxT> {
|
impl<CtxT> Registry<CtxT> {
|
||||||
|
|
332
src/executor_tests/executor.rs
Normal file
332
src/executor_tests/executor.rs
Normal file
|
@ -0,0 +1,332 @@
|
||||||
|
mod field_execution {
|
||||||
|
use value::Value;
|
||||||
|
use ast::InputValue;
|
||||||
|
use schema::model::RootNode;
|
||||||
|
|
||||||
|
struct DataType;
|
||||||
|
struct DeepDataType;
|
||||||
|
|
||||||
|
graphql_object!(DataType: () |&self| {
|
||||||
|
field a() -> &str { "Apple" }
|
||||||
|
field b() -> &str { "Banana" }
|
||||||
|
field c() -> &str { "Cookie" }
|
||||||
|
field d() -> &str { "Donut" }
|
||||||
|
field e() -> &str { "Egg" }
|
||||||
|
field f() -> &str { "Fish" }
|
||||||
|
|
||||||
|
field pic(size: Option<i64>) -> String {
|
||||||
|
format!("Pic of size: {}", size.unwrap_or(50))
|
||||||
|
}
|
||||||
|
|
||||||
|
field deep() -> DeepDataType {
|
||||||
|
DeepDataType
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
graphql_object!(DeepDataType: () |&self| {
|
||||||
|
field a() -> &str { "Already Been Done" }
|
||||||
|
field b() -> &str { "Boring" }
|
||||||
|
field c() -> &[Option<&str>] { &[Some("Contrived"), None, Some("Confusing")] }
|
||||||
|
|
||||||
|
field deeper() -> &[Option<DataType>] { &[Some(DataType), None, Some(DataType) ] }
|
||||||
|
});
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let schema = RootNode::new(DataType, ());
|
||||||
|
let doc = r"
|
||||||
|
query Example($size: Int) {
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
x: c
|
||||||
|
...c
|
||||||
|
f
|
||||||
|
...on DataType {
|
||||||
|
pic(size: $size)
|
||||||
|
}
|
||||||
|
deep {
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
deeper {
|
||||||
|
a
|
||||||
|
b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment c on DataType {
|
||||||
|
d
|
||||||
|
e
|
||||||
|
}";
|
||||||
|
|
||||||
|
let vars = vec![
|
||||||
|
("size".to_owned(), InputValue::int(100))
|
||||||
|
].into_iter().collect();
|
||||||
|
|
||||||
|
let (result, errs) = ::execute(doc, None, &schema, &vars, &())
|
||||||
|
.expect("Execution failed");
|
||||||
|
|
||||||
|
assert_eq!(errs, []);
|
||||||
|
|
||||||
|
println!("Result: {:?}", result);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Value::object(vec![
|
||||||
|
("a", Value::string("Apple")),
|
||||||
|
("b", Value::string("Banana")),
|
||||||
|
("x", Value::string("Cookie")),
|
||||||
|
("d", Value::string("Donut")),
|
||||||
|
("e", Value::string("Egg")),
|
||||||
|
("f", Value::string("Fish")),
|
||||||
|
("pic", Value::string("Pic of size: 100")),
|
||||||
|
("deep", Value::object(vec![
|
||||||
|
("a", Value::string("Already Been Done")),
|
||||||
|
("b", Value::string("Boring")),
|
||||||
|
("c", Value::list(vec![
|
||||||
|
Value::string("Contrived"),
|
||||||
|
Value::null(),
|
||||||
|
Value::string("Confusing"),
|
||||||
|
])),
|
||||||
|
("deeper", Value::list(vec![
|
||||||
|
Value::object(vec![
|
||||||
|
("a", Value::string("Apple")),
|
||||||
|
("b", Value::string("Banana")),
|
||||||
|
].into_iter().collect()),
|
||||||
|
Value::null(),
|
||||||
|
Value::object(vec![
|
||||||
|
("a", Value::string("Apple")),
|
||||||
|
("b", Value::string("Banana")),
|
||||||
|
].into_iter().collect()),
|
||||||
|
])),
|
||||||
|
].into_iter().collect())),
|
||||||
|
].into_iter().collect()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mod merge_parallel_fragments {
|
||||||
|
use value::Value;
|
||||||
|
use schema::model::RootNode;
|
||||||
|
|
||||||
|
struct Type;
|
||||||
|
|
||||||
|
graphql_object!(Type: () |&self| {
|
||||||
|
field a() -> &str { "Apple" }
|
||||||
|
field b() -> &str { "Banana" }
|
||||||
|
field c() -> &str { "Cherry" }
|
||||||
|
field deep() -> Type { Type }
|
||||||
|
});
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let schema = RootNode::new(Type, ());
|
||||||
|
let doc = r"
|
||||||
|
{ a, ...FragOne, ...FragTwo }
|
||||||
|
fragment FragOne on Type {
|
||||||
|
b
|
||||||
|
deep { b, deeper: deep { b } }
|
||||||
|
}
|
||||||
|
fragment FragTwo on Type {
|
||||||
|
c
|
||||||
|
deep { c, deeper: deep { c } }
|
||||||
|
}";
|
||||||
|
|
||||||
|
let vars = vec![].into_iter().collect();
|
||||||
|
|
||||||
|
let (result, errs) = ::execute(doc, None, &schema, &vars, &())
|
||||||
|
.expect("Execution failed");
|
||||||
|
|
||||||
|
assert_eq!(errs, []);
|
||||||
|
|
||||||
|
println!("Result: {:?}", result);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Value::object(vec![
|
||||||
|
("a", Value::string("Apple")),
|
||||||
|
("b", Value::string("Banana")),
|
||||||
|
("c", Value::string("Cherry")),
|
||||||
|
("deep", Value::object(vec![
|
||||||
|
("b", Value::string("Banana")),
|
||||||
|
("c", Value::string("Cherry")),
|
||||||
|
("deeper", Value::object(vec![
|
||||||
|
("b", Value::string("Banana")),
|
||||||
|
("c", Value::string("Cherry")),
|
||||||
|
].into_iter().collect())),
|
||||||
|
].into_iter().collect())),
|
||||||
|
].into_iter().collect()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod threads_context_correctly {
|
||||||
|
use value::Value;
|
||||||
|
use schema::model::RootNode;
|
||||||
|
|
||||||
|
struct Schema;
|
||||||
|
|
||||||
|
graphql_object!(Schema: String |&self| {
|
||||||
|
field a(&mut executor) -> String { executor.context().clone() }
|
||||||
|
});
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let schema = RootNode::new(Schema, ());
|
||||||
|
let doc = r"{ a }";
|
||||||
|
|
||||||
|
let vars = vec![].into_iter().collect();
|
||||||
|
|
||||||
|
let (result, errs) = ::execute(doc, None, &schema, &vars, &"Context value".to_owned())
|
||||||
|
.expect("Execution failed");
|
||||||
|
|
||||||
|
assert_eq!(errs, []);
|
||||||
|
|
||||||
|
println!("Result: {:?}", result);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Value::object(vec![
|
||||||
|
("a", Value::string("Context value")),
|
||||||
|
].into_iter().collect()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod nulls_out_errors {
|
||||||
|
use value::Value;
|
||||||
|
use schema::model::RootNode;
|
||||||
|
use executor::{ExecutionError, FieldResult};
|
||||||
|
use parser::SourcePosition;
|
||||||
|
|
||||||
|
struct Schema;
|
||||||
|
|
||||||
|
graphql_object!(Schema: () |&self| {
|
||||||
|
field sync() -> FieldResult<&str> { Ok("sync") }
|
||||||
|
field sync_error() -> FieldResult<&str> { Err("Error for syncError".to_owned()) }
|
||||||
|
});
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let schema = RootNode::new(Schema, ());
|
||||||
|
let doc = r"{ sync, syncError }";
|
||||||
|
|
||||||
|
let vars = vec![].into_iter().collect();
|
||||||
|
|
||||||
|
let (result, errs) = ::execute(doc, None, &schema, &vars, &())
|
||||||
|
.expect("Execution failed");
|
||||||
|
|
||||||
|
println!("Result: {:?}", result);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Value::object(vec![
|
||||||
|
("sync", Value::string("sync")),
|
||||||
|
("syncError", Value::null()),
|
||||||
|
].into_iter().collect()));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
errs,
|
||||||
|
vec![
|
||||||
|
ExecutionError::new(
|
||||||
|
SourcePosition::new(8, 0, 8),
|
||||||
|
&["syncError"],
|
||||||
|
"Error for syncError",
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod named_operations {
|
||||||
|
use value::Value;
|
||||||
|
use schema::model::RootNode;
|
||||||
|
use ::GraphQLError;
|
||||||
|
|
||||||
|
struct Schema;
|
||||||
|
|
||||||
|
graphql_object!(Schema: () |&self| {
|
||||||
|
field a() -> &str { "b" }
|
||||||
|
});
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uses_inline_operation_if_no_name_provided() {
|
||||||
|
let schema = RootNode::new(Schema, ());
|
||||||
|
let doc = r"{ a }";
|
||||||
|
|
||||||
|
let vars = vec![].into_iter().collect();
|
||||||
|
|
||||||
|
let (result, errs) = ::execute(doc, None, &schema, &vars, &())
|
||||||
|
.expect("Execution failed");
|
||||||
|
|
||||||
|
assert_eq!(errs, []);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Value::object(vec![
|
||||||
|
("a", Value::string("b")),
|
||||||
|
].into_iter().collect()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uses_only_named_operation() {
|
||||||
|
let schema = RootNode::new(Schema, ());
|
||||||
|
let doc = r"query Example { a }";
|
||||||
|
|
||||||
|
let vars = vec![].into_iter().collect();
|
||||||
|
|
||||||
|
let (result, errs) = ::execute(doc, None, &schema, &vars, &())
|
||||||
|
.expect("Execution failed");
|
||||||
|
|
||||||
|
assert_eq!(errs, []);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Value::object(vec![
|
||||||
|
("a", Value::string("b")),
|
||||||
|
].into_iter().collect()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uses_named_operation_if_name_provided() {
|
||||||
|
let schema = RootNode::new(Schema, ());
|
||||||
|
let doc = r"query Example { first: a } query OtherExample { second: a }";
|
||||||
|
|
||||||
|
let vars = vec![].into_iter().collect();
|
||||||
|
|
||||||
|
let (result, errs) = ::execute(doc, Some("OtherExample"), &schema, &vars, &())
|
||||||
|
.expect("Execution failed");
|
||||||
|
|
||||||
|
assert_eq!(errs, []);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Value::object(vec![
|
||||||
|
("second", Value::string("b")),
|
||||||
|
].into_iter().collect()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_if_multiple_operations_provided_but_no_name() {
|
||||||
|
let schema = RootNode::new(Schema, ());
|
||||||
|
let doc = r"query Example { first: a } query OtherExample { second: a }";
|
||||||
|
|
||||||
|
let vars = vec![].into_iter().collect();
|
||||||
|
|
||||||
|
let err = ::execute(doc, None, &schema, &vars, &())
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(err, GraphQLError::MultipleOperationsProvided);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_if_unknown_operation_name_provided() {
|
||||||
|
let schema = RootNode::new(Schema, ());
|
||||||
|
let doc = r"query Example { first: a } query OtherExample { second: a }";
|
||||||
|
|
||||||
|
let vars = vec![].into_iter().collect();
|
||||||
|
|
||||||
|
let err = ::execute(doc, Some("UnknownExample"), &schema, &vars, &())
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(err, GraphQLError::UnknownOperationName);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,3 +2,4 @@ mod introspection;
|
||||||
mod variables;
|
mod variables;
|
||||||
mod enums;
|
mod enums;
|
||||||
mod directives;
|
mod directives;
|
||||||
|
mod executor;
|
||||||
|
|
11
src/lib.rs
11
src/lib.rs
|
@ -226,6 +226,9 @@ pub use schema::meta;
|
||||||
pub enum GraphQLError<'a> {
|
pub enum GraphQLError<'a> {
|
||||||
ParseError(Spanning<ParseError<'a>>),
|
ParseError(Spanning<ParseError<'a>>),
|
||||||
ValidationError(Vec<RuleError>),
|
ValidationError(Vec<RuleError>),
|
||||||
|
NoOperationProvided,
|
||||||
|
MultipleOperationsProvided,
|
||||||
|
UnknownOperationName,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a query in a provided schema
|
/// Execute a query in a provided schema
|
||||||
|
@ -260,7 +263,7 @@ pub fn execute<'a, CtxT, QueryT, MutationT>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(execute_validated_query(document, operation_name, root_node, variables, context))
|
execute_validated_query(document, operation_name, root_node, variables, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<Spanning<ParseError<'a>>> for GraphQLError<'a> {
|
impl<'a> From<Spanning<ParseError<'a>>> for GraphQLError<'a> {
|
||||||
|
@ -274,6 +277,12 @@ impl<'a> ToJson for GraphQLError<'a> {
|
||||||
let errs = match *self {
|
let errs = match *self {
|
||||||
GraphQLError::ParseError(ref err) => parse_error_to_json(err),
|
GraphQLError::ParseError(ref err) => parse_error_to_json(err),
|
||||||
GraphQLError::ValidationError(ref errs) => errs.to_json(),
|
GraphQLError::ValidationError(ref errs) => errs.to_json(),
|
||||||
|
GraphQLError::MultipleOperationsProvided => Json::String(
|
||||||
|
"Must provide operation name if query contains multiple operations.".to_owned()),
|
||||||
|
GraphQLError::NoOperationProvided => Json::String(
|
||||||
|
"Must provide an operation".to_owned()),
|
||||||
|
GraphQLError::UnknownOperationName => Json::String(
|
||||||
|
"Unknown operation".to_owned()),
|
||||||
};
|
};
|
||||||
|
|
||||||
Json::Object(vec![
|
Json::Object(vec![
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
|
||||||
use ast::{InputValue, Selection, Directive, FromInputValue};
|
use ast::{InputValue, Selection, Directive, FromInputValue};
|
||||||
use value::Value;
|
use value::Value;
|
||||||
|
@ -324,8 +325,11 @@ fn resolve_selection_set_into<T, CtxT>(
|
||||||
&mut sub_exec);
|
&mut sub_exec);
|
||||||
|
|
||||||
match field_result {
|
match field_result {
|
||||||
Ok(v) => { result.insert(response_name.clone(), v); }
|
Ok(v) => merge_key_into(result, response_name.clone(), v),
|
||||||
Err(e) => { sub_exec.push_error(e, start_pos); }
|
Err(e) => {
|
||||||
|
sub_exec.push_error(e, start_pos);
|
||||||
|
result.insert(response_name.clone(), Value::null());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Selection::FragmentSpread(Spanning { item: spread, .. }) => {
|
Selection::FragmentSpread(Spanning { item: spread, .. }) => {
|
||||||
|
@ -408,3 +412,40 @@ fn is_excluded(directives: &Option<Vec<Directive>>, vars: &HashMap<String, Input
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn merge_key_into(
|
||||||
|
result: &mut HashMap<String, Value>,
|
||||||
|
response_name: String,
|
||||||
|
value: Value,
|
||||||
|
) {
|
||||||
|
match result.entry(response_name) {
|
||||||
|
Entry::Occupied(mut e) => {
|
||||||
|
println!("Merging object at '{}'", e.key());
|
||||||
|
match (e.get_mut().as_mut_object_value(), value) {
|
||||||
|
(Some(dest_obj), Value::Object(src_obj)) => {
|
||||||
|
merge_maps(dest_obj, src_obj);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
println!("Not merging object/object - this is bad :(");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Entry::Vacant(e) => {
|
||||||
|
e.insert(value);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_maps(
|
||||||
|
dest: &mut HashMap<String, Value>,
|
||||||
|
src: HashMap<String, Value>,
|
||||||
|
) {
|
||||||
|
for (key, value) in src {
|
||||||
|
if dest.contains_key(&key) {
|
||||||
|
merge_key_into(dest, key, value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dest.insert(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -75,6 +75,14 @@ impl Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mutable view into the underlying object value, if present.
|
||||||
|
pub fn as_mut_object_value(&mut self) -> Option<&mut HashMap<String, Value>> {
|
||||||
|
match *self {
|
||||||
|
Value::Object(ref mut o) => Some(o),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// View the underlying list value, if present.
|
/// View the underlying list value, if present.
|
||||||
pub fn as_list_value(&self) -> Option<&Vec<Value>> {
|
pub fn as_list_value(&self) -> Option<&Vec<Value>> {
|
||||||
match *self {
|
match *self {
|
||||||
|
|
Loading…
Reference in a new issue