Skip to content

Commit

Permalink
Implement serde for basic AST types (#760)
Browse files Browse the repository at this point in the history
Extracted from #758

Co-authored-by: Renée <[email protected]>
  • Loading branch information
SimonSapin and goto-bus-stop authored Nov 30, 2023
1 parent 67f8f91 commit 90d48b6
Show file tree
Hide file tree
Showing 9 changed files with 569 additions and 10 deletions.
18 changes: 16 additions & 2 deletions crates/apollo-compiler/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,24 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## Features

- **Add `parse_and_validate` constructors for `Schema` and `ExecutableDocument` - [SimonSapin],
- **Add `parse_and_validate` constructors for `Schema` and `ExecutableDocument` - [SimonSapin],
[pull/752]:**
when mutating isn’t needed after parsing,
this returns an immutable `Valid<_>` value in one step.
- **Serialize multi-line strings as block strings- [SimonSapin], [pull/724]**

- **Implement serde `Serialize` and `Deserialize` for some AST types - [SimonSapin], [pull/760]:**
* `Node`
* `NodeStr`
* `Name`
* `IntValue`
* `FloatValue`
* `Value`
* `Type`
Source locations are not preserved through serialization.

- **Add `ast::Definition::as_*() -> Option<&_>` methods for each variant - [SimonSapin], [pull/760]**

- **Serialize (to GraphQL) multi-line strings as block strings - [SimonSapin], [pull/724]:**
Example before:
```graphql
"Example\n\nDescription description description"
Expand All @@ -129,6 +142,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
[issue/751]: https://github.com/apollographql/apollo-rs/issues/751
[pull/724]: https://github.com/apollographql/apollo-rs/pull/724
[pull/752]: https://github.com/apollographql/apollo-rs/pull/752
[pull/760]: https://github.com/apollographql/apollo-rs/pull/760

## Fixes

Expand Down
2 changes: 1 addition & 1 deletion crates/apollo-compiler/src/ast/from_cst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ impl Convert for cst::Name {
let loc = NodeLocation::new(file_id, self.syntax());
let token = &self.syntax().first_token()?;
let str = token.text();
debug_assert!(ast::Name::is_valid(str));
debug_assert!(ast::Name::valid_syntax(str));
Some(ast::Name(crate::NodeStr::new_parsed(str, loc)))
}
}
280 changes: 276 additions & 4 deletions crates/apollo-compiler/src/ast/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,142 @@ impl Definition {
}
}

pub fn as_operation_definition(&self) -> Option<&Node<OperationDefinition>> {
if let Self::OperationDefinition(def) = self {
Some(def)
} else {
None
}
}

pub fn as_fragment_definition(&self) -> Option<&Node<FragmentDefinition>> {
if let Self::FragmentDefinition(def) = self {
Some(def)
} else {
None
}
}

pub fn as_directive_definition(&self) -> Option<&Node<DirectiveDefinition>> {
if let Self::DirectiveDefinition(def) = self {
Some(def)
} else {
None
}
}

pub fn as_schema_definition(&self) -> Option<&Node<SchemaDefinition>> {
if let Self::SchemaDefinition(def) = self {
Some(def)
} else {
None
}
}

pub fn as_scalar_type_definition(&self) -> Option<&Node<ScalarTypeDefinition>> {
if let Self::ScalarTypeDefinition(def) = self {
Some(def)
} else {
None
}
}

pub fn as_object_type_definition(&self) -> Option<&Node<ObjectTypeDefinition>> {
if let Self::ObjectTypeDefinition(def) = self {
Some(def)
} else {
None
}
}

pub fn as_interface_type_definition(&self) -> Option<&Node<InterfaceTypeDefinition>> {
if let Self::InterfaceTypeDefinition(def) = self {
Some(def)
} else {
None
}
}

pub fn as_union_type_definition(&self) -> Option<&Node<UnionTypeDefinition>> {
if let Self::UnionTypeDefinition(def) = self {
Some(def)
} else {
None
}
}

pub fn as_enum_type_definition(&self) -> Option<&Node<EnumTypeDefinition>> {
if let Self::EnumTypeDefinition(def) = self {
Some(def)
} else {
None
}
}

pub fn as_input_object_type_definition(&self) -> Option<&Node<InputObjectTypeDefinition>> {
if let Self::InputObjectTypeDefinition(def) = self {
Some(def)
} else {
None
}
}

pub fn as_schema_extension(&self) -> Option<&Node<SchemaExtension>> {
if let Self::SchemaExtension(def) = self {
Some(def)
} else {
None
}
}

pub fn as_scalar_type_extension(&self) -> Option<&Node<ScalarTypeExtension>> {
if let Self::ScalarTypeExtension(def) = self {
Some(def)
} else {
None
}
}

pub fn as_object_type_extension(&self) -> Option<&Node<ObjectTypeExtension>> {
if let Self::ObjectTypeExtension(def) = self {
Some(def)
} else {
None
}
}

pub fn as_interface_type_extension(&self) -> Option<&Node<InterfaceTypeExtension>> {
if let Self::InterfaceTypeExtension(def) = self {
Some(def)
} else {
None
}
}

pub fn as_union_type_extension(&self) -> Option<&Node<UnionTypeExtension>> {
if let Self::UnionTypeExtension(def) = self {
Some(def)
} else {
None
}
}

pub fn as_enum_type_extension(&self) -> Option<&Node<EnumTypeExtension>> {
if let Self::EnumTypeExtension(def) = self {
Some(def)
} else {
None
}
}

pub fn as_input_object_type_extension(&self) -> Option<&Node<InputObjectTypeExtension>> {
if let Self::InputObjectTypeExtension(def) = self {
Some(def)
} else {
None
}
}

serialize_method!();
}

Expand Down Expand Up @@ -972,6 +1108,104 @@ impl fmt::Debug for FloatValue {
}
}

impl<'de> serde::Deserialize<'de> for IntValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
const EXPECTING: &str = "a string in GraphQL IntValue syntax";
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = IntValue;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(EXPECTING)
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if IntValue::valid_syntax(v) {
Ok(IntValue(v.to_owned()))
} else {
Err(E::invalid_value(serde::de::Unexpected::Str(v), &EXPECTING))
}
}

fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if IntValue::valid_syntax(&v) {
Ok(IntValue(v))
} else {
Err(E::invalid_value(serde::de::Unexpected::Str(&v), &EXPECTING))
}
}
}
deserializer.deserialize_string(Visitor)
}
}

impl<'de> serde::Deserialize<'de> for FloatValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
const EXPECTING: &str = "a string in GraphQL FloatValue syntax";
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = FloatValue;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(EXPECTING)
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if FloatValue::valid_syntax(v) {
Ok(FloatValue(v.to_owned()))
} else {
Err(E::invalid_value(serde::de::Unexpected::Str(v), &EXPECTING))
}
}

fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if FloatValue::valid_syntax(&v) {
Ok(FloatValue(v))
} else {
Err(E::invalid_value(serde::de::Unexpected::Str(&v), &EXPECTING))
}
}
}
deserializer.deserialize_string(Visitor)
}
}

impl serde::Serialize for IntValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}

impl serde::Serialize for FloatValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}

impl fmt::Display for FloatOverflowError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("value magnitude too large to be converted to `f64`")
Expand Down Expand Up @@ -1298,7 +1532,7 @@ impl<N: Into<Name>, V: Into<Node<Value>>> From<(N, V)> for Node<Argument> {
/// ```compile_fail
/// # use apollo_compiler::name;
/// // error[E0080]: evaluation of constant value failed
/// // assertion failed: ::apollo_compiler::ast::Name::is_valid(\"è_é\")
/// // assertion failed: ::apollo_compiler::ast::Name::valid_syntax(\"è_é\")
/// let invalid = name!("è_é");
/// ```
#[macro_export]
Expand All @@ -1307,7 +1541,7 @@ macro_rules! name {
$crate::name!(stringify!($value))
};
($value: expr) => {{
const _: () = { assert!($crate::ast::Name::is_valid($value)) };
const _: () = { assert!($crate::ast::Name::valid_syntax($value)) };
$crate::ast::Name::new_unchecked($crate::NodeStr::from_static(&$value))
}};
}
Expand All @@ -1316,7 +1550,7 @@ impl Name {
/// Creates a new `Name` if the given value is a valid GraphQL name.
pub fn new(value: impl Into<NodeStr>) -> Result<Self, InvalidNameError> {
let value = value.into();
if Self::is_valid(&value) {
if Self::valid_syntax(&value) {
Ok(Self::new_unchecked(value))
} else {
Err(InvalidNameError(value))
Expand All @@ -1334,7 +1568,7 @@ impl Name {
/// Returns whether the given string is a valid GraphQL name.
///
/// <https://spec.graphql.org/October2021/#Name>
pub const fn is_valid(value: &str) -> bool {
pub const fn valid_syntax(value: &str) -> bool {
let bytes = value.as_bytes();
let Some(&first) = bytes.first() else {
return false;
Expand Down Expand Up @@ -1373,6 +1607,44 @@ impl Name {
}
}

impl<'de> serde::Deserialize<'de> for Name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
const EXPECTING: &str = "a string in GraphQL Name syntax";
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = Name;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(EXPECTING)
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if Name::valid_syntax(v) {
Ok(Name(v.into()))
} else {
Err(E::invalid_value(serde::de::Unexpected::Str(v), &EXPECTING))
}
}
}
deserializer.deserialize_str(Visitor)
}
}

impl serde::Serialize for Name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self)
}
}

impl TryFrom<NodeStr> for Name {
type Error = InvalidNameError;

Expand Down
4 changes: 2 additions & 2 deletions crates/apollo-compiler/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ pub struct VariableDefinition {
pub directives: DirectiveList,
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub enum Type {
Named(NamedType),
NonNullNamed(NamedType),
Expand Down Expand Up @@ -337,7 +337,7 @@ pub struct InlineFragment {
pub selection_set: Vec<Selection>,
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub enum Value {
Null,
Enum(Name),
Expand Down
Loading

0 comments on commit 90d48b6

Please sign in to comment.