Skip to content

Commit

Permalink
feat: support detailed configuration on scalarTypes (#41)
Browse files Browse the repository at this point in the history
* feat: support other shapes of ScalarTypeConfig

* feat: update signature of nitrogql_ts_type

* feat: update graphql-scalars plugin support

* feat: change type of the ID scalar

* feat: reflect ScalarTypeConfig to TypeScript type definition

* docs(website): configuration/scalar-types

* chore: fix example
  • Loading branch information
uhyo authored Jan 2, 2024
1 parent 3358442 commit 4973be7
Showing 18 changed files with 840 additions and 66 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 11 additions & 3 deletions crates/cli/src/builtins.rs
Original file line number Diff line number Diff line change
@@ -17,18 +17,26 @@ pub fn nitrogql_builtins() -> Vec<TypeSystemDefinitionOrExtension<'static>> {
name: ident("nitrogql_ts_type"),
description: None,
arguments: Some(ArgumentsDefinition {
input_values: vec![InputValueDefinition {
input_values: [
"resolverInput",
"resolverOutput",
"operationInput",
"operationOutput",
]
.into_iter()
.map(|name| InputValueDefinition {
description: None,
position: Pos::builtin(),
name: ident("type"),
name: ident(name),
r#type: Type::NonNull(Box::new(NonNullType {
r#type: Type::Named(NamedType {
name: ident("String"),
}),
})),
default_value: None,
directives: vec![],
}],
})
.collect(),
}),
repeatable: None,
locations: vec![ident("SCALAR")],
3 changes: 3 additions & 0 deletions crates/config-file/Cargo.toml
Original file line number Diff line number Diff line change
@@ -17,3 +17,6 @@ serde = { version = "1.0.160", features = ["derive"] }
serde_yaml = "0.9.21"
thiserror = "1.0.40"
log = "0.4.17"

[dev-dependencies]
serde_json = "1.0.94"
2 changes: 1 addition & 1 deletion crates/config-file/src/lib.rs
Original file line number Diff line number Diff line change
@@ -21,5 +21,5 @@ pub use load_config::load_config;
#[cfg(feature = "execute_js")]
pub use node::{load_default_from_js_file, run_node};
pub use parse_config::parse_config;
pub use scalar_type::ScalarTypeConfig;
pub use scalar_type::{ScalarTypeConfig, SendReceiveScalarTypeConfig, SeparateScalarTypeConfig};
pub use type_target::TypeTarget;
209 changes: 177 additions & 32 deletions crates/config-file/src/scalar_type.rs
Original file line number Diff line number Diff line change
@@ -4,57 +4,202 @@ use crate::TypeTarget;

/// Representation of a scalar type's TypeScript type
/// as defined in the config file.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(untagged)]
pub enum ScalarTypeConfig {
/// Single specification for use in all situations.
Single(String),
/// Specification as a pair of send type and receive type.
SendReceive(SendReceiveScalarTypeConfig),
/// Specification as four diffrent types.
Separate(SeparateScalarTypeConfig),
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SendReceiveScalarTypeConfig {
pub send: String,
pub receive: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SeparateScalarTypeConfig {
pub resolver_output: String,
pub resolver_input: String,
pub operation_output: String,
pub operation_input: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SeparateScalarTypeConfigRef<'a> {
pub resolver_output: &'a str,
pub resolver_input: &'a str,
pub operation_output: &'a str,
pub operation_input: &'a str,
}

impl ScalarTypeConfig {
/// Get the TypeScript type for given target.
pub fn get_type(&self, _target: TypeTarget) -> &str {
pub fn get_type(&self, target: TypeTarget) -> &str {
match self {
ScalarTypeConfig::Single(type_name) => type_name,
ScalarTypeConfig::SendReceive(config) => match target {
TypeTarget::ResolverOutput => &config.send,
TypeTarget::ResolverInput => &config.receive,
TypeTarget::OperationOutput => &config.receive,
TypeTarget::OperationInput => &config.send,
},
ScalarTypeConfig::Separate(config) => match target {
TypeTarget::ResolverOutput => &config.resolver_output,
TypeTarget::ResolverInput => &config.resolver_input,
TypeTarget::OperationOutput => &config.operation_output,
TypeTarget::OperationInput => &config.operation_input,
},
}
}
/// Get the TypeScript type as a resolver output type.
pub fn as_resolver_output_type(&self) -> &str {
match self {
ScalarTypeConfig::Single(type_name) => type_name,
}
}
/// Get the TypeScript type as a resolver input type.
pub fn as_resolver_input_type(&self) -> &str {
/// Returns an Iterator over all type names used in this config.
pub fn type_names(&self) -> impl Iterator<Item = &str> {
match self {
ScalarTypeConfig::Single(type_name) => type_name,
ScalarTypeConfig::Single(type_name) => vec![(type_name.as_str())].into_iter(),
ScalarTypeConfig::SendReceive(config) => {
vec![config.send.as_str(), config.receive.as_str()].into_iter()
}
ScalarTypeConfig::Separate(config) => vec![
config.resolver_output.as_str(),
config.resolver_input.as_str(),
config.operation_output.as_str(),
config.operation_input.as_str(),
]
.into_iter(),
}
}
/// Get the TypeScript type as an operation output type.
pub fn as_operation_output_type(&self) -> &str {
/// Returns this config represented as four separate types.
pub fn separate_ref(&self) -> SeparateScalarTypeConfigRef {
match self {
ScalarTypeConfig::Single(type_name) => type_name,
ScalarTypeConfig::Single(type_name) => SeparateScalarTypeConfigRef {
resolver_output: type_name.as_str(),
resolver_input: type_name.as_str(),
operation_output: type_name.as_str(),
operation_input: type_name.as_str(),
},
ScalarTypeConfig::SendReceive(config) => SeparateScalarTypeConfigRef {
resolver_output: config.send.as_str(),
resolver_input: config.receive.as_str(),
operation_output: config.receive.as_str(),
operation_input: config.send.as_str(),
},
ScalarTypeConfig::Separate(config) => SeparateScalarTypeConfigRef {
resolver_output: config.resolver_output.as_str(),
resolver_input: config.resolver_input.as_str(),
operation_output: config.operation_output.as_str(),
operation_input: config.operation_input.as_str(),
},
}
}
/// Get the TypeScript type as an operation input type.
pub fn as_operation_input_type(&self) -> &str {
match self {
ScalarTypeConfig::Single(type_name) => type_name,
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_scalar_type_config() {
let config = ScalarTypeConfig::Single("string".to_string());
assert_eq!(config.get_type(TypeTarget::ResolverOutput), "string");
assert_eq!(config.get_type(TypeTarget::ResolverInput), "string");
assert_eq!(config.get_type(TypeTarget::OperationOutput), "string");
assert_eq!(config.get_type(TypeTarget::OperationInput), "string");

let config = ScalarTypeConfig::SendReceive(SendReceiveScalarTypeConfig {
send: "string".to_string(),
receive: "number".to_string(),
});
assert_eq!(config.get_type(TypeTarget::ResolverOutput), "string");
assert_eq!(config.get_type(TypeTarget::ResolverInput), "number");
assert_eq!(config.get_type(TypeTarget::OperationOutput), "number");
assert_eq!(config.get_type(TypeTarget::OperationInput), "string");

let config = ScalarTypeConfig::Separate(SeparateScalarTypeConfig {
resolver_output: "string".to_string(),
resolver_input: "number".to_string(),
operation_output: "boolean".to_string(),
operation_input: "bigint".to_string(),
});
assert_eq!(config.get_type(TypeTarget::ResolverOutput), "string");
assert_eq!(config.get_type(TypeTarget::ResolverInput), "number");
assert_eq!(config.get_type(TypeTarget::OperationOutput), "boolean");
assert_eq!(config.get_type(TypeTarget::OperationInput), "bigint");
}
/// Returns an Iterator over all type names used in this config.
pub fn type_names(&self) -> impl Iterator<Item = &str> {
match self {
ScalarTypeConfig::Single(type_name) => std::iter::once(type_name.as_str()),
}

#[test]
fn test_scalar_type_config_type_names() {
let config = ScalarTypeConfig::Single("string".to_string());
assert_eq!(
config.type_names().collect::<Vec<_>>(),
vec!["string"].into_iter().collect::<Vec<_>>()
);

let config = ScalarTypeConfig::SendReceive(SendReceiveScalarTypeConfig {
send: "string".to_string(),
receive: "number".to_string(),
});
assert_eq!(
config.type_names().collect::<Vec<_>>(),
vec!["string", "number"].into_iter().collect::<Vec<_>>()
);

let config = ScalarTypeConfig::Separate(SeparateScalarTypeConfig {
resolver_output: "string".to_string(),
resolver_input: "number".to_string(),
operation_output: "boolean".to_string(),
operation_input: "bigint".to_string(),
});
assert_eq!(
config.type_names().collect::<Vec<_>>(),
vec!["string", "number", "boolean", "bigint"]
.into_iter()
.collect::<Vec<_>>()
);
}
}

impl<'de> Deserialize<'de> for ScalarTypeConfig {
fn deserialize<D>(deserializer: D) -> Result<ScalarTypeConfig, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(ScalarTypeConfig::Single(s))
#[test]
fn parsing() {
let config: ScalarTypeConfig = serde_json::from_str(r#""string""#).unwrap();
assert_eq!(config, ScalarTypeConfig::Single("string".to_string()));

let config: ScalarTypeConfig = serde_json::from_str(
r#"{
"send": "string",
"receive": "number"
}"#,
)
.unwrap();
assert_eq!(
config,
ScalarTypeConfig::SendReceive(SendReceiveScalarTypeConfig {
send: "string".to_string(),
receive: "number".to_string(),
})
);

let config: ScalarTypeConfig = serde_json::from_str(
r#"{
"resolverOutput": "string",
"resolverInput": "number",
"operationOutput": "boolean",
"operationInput": "bigint"
}"#,
)
.unwrap();
assert_eq!(
config,
ScalarTypeConfig::Separate(SeparateScalarTypeConfig {
resolver_output: "string".to_string(),
resolver_input: "number".to_string(),
operation_output: "boolean".to_string(),
operation_input: "bigint".to_string(),
})
);
}
}
Loading

0 comments on commit 4973be7

Please sign in to comment.