apollographql / apollo-rs

Spec compliant GraphQL Tools in Rust.
Apache License 2.0
566 stars 43 forks source link

Trying to find an example of how to modify the AST #856

Closed dejang closed 4 months ago

dejang commented 4 months ago

First of all I apologize if this issue was opened in the wrong section.

I am trying out the parser in a small scale personal project in which I'd like to make some changes to the initial query. For the sake of keeping things simple let's assume I am trying to replace all occurrences of string values "John" to "John Smith" in the user provided query for all query arguments.

While looking through the opened issues I found an inspiration for a Visitor module which I could use to parse the AST nicely. Kudos to the owner of that issue for putting it up.

However, mutating the AST with or without such a module is still something I'm confused about. Would it possible to provide an example of how to alter the AST using the simple scenario in the first paragraph?

SimonSapin commented 4 months ago

Hi! Could you say a bit more about what you’re trying to do? Replacing all string values is maybe not the easiest example because apollo-rs currently does not provide a visitor, and string values are potentially found in a number of different places. (And arguably, do you really want to indiscriminately transform field arguments and directive arguments and variable defaults etc in the same way?). As for the more general question:

First of all, consider using apollo-compiler version 1.0 even though it is still in beta at this time. Versions 0.x are not as convenient and won’t get further development.

Next, pick a level of abstraction:

Either way, many things are wrapped in either Node<_> or Component<_>. This provides reference-counting, so that reusing a sub-tree does not require deep copy. On the other hand, because of this sharing, the implement Deref but not DerefMut so their contents can’t be modified directly. To modify, call their .make_mut() method which clones the content if it was shared, and returns a &mut _ exclusive reference. This is similar to (and if fact based on) the standard library’s Arc::make_mut.

https://github.com/apollographql/apollo-rs/blob/apollo-compiler%401.0.0-beta.15/crates/apollo-compiler/examples/rename.rs provides an example (using make_mut) of modifying a type in a schema. Here is an incomplete example for modifying string values in field argument. The string itself has type NodeStr which is immutable, so this creates new ones by converting from String with .into().


use apollo_compiler::ast::Definition;
use apollo_compiler::ast::Selection;
use apollo_compiler::ast::Value;

let query = r#"query { field(arg: "string") }"#;
let mut doc = apollo_compiler::ast::Document::parse(query, "example.graphql").unwrap();
for def in &mut doc.definitions {
    match def {
        Definition::OperationDefinition(operation) => {
            let operation = operation.make_mut();
            modify_selection_set(&mut operation.selection_set)
        }
        Definition::FragmentDefinition(_fragment) => todo!(),
        // Other definition kinds are for schemas
        _ => {}
    }
}
println!("{doc}")

fn modify_selection_set(selection_set: &mut [Selection]) {
    for selection in selection_set {
        match selection {
            Selection::Field(field) => {
                let field = field.make_mut();
                for arg in &mut field.arguments {
                    if let Value::String(string) = arg.make_mut().value.make_mut() {
                        *string = format!("{string}‽").into()
                    }
                }
                modify_selection_set(&mut field.selection_set);
            }
            Selection::FragmentSpread(_spread) => todo!(),
            Selection::InlineFragment(_inline) => todo!(),
        }
    }
}
dejang commented 4 months ago

Thank you, this was what I needed.