#[cfg(test)]
mod tests;
use proc_macro::TokenStream;
use syn::LitStr;
use cratestack_core::{ExtensionKind, Schema};
fn required_feature(kind: ExtensionKind) -> (&'static str, u32) {
match kind {
ExtensionKind::RateLimit => ("rate_limit", 154),
ExtensionKind::Pgvector => ("pgvector", 155),
}
}
fn feature_enabled(kind: ExtensionKind) -> bool {
match kind {
ExtensionKind::RateLimit => cfg!(feature = "rate_limit"),
ExtensionKind::Pgvector => cfg!(feature = "pgvector"),
}
}
fn first_missing_extension(schema: &Schema) -> Option<ExtensionKind> {
schema
.declared_extensions
.iter()
.copied()
.find(|kind| !feature_enabled(*kind))
}
fn guard_declared_extensions(schema_path: &LitStr, schema: &Schema) -> Result<(), TokenStream> {
match first_missing_extension(schema) {
Some(kind) => Err(missing_feature_error(schema_path, kind)),
None => Ok(()),
}
}
pub(super) fn guard_server_declared_extensions(
schema_path: &LitStr,
schema: &Schema,
) -> Result<(), TokenStream> {
guard_declared_extensions(schema_path, schema)
}
pub(super) fn guard_client_declared_extensions(
schema_path: &LitStr,
schema: &Schema,
) -> Result<(), TokenStream> {
guard_declared_extensions(schema_path, schema)
}
pub(super) fn guard_embedded_declared_extensions(
schema_path: &LitStr,
schema: &Schema,
) -> Result<(), TokenStream> {
if schema
.declared_extensions
.contains(&ExtensionKind::Pgvector)
{
return Err(embedded_pgvector_error(schema_path));
}
guard_declared_extensions(schema_path, schema)
}
fn missing_feature_error(schema_path: &LitStr, kind: ExtensionKind) -> TokenStream {
let name = kind.as_str();
let (feature, tracking_issue) = required_feature(kind);
TokenStream::from(
syn::Error::new(
schema_path.span(),
format!(
"schema declares `extension {name} {{ }}`, but `cratestack-macros` was compiled \
without its `{feature}` Cargo feature enabled. Declaring an extension only \
unlocks schema syntax (docs/design/extensions.md §2, layer 1); the matching \
Cargo feature is what makes the supporting code exist in this build at all \
(layer 2) — enable `{feature}` on the facade crate you depend on (e.g. \
`cratestack = {{ package = \"cratestack-pg\", features = [\"{feature}\"] }}`) \
once that facade forwards it (tracking: \
https://github.com/cratestack/cratestack/issues/{tracking_issue}), or drop the \
`extension {name} {{ }}` block if this schema doesn't actually need it."
),
)
.to_compile_error(),
)
}
fn embedded_pgvector_error(schema_path: &LitStr) -> TokenStream {
TokenStream::from(
syn::Error::new(
schema_path.span(),
"schema declares `extension pgvector { }`, but `include_embedded_schema!` can never \
support it, no matter which Cargo features are enabled — pgvector is a Postgres \
extension and the embedded backend is rusqlite-only, so there is no `vector(n)` \
column type or `CREATE EXTENSION` for it to map onto. Use `include_server_schema!` \
instead (with its `pgvector` Cargo feature enabled once cratestack#155 lands) for \
schemas that need `Vector(n)` fields, or drop the `extension pgvector { }` block \
from schemas meant for the embedded backend. See docs/design/extensions.md §6.",
)
.to_compile_error(),
)
}