The goal here is to create a smoother GraphQL client experience in Rust.
In most GraphQL APIs, every field is optional. Secondly, the query defines the structure of the outcome, which means that some functions will have more or fewer fields to access.
However, we still want to write generic functions over combinations of those fields. For example, imagine a GraphQL API:
scalarDatetypeUser {
id: IDname: StringcreatedAt: Date
}Function A querying id, name, and created_at and Function B querying only id and created_at would have different Rust structs generated for them since there are different fields returned:
structFunctionAReturn{id:String,name:String,created_at:DateTime<Utc>,}structFunctionBReturn{id:String,created_at:DateTime<Utc>,}A generic function over, for example, the id and created_at field cannot handle both of these structs:
fnprint_age(values:_){// What is values?println!("{}: {:?}", values.id, values.created_at)}I propose instead a set of proc_macros that add getter traits for each field, and allow you to write generic functions easily over the sum of those traits:
#[derive(SubstructRoot)]structUser{id:String,name:String,created_at:DateTime<Utc>,}#[derive(SubstructChild)]#[root(User)]#[fields(id, name, created_at)]structFunctionAReturn;#[derive(SubstructChild)]#[root(User)]#[fields(id, created_at)]structFunctionBReturn;#[substruct_use(root = User, fields(id, created_at))]fnget_name(query:_){println!("{}: {:?}", query.id(), query.created_at())}This expands to something like:
structUser{id:String,name:String,created_at:DateTime<Utc>,}traitUserId{fnid(&self) -> String;}traitUserName{fnname(&self) -> String;}traitUserCreatedAt{fncreated_at(&self) -> DateTime<Utc>;}implUserIdforUser{fnid(&self) -> String{self.id}}implUserNameforUser{fnname(&self) -> String{self.name}}implUserCreatedAtforUser{fncreated_at(&self) -> String{self.created_at}}structFunctionAReturn{id:String,name:String,created_at:DateTime<Utc>,}implUserIdforFunctionAReturn{fnid(&self) -> String{self.id}}implUserNameforFunctionAReturn{fnname(&self) -> String{self.name}}implUserCreatedAtforFunctionAReturn{fncreated_at(&self) -> String{self.created_at}}structFunctionBReturn{id:String,created_at:DateTime<Utc>,}implUserIdforFunctionBReturn{fnid(&self) -> String{self.id}}implUserCreatedAtforFunctionBReturn{fncreated_at(&self) -> String{self.created_at}}traitGetNameInput:UserId + UserCreatedAt{}impl<T:UserId + UserCreatedAt>GetNameInputforT{}fnget_name(query:implGetNameInput){println!("{}: {:?}", query.id(), query.created_at())}This is incomplete, of course. I'm still working specifically on the
SubstructChild, and the substruct_use is very unfinished. I'm sure there's
other issues that will arrive over the course of finishing this.
Additionally, I've not tested integrating this with any existing GraphQL client
implementations, nor have I thought about how to resolve the Option<...>
nesting or nesting of GraphQL elements.
Who knows if I ever finish this :)