razeri-plugin-api 0.15.0

Extension traits and registries for Razeri plugins
Documentation

razeri-plugin-api

Extension traits and registries for building Razeri plugins.

This is the single crate third-party authors depend on to extend Razeri with new database dialects, storage backends, LLM providers, AI model adapters, language renderers, and event sinks.

Extension Points

Trait Registry Purpose
PlatformDialect PlatformRegistry SQL dialect for a new database
StorageProviderFactory StorageRegistry Storage backend (S3, Azure, etc.)
LlmClientFactory LlmRegistry LLM provider (Ollama, OpenAI, etc.)
LlmModelAdapter ModelRegistry Model capabilities and routing
LanguageRendererFactory LanguageRegistry Code generation target language
EventSink EventRegistry Structured telemetry sink

The rendering traits themselves live in razeri-plugin-contract, which this crate depends on and re-exports as razeri_plugin_api::razeri_plugin_contract.

Quick Start

use std::sync::Arc;
use razeri_plugin_api::EngineVersion;
use razeri_plugin_api::dialect::{DialectInfo, PlatformDialect};
use razeri_plugin_api::razeri_plugin_contract::{
    render_data_type_ansi, DataType, DdlRenderer, DialectCapabilities,
    DialectFamily, DmlRenderer, ExpressionRenderer, FormulaRenderer,
    ProcedureRenderer, ReplicationRenderer, SqlRenderer, StorageRenderer,
    TaskRenderer,
};

struct MyDialect;

// SqlRenderer: 4 required methods; everything else defaults to ANSI behaviour.
impl SqlRenderer for MyDialect {
    fn render_data_type(&self, dt: &DataType) -> String { render_data_type_ansi(dt) }
    fn quote_identifier(&self, name: &str) -> String { format!("\"{name}\"") }
    fn qualify_name(&self, schema: Option<&str>, name: &str) -> String {
        match schema {
            Some(s) => format!("{s}.{name}"),
            None => name.to_string(),
        }
    }
    fn render_current_timestamp(&self) -> String { "CURRENT_TIMESTAMP".into() }
}

// Remaining traits: all methods have defaults — each override is a decision
// your engine states about itself.
impl ExpressionRenderer for MyDialect {}
impl DdlRenderer for MyDialect {}
impl DmlRenderer for MyDialect {}
impl ProcedureRenderer for MyDialect {}
impl FormulaRenderer for MyDialect {}
impl TaskRenderer for MyDialect {}
impl ReplicationRenderer for MyDialect {}
impl StorageRenderer for MyDialect {}
impl DialectCapabilities for MyDialect {}

impl PlatformDialect for MyDialect {
    fn info(&self) -> DialectInfo {
        DialectInfo {
            name: "my-database",
            display_name: "My Database",
            // The other spellings a design may use for this platform.
            aliases: &["mydb"],
            dialect_family: DialectFamily::Custom("my-database"),
            family: "My Database SQL",
            is_cloud_native: false,
            is_olap: false,
            is_postgres_compatible: false,
            // The versions of the engine this dialect answers for, the floor
            // first; `&[]` for a managed service that chooses its own.
            versions: &[EngineVersion::declared("2024")],
            // Which client a bundle's deploy script drives: one razeri writes
            // a script for, one described with `DeployScript::Client`, or
            // `None` for no script.
            deploy_script: None,
        }
    }
}

fn register(builder: &mut razeri_plugin_api::PluginRegistryBuilder) {
    builder.register_dialect(Arc::new(MyDialect));
}

// One line makes the crate a plugin, loaded from a directory or linked in.
razeri_plugin_api::export_plugin!(register);

Running a plugin

A plugin reaches razeri one of two ways, and export_plugin! serves both:

  • Loaded from a plugin directory. Build the crate as a cdylib and put the library in the directory razeri --plugin-dir names. It shares Rust types with the razeri that loads it, so it must be built with the same compiler, against the same versions of this crate and razeri-plugin-contract, from the same source. razeri plugin list prints what that razeri was built with, and a library built any other way is refused with a message saying what to rebuild it with.
  • Linked into a razeri you build. Depend on the crate from a program whose main runs razeri's command line, and the dialect is one of the platforms it renders. One build means nothing to match.

The book's Dynamic loading and Linking a dialect pages give both in full. The book comes with each release, on the releases page.

Proving a plugin

The conformance kit runs from your own test suite and grades the plugin the way Razeri grades its shipped dialects — defaulted decisions, contradictory capability answers, missing refusal wording:

let report = razeri_plugin_api::conformance::check(&MyDialect);
assert!(report.is_clean(), "{report}");

Your dialect's names are yours: name is the one Razeri reports, aliases is every other spelling a design may use for the same platform, and the resolver reads both off the dialects that are loaded. A clean report never depends on anything inside Razeri — only on not claiming a spelling another loaded platform already answers to.

Stability

This crate and razeri-plugin-contract follow semantic versioning while the major version is zero. A trait gains a method only with a default body, so a new method costs an existing plugin no line of code. A change that does break a plugin's build comes only in a minor release, and the CHANGELOG.md in razeri-plugin-contract's package says what to change. A method that leaves does so as #[deprecated] for one minor version before it is removed, so a plugin sees the warning before the removal, not after.

Features

  • dynamic (default) — Enables runtime plugin loading via shared libraries (libloading).

Documentation

  • The book's Plugin Authoring Guide and Plugin Architecture pages, in the book that comes with each release on the releases page.
  • API reference, on docs.rs.

License

Dual-licensed under MIT or Apache 2.0, at your option — this crate is the open surface of an otherwise proprietary workspace, so plugin authors depend on it freely. The rest of Razeri is covered by the licence it is released under (see its §4, "Plugins are open").

Security

Report a vulnerability as Razeri's security policy says.