Potential ergonomic improvements for `Queryable`
It's quite exciting to see all of the progress being made on gel-rust. One feature that I'd like to use more often is the Queryable derive macro, but I'm running into certain limitations.
Here is an example of code I'd like to write:
module default {
scalar type Position extending int32 {
constraint min_value(0);
constraint max_value(100);
}
type Widget {
required size: int32 { constraint min_value(0); }
required position: Position;
}
}
pub struct Ready;
#[derive(Queryable)]
pub struct Position(i32);
#[derive(Queryable)]
pub struct Widget<State = Ready> {
size: u32,
position: Position,
state: PhantomData<State>,
}
All 3 of the fields in the Rust Widget will error when deriving Queryable:
-
DecodeScalaris not implemented for u32 - No newtypes: only named fields are supported
-
DecodeScalaris not implemented forPhantomData<State>
My impression is that the main options in that situation are:
- Query as
<json>and useserde_json::from_str(), so that my struct now looks more like:
#[derive(Deserialize, Serialize)]
pub struct Widget<State = Ready> {
size: u32,
position: Position,
#[serde(skip)]
state: PhantomData<State>,
- Create a dedicated struct for the query and do some conversion:
#[derive(Queryable)]
pub struct QueryWidget {
size: i32,
position: i32,
}
impl TryFrom<QueryWidget> for Widget<Ready> {...}
I don't know what the long term view is on the Queryable derive macro, but I would love to be able to annotate some of these fields so that I could use my original Widget with Queryable, without having to pass through JSON. I'm picturing serde-like field attributes that would look something like:
#[derive(Queryable)]
pub struct Widget<State = Ready> {
#[gel(cast)]
size: u32,
#[gel(transparent)]
position: Position,
#[gel(skip)]
state: PhantomData<State>,
}
I realize that things like integer casts could fail if the DB constraints are not correct, but since the query methods on the gel-tokio Client already return Result, I think the user could be expected to handle such cases.
All of this assumes that there isn't a much easier path, such as implementing DecodeScalar for the types in question, and doing the cast/skip under the hood somehow.
Does any of this sound feasible/reasonable? As always, I appreciate your time.
I was thinking if we can replace custom Queryable macro with serde::Deserialize and just implement Deserializer for gel protocol.
Also, you can avoid manually writing Queryable types by using https://github.com/ifiokjr/edgedb_codegen
Using serde sounds like a great idea -- it shouldn't be too tough, but I'd have to make sure we can make the API work ergonomically.
I think we can probably expose some more powerful APIs to make something like edgedb_codegen easier to write as well.
I've recently created a new implementation of our wire protocol, and I'm going to work on 1.0/2.0 codecs next which should help unblock some of this work.