use super::{
super::{
FieldDefinition, FieldType, LOCALIZED_INPUT_TYPE, LOCALIZED_STRING_INPUT_TYPE,
LOCALIZED_STRING_TYPE, TRANSLATIONS_SUFFIX,
},
types::{
IntrospectionField, IntrospectionInputValue, IntrospectionType,
IntrospectionValidationRule, TypeKind,
},
};
pub(super) fn build_field(field: &FieldDefinition) -> IntrospectionField {
IntrospectionField {
name: field.output_name().to_string(),
description: field.description.clone(),
args: if field.localized {
vec![locale_argument()]
} else {
vec![]
},
field_type: field_type_to_introspection(
&field.field_type,
field.published_nullable(),
),
is_deprecated: field.is_deprecated(),
deprecation_reason: field.deprecation_reason().map(ToString::to_string),
}
}
pub(super) fn build_translations_field(field: &FieldDefinition) -> IntrospectionField {
let element = non_null(type_ref_with_kind(LOCALIZED_STRING_TYPE, TypeKind::Object));
IntrospectionField {
name: format!("{}{TRANSLATIONS_SUFFIX}", field.output_name()),
description: Some(format!(
"Every allowed locale's label of `{}`, in the order of the allowed locales.",
field.output_name()
)),
args: vec![],
field_type: non_null(IntrospectionType {
kind: TypeKind::List,
name: None,
description: None,
fields: None,
interfaces: None,
possible_types: None,
enum_values: None,
input_fields: None,
of_type: Some(Box::new(element)),
specified_by_u_r_l: None,
}),
is_deprecated: field.is_deprecated(),
deprecation_reason: field.deprecation_reason().map(ToString::to_string),
}
}
pub(super) fn build_localized_string_type() -> IntrospectionType {
let field = |name: &str, description: &str| IntrospectionField {
name: name.to_string(),
description: Some(description.to_string()),
args: vec![],
field_type: non_null(type_ref("String")),
is_deprecated: false,
deprecation_reason: None,
};
IntrospectionType {
kind: TypeKind::Object,
name: Some(LOCALIZED_STRING_TYPE.to_string()),
description: Some("One locale's label of a localized field.".to_string()),
fields: Some(vec![
field("locale", "The locale tag."),
field("value", "The label in that locale."),
]),
interfaces: Some(vec![]),
possible_types: None,
enum_values: None,
input_fields: None,
of_type: None,
specified_by_u_r_l: None,
}
}
pub(super) fn build_localized_input_types() -> [IntrospectionType; 2] {
let value =
|name: &str, description: &str, input_type: IntrospectionType| IntrospectionInputValue {
name: name.to_string(),
description: Some(description.to_string()),
input_type,
default_value: None,
is_deprecated: false,
deprecation_reason: None,
validation_rules: vec![],
};
let input_object =
|name: &str, description: &str, fields: Vec<IntrospectionInputValue>| IntrospectionType {
kind: TypeKind::InputObject,
name: Some(name.to_string()),
description: Some(description.to_string()),
fields: None,
interfaces: None,
possible_types: None,
enum_values: None,
input_fields: Some(fields),
of_type: None,
specified_by_u_r_l: None,
};
let list_of_translations = IntrospectionType {
kind: TypeKind::List,
name: None,
description: None,
fields: None,
interfaces: None,
possible_types: None,
enum_values: None,
input_fields: None,
of_type: Some(Box::new(non_null(type_ref_with_kind(
LOCALIZED_STRING_INPUT_TYPE,
TypeKind::InputObject,
)))),
specified_by_u_r_l: None,
};
[
input_object(
LOCALIZED_INPUT_TYPE,
"A localized value: exactly one of `value` (the request locale's label) and \
`translations`.",
vec![
value("value", "The request locale's label.", type_ref("String")),
value("translations", "Labels by locale.", list_of_translations),
],
),
input_object(
LOCALIZED_STRING_INPUT_TYPE,
"One locale's label; a null value removes it.",
vec![
value("locale", "An allowed locale.", non_null(type_ref("String"))),
value("value", "The label, or null to remove it.", type_ref("String")),
],
),
]
}
fn non_null(inner: IntrospectionType) -> IntrospectionType {
wrap(TypeKind::NonNull, inner)
}
fn locale_argument() -> IntrospectionInputValue {
IntrospectionInputValue {
name: crate::runtime::LOCALE_ARGUMENT.to_string(),
description: Some(
"Read this field in another allowed locale than the request's.".to_string(),
),
input_type: type_ref("String"),
default_value: None,
is_deprecated: false,
deprecation_reason: None,
validation_rules: vec![],
}
}
pub(super) fn field_type_to_introspection(
field_type: &FieldType,
nullable: bool,
) -> IntrospectionType {
let inner = match field_type {
FieldType::Int => type_ref("Int"),
FieldType::Float => type_ref("Float"),
FieldType::String | FieldType::BitVector | FieldType::SparseVector => type_ref("String"),
FieldType::Boolean => type_ref("Boolean"),
FieldType::Id => type_ref("ID"),
FieldType::DateTime => type_ref("DateTime"),
FieldType::Date => type_ref("Date"),
FieldType::Time => type_ref("Time"),
FieldType::Uuid => type_ref("UUID"),
FieldType::Json | FieldType::Vector | FieldType::HalfVector => type_ref("JSON"),
FieldType::Decimal => type_ref("Decimal"),
FieldType::Scalar(name) => type_ref(name), FieldType::Object(name) => type_ref_with_kind(name, TypeKind::Object),
FieldType::Enum(name) => type_ref_with_kind(name, TypeKind::Enum),
FieldType::Input(name) => type_ref_with_kind(name, TypeKind::InputObject),
FieldType::Interface(name) => type_ref_with_kind(name, TypeKind::Interface),
FieldType::Union(name) => type_ref_with_kind(name, TypeKind::Union),
FieldType::List {
item,
nullable_items,
} => wrap(TypeKind::List, field_type_to_introspection(item, *nullable_items)),
};
if nullable { inner } else { non_null(inner) }
}
fn wrap(kind: TypeKind, of_type: IntrospectionType) -> IntrospectionType {
IntrospectionType {
kind,
name: None,
description: None,
fields: None,
interfaces: None,
possible_types: None,
enum_values: None,
input_fields: None,
of_type: Some(Box::new(of_type)),
specified_by_u_r_l: None,
}
}
pub fn type_ref(name: &str) -> IntrospectionType {
type_ref_with_kind(name, TypeKind::Scalar)
}
pub fn type_ref_with_kind(name: &str, kind: TypeKind) -> IntrospectionType {
IntrospectionType {
kind,
name: Some(name.to_string()),
description: None,
fields: None,
interfaces: None,
possible_types: None,
enum_values: None,
input_fields: None,
of_type: None,
specified_by_u_r_l: None,
}
}
pub(super) fn build_validation_rule(
rule: &crate::validation::rules::ValidationRule,
) -> IntrospectionValidationRule {
use crate::validation::rules::ValidationRule;
match rule {
ValidationRule::Required => IntrospectionValidationRule {
rule_type: "required".to_string(),
pattern: None,
pattern_message: None,
min: None,
max: None,
allowed_values: None,
algorithm: None,
field_reference: None,
operator: None,
field_list: None,
description: Some("Field is required".to_string()),
},
ValidationRule::Pattern { pattern, message } => IntrospectionValidationRule {
rule_type: "pattern".to_string(),
pattern: Some(pattern.as_str().to_string()),
pattern_message: message.clone(),
min: None,
max: None,
allowed_values: None,
algorithm: None,
field_reference: None,
operator: None,
field_list: None,
description: message.clone(),
},
ValidationRule::Length { min, max } => IntrospectionValidationRule {
rule_type: "length".to_string(),
pattern: None,
pattern_message: None,
min: min.map(|v| i64::try_from(v).unwrap_or(i64::MAX)),
max: max.map(|v| i64::try_from(v).unwrap_or(i64::MAX)),
allowed_values: None,
algorithm: None,
field_reference: None,
operator: None,
field_list: None,
description: None,
},
ValidationRule::Range { min, max } => IntrospectionValidationRule {
rule_type: "range".to_string(),
pattern: None,
pattern_message: None,
min: *min,
max: *max,
allowed_values: None,
algorithm: None,
field_reference: None,
operator: None,
field_list: None,
description: None,
},
ValidationRule::Enum { values } => IntrospectionValidationRule {
rule_type: "enum".to_string(),
pattern: None,
pattern_message: None,
min: None,
max: None,
allowed_values: Some(values.clone()),
algorithm: None,
field_reference: None,
operator: None,
field_list: None,
description: None,
},
ValidationRule::Checksum { algorithm } => IntrospectionValidationRule {
rule_type: "checksum".to_string(),
pattern: None,
pattern_message: None,
min: None,
max: None,
allowed_values: None,
algorithm: Some(algorithm.clone()),
field_reference: None,
operator: None,
field_list: None,
description: None,
},
ValidationRule::CrossField { field, operator } => IntrospectionValidationRule {
rule_type: "cross_field".to_string(),
pattern: None,
pattern_message: None,
min: None,
max: None,
allowed_values: None,
algorithm: None,
field_reference: Some(field.clone()),
operator: Some(operator.clone()),
field_list: None,
description: None,
},
ValidationRule::Conditional { .. } => IntrospectionValidationRule {
rule_type: "conditional".to_string(),
pattern: None,
pattern_message: None,
min: None,
max: None,
allowed_values: None,
algorithm: None,
field_reference: None,
operator: None,
field_list: None,
description: Some("Conditional validation".to_string()),
},
ValidationRule::All(_) => IntrospectionValidationRule {
rule_type: "all".to_string(),
pattern: None,
pattern_message: None,
min: None,
max: None,
allowed_values: None,
algorithm: None,
field_reference: None,
operator: None,
field_list: None,
description: Some("All rules must pass".to_string()),
},
ValidationRule::Any(_) => IntrospectionValidationRule {
rule_type: "any".to_string(),
pattern: None,
pattern_message: None,
min: None,
max: None,
allowed_values: None,
algorithm: None,
field_reference: None,
operator: None,
field_list: None,
description: Some("At least one rule must pass".to_string()),
},
ValidationRule::OneOf { fields } => IntrospectionValidationRule {
rule_type: "one_of".to_string(),
pattern: None,
pattern_message: None,
min: None,
max: None,
allowed_values: None,
algorithm: None,
field_reference: None,
operator: None,
field_list: Some(fields.clone()),
description: None,
},
ValidationRule::AnyOf { fields } => IntrospectionValidationRule {
rule_type: "any_of".to_string(),
pattern: None,
pattern_message: None,
min: None,
max: None,
allowed_values: None,
algorithm: None,
field_reference: None,
operator: None,
field_list: Some(fields.clone()),
description: None,
},
ValidationRule::ConditionalRequired { .. } => IntrospectionValidationRule {
rule_type: "conditional_required".to_string(),
pattern: None,
pattern_message: None,
min: None,
max: None,
allowed_values: None,
algorithm: None,
field_reference: None,
operator: None,
field_list: None,
description: None,
},
ValidationRule::RequiredIfAbsent { .. } => IntrospectionValidationRule {
rule_type: "required_if_absent".to_string(),
pattern: None,
pattern_message: None,
min: None,
max: None,
allowed_values: None,
algorithm: None,
field_reference: None,
operator: None,
field_list: None,
description: None,
},
ValidationRule::Email => IntrospectionValidationRule {
rule_type: "email".to_string(),
pattern: None,
pattern_message: None,
min: None,
max: None,
allowed_values: None,
algorithm: None,
field_reference: None,
operator: None,
field_list: None,
description: Some("Must be a valid email address".to_string()),
},
ValidationRule::Phone => IntrospectionValidationRule {
rule_type: "phone".to_string(),
pattern: None,
pattern_message: None,
min: None,
max: None,
allowed_values: None,
algorithm: None,
field_reference: None,
operator: None,
field_list: None,
description: Some("Must be a valid E.164 phone number".to_string()),
},
}
}
pub(super) fn build_arg_input_value(
arg: &super::super::ArgumentDefinition,
) -> IntrospectionInputValue {
let localized = FieldType::Input(LOCALIZED_INPUT_TYPE.to_string());
let arg_type = if arg.localized {
&localized
} else {
&arg.arg_type
};
IntrospectionInputValue {
name: arg.name.clone(),
description: arg.description.clone(),
input_type: field_type_to_introspection(arg_type, arg.nullable),
default_value: arg.default_value.as_ref().map(|v| v.to_string()),
is_deprecated: arg.is_deprecated(),
deprecation_reason: arg.deprecation_reason().map(ToString::to_string),
validation_rules: vec![],
}
}