Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
dejang opened this issue Apr 25, 2024 · 2 comments
Closed

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

dejang opened this issue Apr 25, 2024 · 2 comments
Labels
documentation Improvements or additions to documentation triage

Comments

@dejang
Copy link

dejang commented Apr 25, 2024

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?

@dejang dejang added documentation Improvements or additions to documentation triage labels Apr 25, 2024
@SimonSapin
Copy link
Contributor

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:

  • apollo_compiler::ast has a representation close to GraphQL syntax, where a document can contain any mix of executable definitions (operations and fragments) and schema / type system definitions.
  • apollo_compiler::Schema and apollo_compiler::ExecutableDocument have more semantics attached, but the latter requires a corresponding valid schema.

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
Copy link
Author

dejang commented Apr 28, 2024

Thank you, this was what I needed.

@lrlna lrlna closed this as completed May 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation triage
Projects
None yet
Development

No branches or pull requests

3 participants