Aside from the promised features, I've been reading Rust for Rustaceans lately, influencing some of the design decisions made in the internal code structure. I'm at 6 out of 13 chapters, can't wait for what's more in the store!
I highly recommend this book for any aspiring Rustacean 📖🦀
And last but not least, CI configuration has improved a lot. It contains multiple stages now, caches build results and dependencies, builds with multiple toolchains, does security audits, and brings static analysis with rustfmt
and clippy
📎
The Book: Existential types
In the second chapter of the book, I've learned this trick with "Existential types." You can make a public trait with private implementation. And make methods that would return Type
instead return impl Trait
. That impl Trait
thing is syntax sugar for generic return types.
That way you can change the implementation without introducing a breaking change.
fn info(&self) -> Result<impl Info> { // <- returns some implementation of the public trait
// ...
Ok(RawInfo::from_str(...).unwrap()) // <- returns an instance of private type implementing the trait
}
Now I'm wondering where is the stopping point? Should I go and turn all my public structs into traits and hide their implementation from the public API? Guess only the next release will tell!
Features
Let me introduce basex::serializer::Options
and basex::compiler::Info
. Both of these concepts belong to Query
and are returned by Query::info
and Query::options
.
New: Query::info
fn info(&self) -> Result<impl Info>;
You can now collect parsed compiler info from the query—for instance, the optimized version of the query and its time to produce it.
let info = query.info()?;
// Read compilation info
println!("Optimized Query: {:?}", info.optimized_query());
println!("Total Time: {:?}", info.total_time());
println!("Hit(s): {:?}", info.hits());
The compilation info needs to be enabled before creating the query server-side. This influenced the design of Client::query
, which now returns a builder object, asking you if you want to query with_info
, unlocking Query::info
for you, or without_info
, reducing overhead but making Query::info
inaccessible.
New: Query::options
fn options(&self) -> Result<Options>;
You can now read and update server-side query result serializer options. This influences the way the result looks and might for instance change encoding.
// Get options from the server for the query
let options = query.options()?;
// Read options
let encoding = options.get("encoding").unwrap();
let indent = options.get("indent").unwrap();
println!("Encoding: {}", encoding.as_str());
println!("Indent: {}", if indent.as_bool().unwrap() { "ON" } else { "OFF" });
// Set options
let encoding = options.set("encoding", "UTF-8");
let indent = options.set("indent", true);
println!("Set Encoding: {}", encoding.as_str());
println!("Set Indent: {}", if indent.as_bool().unwrap() { "ON" } else { "OFF" });
// Save options to session on server
options.save(client);
My decision was to make it like a key-value store. Now an exhaustive could be made using the BaseX documentation, but there is no guarantee that new options might be created in the next release of the BaseX server, making it incompatible with the client.
CI
The build pipeline now has a bunch of new jobs! 🚦
The code is now formatted by rustfmt
and further analyzed by clippy
.
There are 3 build jobs now using:
- Nightly toolchain
- Nightly toolchain with 6 months old Rust version
- Nightly toolchain with minimal dependency versions
And the build is cached.
Packages are now checked for security issues via cargo-audit
, which runs only on Cargo.toml
changes and once per day.
Chores
- Set version range of all dependencies the widest possible.
Fixes
- Upgrade rust-embed minimal version from 0.5.2 to 0.6.3 due to RUSTSEC-2021-0126 critical security issue.
Upgrading from the previous version
Getting the code work is pretty straightforward, but writing it in a way that makes sense will require a bit more effort.
Before
let mut client = Client::connect("localhost", 1984, "admin", "admin")?;
let query = client.query("count(/None/*)")?;
let info = query.info()?;
let options = query.options()?;
After
let mut client = Client::connect("localhost", 1984, "admin", "admin")?;
let query = client.query("count(/None/*)")?.with_info()?;
let info = query.info()?.to_string();
let options = query.options()?.to_string();
Plans for next version
The feature set is pretty much complete as far as the required functionality goes. The library can be only polished further, introduce async or add features on top. Which is exactly what is planned next.
The next version temporarily stabilizes the API and then basex-rs
is going async
!