Expand description
Generate fully typed rust code from your gel schema and inline queries with
gelx.
§Installation
To install the gelx crate you can use the following commands.
cargo add gelx
# Optional dependency when using the `query` feature
cargo add gel-protocolOr directly add the following to your Cargo.toml file.
[dependencies]
gelx = "0.4"
# Optional dependencies
gel-protocol = "0.8" # needed when using the `query` featureMake sure you’ve installed the gel CLI for your platform.
Once installed you should be able to run the following command to verify your installation.
gel --versionAnd then initialize in the project directory.
gel init§gelx!
Working with the default gel crate requires manually writing untyped queries and creating the rust structs and enums for both the input and output into these queries with. The correctness of your code can only be checked at runtime increasing the risk of bugs and errors.
gelx! transforms your queries into rust structs, types and functions, providing safety during development of your project.
§Inline Queries
use gel_errors::Error;
use gel_tokio::create_client;
use gelx::gelx;
// Creates a module called `example` with a function called `query` and structs
// for the `Input` and `Output`.
gelx!(
example,
"select { hello := \"world\", custom := <str>$custom }"
);
#[tokio::main]
async fn main() -> Result<(), Error> {
let client = create_client().await?;
let input = example::Input {
custom: String::from("custom"),
};
// For queries the following code can be used.
let output = example::query(&client, &input).await?;
Ok(())
}The macro above generates the following code in the background::
pub mod example {
use ::gelx::exports as __g;
/// Execute the desired query.
pub async fn query(
client: &__g::gel_tokio::Client,
props: &Input,
) -> ::core::result::Result<Output, __g::gel_errors::Error> {
client.query_required_single(QUERY, props).await
}
/// Compose the query as part of a larger transaction.
pub async fn transaction(
conn: &mut __g::gel_tokio::Transaction,
props: &Input,
) -> ::core::result::Result<Output, __g::gel_errors::Error> {
conn.query_required_single(QUERY, props).await
}
#[derive(
Clone,
Debug,
__g::serde::Serialize,
__g::serde::Deserialize,
__g::typed_builder::TypedBuilder,
__g::gel_derive::Queryable,
)]
pub struct Input {
#[builder(setter(into))]
pub custom: String,
}
impl __g::gel_protocol::query_arg::QueryArgs for Input {
fn encode(
&self,
encoder: &mut __g::gel_protocol::query_arg::Encoder,
) -> core::result::Result<(), __g::gel_errors::Error> {
let map = __g::gel_protocol::named_args! {
"custom" => self.custom.clone(),
};
map.encode(encoder)
}
}
#[derive(
Clone, Debug, __g::serde::Serialize, __g::serde::Deserialize, __g::gel_derive::Queryable,
)]
pub struct Output {
pub hello: String,
pub custom: String,
}
/// The original query string provided to the macro. Can be reused in your
/// codebase.
pub const QUERY: &str = "select { hello := \"world\", custom := <str>$custom }";
}§Query Files
Define a query file in the queries directory of your crate called select_user.edgeql.
# queries/select_user.edgeql
select User {
name,
bio,
slug,
} filter .slug = <str>$slug;Then use the gelx macro to import the query.
use gel_errors::Error;
use gelx::create_client;
use gelx::gelx;
// Creates a module called `select_user` with public functions `transaction` and
// `query` as well as structs for the `Input` and `Output`.
gelx!(select_user);
#[tokio::main]
async fn main() -> Result<(), Error> {
let client = create_client().await?;
// Generated code can be run inside a transaction.
let result = client
.transaction(|mut txn| {
async move {
let input = select_user::Input {
slug: String::from("test"),
};
let output = select_user::transaction(&mut txn, &input).await?;
Ok(output)
}
})
.await?;
Ok(())
}§gelx_build
By default macros can’t read from the Cargo.toml file with the consuming crate. The gelx_build crate provides a way to read the configuration from the Cargo.toml file using the build.rs script.
You can read the gelx_build readme for more information.
§CLI
The gelx_cli crate exposes a binary called gelx which can be used to generate the typed code into rust files rather than inline queries.
It should be run from the crate directory and will read from the configuration specified in the next section.
gelx generate --cwd path/to/crateSometimes you will need to check that the generated code matches the current database schema and queries generated by gel. This is useful for CI pipelines to ensure the generated code is up to date.
gelx check --cwd path/to/crateIf there are changes that haven’t been accounted for, the check will fail with a diff and you should regenerate the code.
More information can be found in the gelx_cli readme.
§Configuration
The following configuration options are supported and the provided defaults will be used if not specified.
[package.metadata.gelx]
## The path to the directory containing the queries.
queries_path = "./queries"
## The features to enable and their aliases. By default all features are enabled.
## To disable a feature set it to false. To alias a feature behind a feature flag
## use the following format `feature = { query = "ssr" }`. This will enable the query
## feature only when the `ssr` feature is enabled.
##
## The available features are:
##
## - `query` - When enabled you must include `gel-protocol` as a dependency.
## - `serde` - Enable serde for the generated code.
## - `strum` - When enabled you must include `strum` as a dependency.
features = { query = true, strum = true, serde = true }
## The location of the generated code when using the `gelx` cli.
output_path = "./src/db"
## The name of the arguments input struct. Will be transformed to PascalCase.
input_struct_name = "Input"
## The name of the exported output struct for generated queries. Will be transformed to PascalCase.
output_struct_name = "Output"
## The name of the query function exported.
query_function_name = "query"
## The name of the transaction function exported.
transaction_function_name = "transaction"
## The name of the query constant exported.
query_constant_name = "QUERY"
## The alias used for the `gelx::exports` module.
exports_alias = "__g"
## The macros which are always derived for the generated structs.
struct_derive_macros = ["Debug", "Clone"]
## The macros which are always derived for the generated enums.
enum_derive_macros = ["Debug", "Clone", "Copy"]
## The relative path to the `gel` config file. This is optional and if not provided the `gel`
## config will be read from the environment variables.
# gel_config_path = "./gel.toml"
## The name of the `gel` instance to use. This is optional and if not provided the environment
## variable `$GEL_INSTANCE` will be used.
# gel_instance = "$GEL_INSTANCE"
## The name of the `gel` branch to use. This is optional and if not provided the environment
## variable `$GEL_BRANCH` will be used.
# gel_branch = "$GEL_BRANCH"§Geometry and Geography
The gelx crate provides wrapper types for the Geometry and Geography types from the geo crate.
# queries/insert_location.edgeql
with NewLocation := (insert Location {
point := <ext::postgis::geometry>$point,
area := <ext::postgis::geography>$area,
})
select NewLocation {
point,
area,
};use gel_errors::Error;
use gelx::Geography;
use gelx::Geometry;
use gelx::create_client;
use gelx::gelx;
use gelx::geo::point;
use gelx::geo::polygon;
// Creates a module called `insert_location` with public functions `transaction`
// and `query` as well as structs for the `Input` and `Output`.
gelx!(insert_location);
#[tokio::main]
async fn main() -> Result<(), Error> {
let client = create_client().await?;
let point = point!(x: 1.0, y: 1.0);
let polygon = polygon![
(x: -111., y: 45.),
(x: -111., y: 41.),
(x: -104., y: 41.),
(x: -104., y: 45.),
];
let output = insert_location::query(
&client,
&insert_location::Input {
point: Geometry(point.into()),
area: Geography(polygon.into()),
},
)
.await?;
println!("{:?}", output);
Ok(())
}§Future Work ⚠️
This crate is still in early development and there are several features that are not yet implemented.
§Missing Types
Currently the following types are not supported:
MultiRange- The cli / macro will panic if a multirange is used.
§MultiRange
These are not currently exported by the gel-protocol so should be added in a PR to the gel-protocol crate, if they are still supported in the new protocol.
§LSP parsing
Currently the macro depends on having a running gel instance to parse the query string.
Once an LSP is created for gel it would make sense to switch from using string to using inline gel queries.
use gelx::gelx;
gelx!(
example,
select User {**}
);§Features
default— The default feature iswith_all.with_bigint— Include thenum-bigintdependency.with_bigdecimal— Use thebigdecimalcrate.with_chrono— Use thechronocrate for all dates.with_geo— Use thegeocrate for all geometry and geography types.with_all(enabled by default) — Include all additional types. This is included by default. Usedefault-features = falseto disable.builder— Use thetyped-buildercrate to generate the builders for the generatedInputstructs.query— Turn on thequeryandtransactionmethods and anything that relies ongel-tokio. The reason to separate this feature is to enable usage of this macro in browser environments wheregel-tokiois not feasible.serde— Enable serde for the generated code.strum- Use thestrumcrate for deriving strings from the created enums.
§Recommended Setup
Create a gel.toml in the root of your project with the folloiwng configuration. The following configuration will work for a single crate project.
[instance]
server-version = "6.7"
[project]
schema-dir = "dbschema"
[hooks]
project.init.after = "gelx generate"
branch.switch.after = "gelx generate"
schema.update.after = "gelx generate"
[[watch]]
files = ["dbschema/*.gel"]
script = "gelx generate"
[[watch]]
files = ["queries/*.edgeql"]
script = "gelx generate"By default this will generate the code into the src/db directory. You can change this by setting the output_path in the configuration.
§Contributing
devenv is used to provide a reproducible development environment for this project. Follow the getting started instructions.
To automatically load the environment you should install direnv and then load the direnv.
direnv allow .You now have a shell with all the dependencies installed and project specific commands available.
Run the following commands to install all the required dependencies.
install:allThis installs all the cargo binaries locally so you don’t need to worry about polluting your global namespace.
At this point you must setup the gel instance.
db:setup # setup the gel instanceThe above command will setup the local database and install the postgis extension.
Now you can make your changes and run tests.
test:all§Available Commands
You can view all the available scripts, packages, tasks and environment variables by running devenv info.
build:all: Build all crates with all features activated.build:docs: Build documentation site.coverage:all: Test all files and generate a coverage report for upload to codecov.db:destroy: Destroy the local database.db:setup: Setup the local database.db:reset: Reset the local database.db:up: Watch changes to the local database.fix:all: Fix all fixable lint issues.fix:clippy: Fix fixable lint issues raised by rust clippy.fix:format: Fix formatting for entire project.fix:gelx: Fix fixable lint issues raised by gelx.gelx: Install all dependencies.install:all: Install all dependencies.install:cargo:bin: Install cargo binaries locally.lint:all: Lint all project files.lint:clippy: Check rust clippy lints.lint:format: Check all formatting is correct.lint:gelx: Check gelx is formatted correctly.test:all: Test all project files.update:deps: Update all project dependencies.
§Upgrading devenv
If you have an outdated version of devenv you can update it by running the following commands. If you have an easier way, please create a PR and I’ll update these docs.
nix profile list # find the index of the devenv package
nix profile remove <index>
nix profile install ---accept-flake-config nixpkgs#devenv§Editor Setup
To setup recommended configuration for your favorite editor run the following commands.
setup:vscode # Setup vscode with recommended configurationsetup:helix # Setup helix with recommended configuration§License
Unlicense, see the license file.
§Features
-
default— The default feature iswith_all. -
with_bigint(enabled by default) — Include thenum-bigintdependency. -
with_bigdecimal(enabled by default) — Use thebigdecimalcrate. -
with_chrono(enabled by default) — Use thechronocrate for all dates. -
with_geo(enabled by default) — Use thegeo-typescrate for all geo types. -
with_all(enabled by default) — Include all additional types. This is included by default. Usedefault-features = falseto disable. -
builder(enabled by default) — Use thetyped-buildercrate to generate the builders for the generatedInputstructs. -
query— Turn on thequeryandtransactionmethods and anything that relies ongel-tokio. The reason to separate this feature is to enable usage of this macro in browser environments wheregel-tokiois not feasible.Adding this feature requires adding the
gel-protocoldependency to your project. This is because theQueryablederive macro requires thegel-protocolcrate to be present in the consuming crate. -
serde— Enable serde for the generated code. -
strum— Enable strum for the generated code.Adding this feature requires adding the
strumdependency to your project.
Re-exports§
pub use geo;pub use geo_types;pub use geo_traits;pub use wkb;
Modules§
Macros§
- gelx
- Generates a query module from a query string.
- gelx_
file Deprecated - Generates a query module from a query string relative to the root of the
crate this is defined in. This is useful for queries that are not placed in
the
queriesfolder at the root of the crate.
Structs§
- Geography
- Another wrapper struct for the
geo::Geometryenum with support for WKB encoding and interop with theext::postgis::geographytype. - Geometry
- A wrapper for the
geo::Geometryenum with support for WKB encoding and interop with theext::postgis::geometrytype.
Functions§
- create_
client - Create a connection to the database with default parameters