use fraiseql_core::validation::ValidationRule;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct IntermediateSchema {
#[serde(default = "default_version")]
pub version: String,
#[serde(default)]
pub types: Vec<IntermediateType>,
#[serde(default)]
pub enums: Vec<IntermediateEnum>,
#[serde(default)]
pub input_types: Vec<IntermediateInputObject>,
#[serde(default)]
pub interfaces: Vec<IntermediateInterface>,
#[serde(default)]
pub unions: Vec<IntermediateUnion>,
#[serde(default)]
pub queries: Vec<IntermediateQuery>,
#[serde(default)]
pub mutations: Vec<IntermediateMutation>,
#[serde(default)]
pub subscriptions: Vec<IntermediateSubscription>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fragments: Option<Vec<IntermediateFragment>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub directives: Option<Vec<IntermediateDirective>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fact_tables: Option<Vec<IntermediateFactTable>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub aggregate_queries: Option<Vec<IntermediateAggregateQuery>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub observers: Option<Vec<IntermediateObserver>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub custom_scalars: Option<Vec<IntermediateScalar>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub security: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub observers_config: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub federation_config: Option<serde_json::Value>,
}
fn default_version() -> String {
"2.0.0".to_string()
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateType {
pub name: String,
pub fields: Vec<IntermediateField>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub implements: Vec<String>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub is_error: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateField {
pub name: String,
#[serde(rename = "type")]
pub field_type: String,
pub nullable: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub directives: Option<Vec<IntermediateAppliedDirective>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub requires_scope: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateEnum {
pub name: String,
pub values: Vec<IntermediateEnumValue>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateEnumValue {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deprecated: Option<IntermediateDeprecation>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateDeprecation {
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IntermediateScalar {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub specified_by_url: Option<String>,
#[serde(default)]
pub validation_rules: Vec<ValidationRule>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_type: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateInputObject {
pub name: String,
pub fields: Vec<IntermediateInputField>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateInputField {
pub name: String,
#[serde(rename = "type")]
pub field_type: String,
#[serde(default)]
pub nullable: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deprecated: Option<IntermediateDeprecation>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateQuery {
pub name: String,
pub return_type: String,
#[serde(default)]
pub returns_list: bool,
#[serde(default)]
pub nullable: bool,
#[serde(default)]
pub arguments: Vec<IntermediateArgument>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sql_source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_params: Option<IntermediateAutoParams>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deprecated: Option<IntermediateDeprecation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub jsonb_column: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateMutation {
pub name: String,
pub return_type: String,
#[serde(default)]
pub returns_list: bool,
#[serde(default)]
pub nullable: bool,
#[serde(default)]
pub arguments: Vec<IntermediateArgument>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sql_source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub operation: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deprecated: Option<IntermediateDeprecation>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateInterface {
pub name: String,
pub fields: Vec<IntermediateField>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateArgument {
pub name: String,
#[serde(rename = "type")]
pub arg_type: String,
pub nullable: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deprecated: Option<IntermediateDeprecation>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateUnion {
pub name: String,
pub member_types: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateAutoParams {
#[serde(default)]
pub limit: bool,
#[serde(default)]
pub offset: bool,
#[serde(rename = "where", default)]
pub where_clause: bool,
#[serde(default)]
pub order_by: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateSubscription {
pub name: String,
pub return_type: String,
#[serde(default)]
pub arguments: Vec<IntermediateArgument>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub topic: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<IntermediateSubscriptionFilter>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub fields: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deprecated: Option<IntermediateDeprecation>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateSubscriptionFilter {
pub conditions: Vec<IntermediateFilterCondition>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateFilterCondition {
pub argument: String,
pub path: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateFragment {
pub name: String,
#[serde(rename = "on")]
pub type_condition: String,
pub fields: Vec<IntermediateFragmentField>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum IntermediateFragmentField {
Simple(String),
Complex(IntermediateFragmentFieldDef),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateFragmentFieldDef {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub alias: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<Vec<IntermediateFragmentField>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub spread: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub directives: Option<Vec<IntermediateAppliedDirective>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateDirective {
pub name: String,
pub locations: Vec<String>,
#[serde(default)]
pub arguments: Vec<IntermediateArgument>,
#[serde(default)]
pub repeatable: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateAppliedDirective {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arguments: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateFactTable {
pub table_name: String,
pub measures: Vec<IntermediateMeasure>,
pub dimensions: IntermediateDimensions,
pub denormalized_filters: Vec<IntermediateFilter>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateMeasure {
pub name: String,
pub sql_type: String,
pub nullable: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateDimensions {
pub name: String,
pub paths: Vec<IntermediateDimensionPath>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateDimensionPath {
pub name: String,
#[serde(alias = "path")]
pub json_path: String,
#[serde(alias = "type")]
pub data_type: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateFilter {
pub name: String,
pub sql_type: String,
pub indexed: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateAggregateQuery {
pub name: String,
pub fact_table: String,
pub auto_group_by: bool,
pub auto_aggregates: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IntermediateObserver {
pub name: String,
pub entity: String,
pub event: String,
pub actions: Vec<IntermediateObserverAction>,
#[serde(skip_serializing_if = "Option::is_none")]
pub condition: Option<String>,
pub retry: IntermediateRetryConfig,
}
pub type IntermediateObserverAction = serde_json::Value;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntermediateRetryConfig {
pub max_attempts: u32,
pub backoff_strategy: String,
pub initial_delay_ms: u32,
pub max_delay_ms: u32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_minimal_schema() {
let json = r#"{
"types": [],
"queries": [],
"mutations": []
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
assert_eq!(schema.version, "2.0.0");
assert_eq!(schema.types.len(), 0);
assert_eq!(schema.queries.len(), 0);
assert_eq!(schema.mutations.len(), 0);
}
#[test]
fn test_parse_type_with_type_field() {
let json = r#"{
"types": [{
"name": "User",
"fields": [
{
"name": "id",
"type": "Int",
"nullable": false
},
{
"name": "name",
"type": "String",
"nullable": false
}
]
}],
"queries": [],
"mutations": []
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
assert_eq!(schema.types.len(), 1);
assert_eq!(schema.types[0].name, "User");
assert_eq!(schema.types[0].fields.len(), 2);
assert_eq!(schema.types[0].fields[0].name, "id");
assert_eq!(schema.types[0].fields[0].field_type, "Int");
assert!(!schema.types[0].fields[0].nullable);
}
#[test]
fn test_parse_query_with_arguments() {
let json = r#"{
"types": [],
"queries": [{
"name": "users",
"return_type": "User",
"returns_list": true,
"nullable": false,
"arguments": [
{
"name": "limit",
"type": "Int",
"nullable": false,
"default": 10
}
],
"sql_source": "v_user"
}],
"mutations": []
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
assert_eq!(schema.queries.len(), 1);
assert_eq!(schema.queries[0].arguments.len(), 1);
assert_eq!(schema.queries[0].arguments[0].arg_type, "Int");
assert_eq!(schema.queries[0].arguments[0].default, Some(serde_json::json!(10)));
}
#[test]
fn test_parse_fragment_simple() {
let json = r#"{
"types": [],
"queries": [],
"mutations": [],
"fragments": [{
"name": "UserFields",
"on": "User",
"fields": ["id", "name", "email"]
}]
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
assert!(schema.fragments.is_some());
let fragments = schema.fragments.unwrap();
assert_eq!(fragments.len(), 1);
assert_eq!(fragments[0].name, "UserFields");
assert_eq!(fragments[0].type_condition, "User");
assert_eq!(fragments[0].fields.len(), 3);
match &fragments[0].fields[0] {
IntermediateFragmentField::Simple(name) => assert_eq!(name, "id"),
IntermediateFragmentField::Complex(_) => panic!("Expected simple field"),
}
}
#[test]
fn test_parse_fragment_with_nested_fields() {
let json = r#"{
"types": [],
"queries": [],
"mutations": [],
"fragments": [{
"name": "PostFields",
"on": "Post",
"fields": [
"id",
"title",
{
"name": "author",
"alias": "writer",
"fields": ["id", "name"]
}
]
}]
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
let fragments = schema.fragments.unwrap();
assert_eq!(fragments[0].fields.len(), 3);
match &fragments[0].fields[2] {
IntermediateFragmentField::Complex(def) => {
assert_eq!(def.name, "author");
assert_eq!(def.alias, Some("writer".to_string()));
assert!(def.fields.is_some());
assert_eq!(def.fields.as_ref().unwrap().len(), 2);
},
IntermediateFragmentField::Simple(_) => panic!("Expected complex field"),
}
}
#[test]
fn test_parse_directive_definition() {
let json = r#"{
"types": [],
"queries": [],
"mutations": [],
"directives": [{
"name": "auth",
"locations": ["FIELD_DEFINITION", "OBJECT"],
"arguments": [
{"name": "role", "type": "String", "nullable": false}
],
"description": "Requires authentication"
}]
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
assert!(schema.directives.is_some());
let directives = schema.directives.unwrap();
assert_eq!(directives.len(), 1);
assert_eq!(directives[0].name, "auth");
assert_eq!(directives[0].locations, vec!["FIELD_DEFINITION", "OBJECT"]);
assert_eq!(directives[0].arguments.len(), 1);
assert_eq!(directives[0].description, Some("Requires authentication".to_string()));
}
#[test]
fn test_parse_field_with_directive() {
let json = r#"{
"types": [{
"name": "User",
"fields": [
{
"name": "oldId",
"type": "Int",
"nullable": false,
"directives": [
{"name": "deprecated", "arguments": {"reason": "Use 'id' instead"}}
]
}
]
}],
"queries": [],
"mutations": []
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
let field = &schema.types[0].fields[0];
assert_eq!(field.name, "oldId");
assert!(field.directives.is_some());
let directives = field.directives.as_ref().unwrap();
assert_eq!(directives.len(), 1);
assert_eq!(directives[0].name, "deprecated");
assert_eq!(
directives[0].arguments,
Some(serde_json::json!({"reason": "Use 'id' instead"}))
);
}
#[test]
fn test_parse_fragment_with_spread() {
let json = r#"{
"types": [],
"queries": [],
"mutations": [],
"fragments": [
{
"name": "UserFields",
"on": "User",
"fields": ["id", "name"]
},
{
"name": "PostWithAuthor",
"on": "Post",
"fields": [
"id",
"title",
{
"name": "author",
"spread": "UserFields"
}
]
}
]
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
let fragments = schema.fragments.unwrap();
assert_eq!(fragments.len(), 2);
match &fragments[1].fields[2] {
IntermediateFragmentField::Complex(def) => {
assert_eq!(def.name, "author");
assert_eq!(def.spread, Some("UserFields".to_string()));
},
IntermediateFragmentField::Simple(_) => panic!("Expected complex field"),
}
}
#[test]
fn test_parse_enum() {
let json = r#"{
"types": [],
"queries": [],
"mutations": [],
"enums": [{
"name": "OrderStatus",
"values": [
{"name": "PENDING"},
{"name": "PROCESSING", "description": "Currently being processed"},
{"name": "SHIPPED"},
{"name": "DELIVERED"}
],
"description": "Possible states of an order"
}]
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
assert_eq!(schema.enums.len(), 1);
let enum_def = &schema.enums[0];
assert_eq!(enum_def.name, "OrderStatus");
assert_eq!(enum_def.description, Some("Possible states of an order".to_string()));
assert_eq!(enum_def.values.len(), 4);
assert_eq!(enum_def.values[0].name, "PENDING");
assert_eq!(enum_def.values[1].description, Some("Currently being processed".to_string()));
}
#[test]
fn test_parse_enum_with_deprecated_value() {
let json = r#"{
"types": [],
"queries": [],
"mutations": [],
"enums": [{
"name": "UserRole",
"values": [
{"name": "ADMIN"},
{"name": "USER"},
{"name": "GUEST", "deprecated": {"reason": "Use USER with limited permissions instead"}}
]
}]
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
let enum_def = &schema.enums[0];
assert_eq!(enum_def.values.len(), 3);
let guest = &enum_def.values[2];
assert_eq!(guest.name, "GUEST");
assert!(guest.deprecated.is_some());
assert_eq!(
guest.deprecated.as_ref().unwrap().reason,
Some("Use USER with limited permissions instead".to_string())
);
}
#[test]
fn test_parse_input_object() {
let json = r#"{
"types": [],
"queries": [],
"mutations": [],
"input_types": [{
"name": "UserFilter",
"fields": [
{"name": "name", "type": "String", "nullable": true},
{"name": "email", "type": "String", "nullable": true},
{"name": "active", "type": "Boolean", "nullable": true, "default": true}
],
"description": "Filter criteria for users"
}]
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
assert_eq!(schema.input_types.len(), 1);
let input = &schema.input_types[0];
assert_eq!(input.name, "UserFilter");
assert_eq!(input.description, Some("Filter criteria for users".to_string()));
assert_eq!(input.fields.len(), 3);
assert_eq!(input.fields[0].name, "name");
assert_eq!(input.fields[0].field_type, "String");
assert!(input.fields[0].nullable);
assert_eq!(input.fields[2].name, "active");
assert_eq!(input.fields[2].default, Some(serde_json::json!(true)));
}
#[test]
fn test_parse_interface() {
let json = r#"{
"types": [],
"queries": [],
"mutations": [],
"interfaces": [{
"name": "Node",
"fields": [
{"name": "id", "type": "ID", "nullable": false}
],
"description": "An object with a globally unique ID"
}]
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
assert_eq!(schema.interfaces.len(), 1);
let interface = &schema.interfaces[0];
assert_eq!(interface.name, "Node");
assert_eq!(interface.description, Some("An object with a globally unique ID".to_string()));
assert_eq!(interface.fields.len(), 1);
assert_eq!(interface.fields[0].name, "id");
assert_eq!(interface.fields[0].field_type, "ID");
assert!(!interface.fields[0].nullable);
}
#[test]
fn test_parse_type_implements_interface() {
let json = r#"{
"types": [{
"name": "User",
"fields": [
{"name": "id", "type": "ID", "nullable": false},
{"name": "name", "type": "String", "nullable": false}
],
"implements": ["Node"]
}],
"queries": [],
"mutations": [],
"interfaces": [{
"name": "Node",
"fields": [
{"name": "id", "type": "ID", "nullable": false}
]
}]
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
assert_eq!(schema.types.len(), 1);
assert_eq!(schema.types[0].name, "User");
assert_eq!(schema.types[0].implements, vec!["Node"]);
assert_eq!(schema.interfaces.len(), 1);
assert_eq!(schema.interfaces[0].name, "Node");
}
#[test]
fn test_parse_input_object_with_deprecated_field() {
let json = r#"{
"types": [],
"queries": [],
"mutations": [],
"input_types": [{
"name": "CreateUserInput",
"fields": [
{"name": "email", "type": "String!", "nullable": false},
{"name": "name", "type": "String!", "nullable": false},
{
"name": "username",
"type": "String",
"nullable": true,
"deprecated": {"reason": "Use email as unique identifier instead"}
}
]
}]
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
let input = &schema.input_types[0];
let username_field = &input.fields[2];
assert_eq!(username_field.name, "username");
assert!(username_field.deprecated.is_some());
assert_eq!(
username_field.deprecated.as_ref().unwrap().reason,
Some("Use email as unique identifier instead".to_string())
);
}
#[test]
fn test_parse_union() {
let json = r#"{
"types": [
{"name": "User", "fields": [{"name": "id", "type": "ID", "nullable": false}]},
{"name": "Post", "fields": [{"name": "id", "type": "ID", "nullable": false}]}
],
"queries": [],
"mutations": [],
"unions": [{
"name": "SearchResult",
"member_types": ["User", "Post"],
"description": "Result from a search query"
}]
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
assert_eq!(schema.unions.len(), 1);
let union_def = &schema.unions[0];
assert_eq!(union_def.name, "SearchResult");
assert_eq!(union_def.member_types, vec!["User", "Post"]);
assert_eq!(union_def.description, Some("Result from a search query".to_string()));
}
#[test]
fn test_parse_field_with_requires_scope() {
let json = r#"{
"types": [{
"name": "Employee",
"fields": [
{
"name": "id",
"type": "ID",
"nullable": false
},
{
"name": "name",
"type": "String",
"nullable": false
},
{
"name": "salary",
"type": "Float",
"nullable": false,
"description": "Employee salary - protected field",
"requires_scope": "read:Employee.salary"
},
{
"name": "ssn",
"type": "String",
"nullable": true,
"description": "Social Security Number",
"requires_scope": "admin"
}
]
}],
"queries": [],
"mutations": []
}"#;
let schema: IntermediateSchema = serde_json::from_str(json).unwrap();
assert_eq!(schema.types.len(), 1);
let employee = &schema.types[0];
assert_eq!(employee.name, "Employee");
assert_eq!(employee.fields.len(), 4);
assert_eq!(employee.fields[0].name, "id");
assert!(employee.fields[0].requires_scope.is_none());
assert_eq!(employee.fields[1].name, "name");
assert!(employee.fields[1].requires_scope.is_none());
assert_eq!(employee.fields[2].name, "salary");
assert_eq!(employee.fields[2].requires_scope, Some("read:Employee.salary".to_string()));
assert_eq!(employee.fields[3].name, "ssn");
assert_eq!(employee.fields[3].requires_scope, Some("admin".to_string()));
}
}