pub mod binary_builder;
pub mod binary_format;
use std::ffi::{c_char, c_void};
#[repr(C)]
pub struct PluginInfo {
pub name: *const c_char,
pub version: *const c_char,
pub plugin_type: PluginType,
pub description: *const c_char,
}
unsafe impl Sync for PluginInfo {}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PluginType {
DataSource = 0,
OutputSink = 1,
LanguageRuntime = 2,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CapabilityKind {
DataSource = 0,
OutputSink = 1,
Compute = 2,
Model = 3,
LanguageRuntime = 4,
Custom = 255,
}
pub const CAPABILITY_DATA_SOURCE: &str = "shape.datasource";
pub const CAPABILITY_OUTPUT_SINK: &str = "shape.output_sink";
pub const CAPABILITY_MODULE: &str = "shape.module";
pub const CAPABILITY_LANGUAGE_RUNTIME: &str = "shape.language_runtime";
#[repr(C)]
pub struct CapabilityDescriptor {
pub kind: CapabilityKind,
pub contract: *const c_char,
pub version: *const c_char,
pub flags: u64,
}
unsafe impl Sync for CapabilityDescriptor {}
#[repr(C)]
pub struct CapabilityManifest {
pub capabilities: *const CapabilityDescriptor,
pub capabilities_len: usize,
}
unsafe impl Sync for CapabilityManifest {}
#[repr(C)]
pub struct SectionClaim {
pub name: *const c_char,
pub required: bool,
}
unsafe impl Sync for SectionClaim {}
#[repr(C)]
pub struct SectionsManifest {
pub sections: *const SectionClaim,
pub sections_len: usize,
}
unsafe impl Sync for SectionsManifest {}
pub type GetClaimedSectionsFn = unsafe extern "C" fn() -> *const SectionsManifest;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParamType {
String = 0,
Number = 1,
Bool = 2,
StringArray = 3,
NumberArray = 4,
Object = 5,
Timestamp = 6,
Duration = 7,
}
#[repr(C)]
pub struct QueryParam {
pub name: *const c_char,
pub description: *const c_char,
pub param_type: ParamType,
pub required: bool,
pub default_value: *const u8,
pub default_value_len: usize,
pub allowed_values: *const u8,
pub allowed_values_len: usize,
pub nested_schema: *const QuerySchema,
}
unsafe impl Sync for QueryParam {}
#[repr(C)]
pub struct QuerySchema {
pub params: *const QueryParam,
pub params_len: usize,
pub example_query: *const u8,
pub example_query_len: usize,
}
unsafe impl Sync for QuerySchema {}
#[repr(C)]
pub struct OutputField {
pub name: *const c_char,
pub field_type: ParamType,
pub description: *const c_char,
}
unsafe impl Sync for OutputField {}
#[repr(C)]
pub struct OutputSchema {
pub fields: *const OutputField,
pub fields_len: usize,
}
unsafe impl Sync for OutputSchema {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "PascalCase"))]
pub enum DataType {
Number,
Integer,
String,
Boolean,
Timestamp,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ColumnInfo {
pub name: std::string::String,
pub data_type: DataType,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PluginSchema {
pub columns: Vec<ColumnInfo>,
pub timestamp_column: std::string::String,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ModuleFunctionSchema {
pub name: std::string::String,
pub description: std::string::String,
pub params: Vec<std::string::String>,
pub return_type: Option<std::string::String>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ModuleSchema {
pub module_name: std::string::String,
pub functions: Vec<ModuleFunctionSchema>,
}
pub type ProgressCallbackFn = unsafe extern "C" fn(
phase: u8,
rows_processed: u64,
total_rows: u64,
bytes_processed: u64,
user_data: *mut c_void,
) -> i32;
#[repr(C)]
pub struct DataSourceVTable {
pub init: Option<unsafe extern "C" fn(config: *const u8, config_len: usize) -> *mut c_void>,
pub get_query_schema: Option<unsafe extern "C" fn(instance: *mut c_void) -> *const QuerySchema>,
pub get_output_schema:
Option<unsafe extern "C" fn(instance: *mut c_void) -> *const OutputSchema>,
pub get_source_schema: Option<
unsafe extern "C" fn(
instance: *mut c_void,
source_id: *const u8,
source_id_len: usize,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32,
>,
pub validate_query: Option<
unsafe extern "C" fn(
instance: *mut c_void,
query: *const u8,
query_len: usize,
out_error: *mut *mut c_char,
) -> i32,
>,
pub load: Option<
unsafe extern "C" fn(
instance: *mut c_void,
query: *const u8,
query_len: usize,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32,
>,
pub load_binary: Option<
unsafe extern "C" fn(
instance: *mut c_void,
query: *const u8,
query_len: usize,
granularity: u8,
progress_callback: Option<ProgressCallbackFn>,
progress_user_data: *mut c_void,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32,
>,
pub subscribe: Option<
unsafe extern "C" fn(
instance: *mut c_void,
query: *const u8,
query_len: usize,
callback: unsafe extern "C" fn(*const u8, usize, *mut c_void),
callback_data: *mut c_void,
) -> u64,
>,
pub unsubscribe:
Option<unsafe extern "C" fn(instance: *mut c_void, subscription_id: u64) -> i32>,
pub free_buffer: Option<unsafe extern "C" fn(ptr: *mut u8, len: usize)>,
pub free_string: Option<unsafe extern "C" fn(ptr: *mut c_char)>,
pub drop: Option<unsafe extern "C" fn(instance: *mut c_void)>,
}
#[repr(C)]
pub struct OutputSinkVTable {
pub init: Option<unsafe extern "C" fn(config: *const u8, config_len: usize) -> *mut c_void>,
pub get_handled_tags: Option<
unsafe extern "C" fn(instance: *mut c_void, out_ptr: *mut *mut u8, out_len: *mut usize),
>,
pub send: Option<
unsafe extern "C" fn(instance: *mut c_void, alert: *const u8, alert_len: usize) -> i32,
>,
pub flush: Option<unsafe extern "C" fn(instance: *mut c_void) -> i32>,
pub free_buffer: Option<unsafe extern "C" fn(ptr: *mut u8, len: usize)>,
pub drop: Option<unsafe extern "C" fn(instance: *mut c_void)>,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModuleInvokeResultKind {
WireValueMsgpack = 0,
TableArrowIpc = 1,
}
#[repr(C)]
pub struct ModuleInvokeResult {
pub kind: ModuleInvokeResultKind,
pub payload_ptr: *mut u8,
pub payload_len: usize,
}
impl ModuleInvokeResult {
pub const fn empty() -> Self {
Self {
kind: ModuleInvokeResultKind::WireValueMsgpack,
payload_ptr: core::ptr::null_mut(),
payload_len: 0,
}
}
}
#[repr(C)]
pub struct ModuleVTable {
pub init: Option<unsafe extern "C" fn(config: *const u8, config_len: usize) -> *mut c_void>,
pub get_module_schema: Option<
unsafe extern "C" fn(
instance: *mut c_void,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32,
>,
pub get_module_artifacts: Option<
unsafe extern "C" fn(
instance: *mut c_void,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32,
>,
pub invoke: Option<
unsafe extern "C" fn(
instance: *mut c_void,
function: *const u8,
function_len: usize,
args: *const u8,
args_len: usize,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32,
>,
pub invoke_ex: Option<
unsafe extern "C" fn(
instance: *mut c_void,
function: *const u8,
function_len: usize,
args: *const u8,
args_len: usize,
out: *mut ModuleInvokeResult,
) -> i32,
>,
pub free_buffer: Option<unsafe extern "C" fn(ptr: *mut u8, len: usize)>,
pub drop: Option<unsafe extern "C" fn(instance: *mut c_void)>,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorModel {
Dynamic = 0,
Static = 1,
}
#[repr(C)]
pub struct LanguageRuntimeVTable {
pub init: Option<unsafe extern "C" fn(config: *const u8, config_len: usize) -> *mut c_void>,
pub register_types: Option<
unsafe extern "C" fn(instance: *mut c_void, types: *const u8, types_len: usize) -> i32,
>,
pub compile: Option<
unsafe extern "C" fn(
instance: *mut c_void,
name: *const u8,
name_len: usize,
source: *const u8,
source_len: usize,
param_names: *const u8,
param_names_len: usize,
param_types: *const u8,
param_types_len: usize,
return_type: *const u8,
return_type_len: usize,
is_async: bool,
out_error: *mut *mut u8,
out_error_len: *mut usize,
) -> *mut c_void,
>,
pub invoke: Option<
unsafe extern "C" fn(
instance: *mut c_void,
handle: *mut c_void,
args: *const u8,
args_len: usize,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32,
>,
pub dispose_function: Option<unsafe extern "C" fn(instance: *mut c_void, handle: *mut c_void)>,
pub language_id: Option<unsafe extern "C" fn(instance: *mut c_void) -> *const c_char>,
pub get_lsp_config: Option<
unsafe extern "C" fn(
instance: *mut c_void,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32,
>,
pub free_buffer: Option<unsafe extern "C" fn(ptr: *mut u8, len: usize)>,
pub drop: Option<unsafe extern "C" fn(instance: *mut c_void)>,
pub error_model: ErrorModel,
pub get_shape_source: Option<
unsafe extern "C" fn(
instance: *mut c_void,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32,
>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LanguageRuntimeLspConfig {
pub language_id: std::string::String,
pub server_command: Vec<std::string::String>,
pub file_extension: std::string::String,
pub extra_paths: Vec<std::string::String>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TypeSchemaExport {
pub name: std::string::String,
pub kind: TypeSchemaExportKind,
pub fields: Vec<TypeFieldExport>,
pub enum_variants: Option<Vec<EnumVariantExport>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TypeSchemaExportKind {
Struct,
Enum,
Alias,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TypeFieldExport {
pub name: std::string::String,
pub type_name: std::string::String,
pub optional: bool,
pub description: Option<std::string::String>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EnumVariantExport {
pub name: std::string::String,
pub payload_fields: Option<Vec<TypeFieldExport>>,
}
pub type GetPluginInfoFn = unsafe extern "C" fn() -> *const PluginInfo;
pub type GetDataSourceVTableFn = unsafe extern "C" fn() -> *const DataSourceVTable;
pub type GetOutputSinkVTableFn = unsafe extern "C" fn() -> *const OutputSinkVTable;
pub type GetModuleVTableFn = unsafe extern "C" fn() -> *const ModuleVTable;
pub type GetLanguageRuntimeVTableFn = unsafe extern "C" fn() -> *const LanguageRuntimeVTable;
pub type GetCapabilityManifestFn = unsafe extern "C" fn() -> *const CapabilityManifest;
pub type GetCapabilityVTableFn =
unsafe extern "C" fn(contract: *const u8, contract_len: usize) -> *const c_void;
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PluginError {
Success = 0,
InvalidArgument = 1,
ValidationFailed = 2,
ConnectionError = 3,
NotFound = 4,
Timeout = 5,
PermissionDenied = 6,
InternalError = 7,
NotImplemented = 8,
ResourceExhausted = 9,
NotInitialized = 10,
}
use std::collections::BTreeSet;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PermissionCategory {
Filesystem,
Network,
System,
Sandbox,
}
impl PermissionCategory {
pub fn name(&self) -> &'static str {
match self {
Self::Filesystem => "Filesystem",
Self::Network => "Network",
Self::System => "System",
Self::Sandbox => "Sandbox",
}
}
}
impl fmt::Display for PermissionCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Permission {
FsRead,
FsWrite,
FsScoped,
NetConnect,
NetListen,
NetScoped,
Process,
Env,
Time,
Random,
Vfs,
Deterministic,
Capture,
MemLimited,
TimeLimited,
OutputLimited,
}
impl Permission {
pub fn name(&self) -> &'static str {
match self {
Self::FsRead => "fs.read",
Self::FsWrite => "fs.write",
Self::FsScoped => "fs.scoped",
Self::NetConnect => "net.connect",
Self::NetListen => "net.listen",
Self::NetScoped => "net.scoped",
Self::Process => "sys.process",
Self::Env => "sys.env",
Self::Time => "sys.time",
Self::Random => "sys.random",
Self::Vfs => "sandbox.vfs",
Self::Deterministic => "sandbox.deterministic",
Self::Capture => "sandbox.capture",
Self::MemLimited => "sandbox.mem_limited",
Self::TimeLimited => "sandbox.time_limited",
Self::OutputLimited => "sandbox.output_limited",
}
}
pub fn description(&self) -> &'static str {
match self {
Self::FsRead => "Read files and directories",
Self::FsWrite => "Write, create, and delete files and directories",
Self::FsScoped => "Filesystem access scoped to specific paths",
Self::NetConnect => "Open outbound network connections",
Self::NetListen => "Listen for inbound network connections",
Self::NetScoped => "Network access scoped to specific hosts/ports",
Self::Process => "Spawn child processes",
Self::Env => "Read environment variables",
Self::Time => "Access wall-clock time",
Self::Random => "Access random number generation",
Self::Vfs => "Operate against a virtual filesystem",
Self::Deterministic => "Run in a deterministic runtime (fixed time, seeded RNG)",
Self::Capture => "Output is captured for inspection",
Self::MemLimited => "Memory usage is limited to a configured ceiling",
Self::TimeLimited => "Execution time is capped",
Self::OutputLimited => "Output volume is capped",
}
}
pub fn category(&self) -> PermissionCategory {
match self {
Self::FsRead | Self::FsWrite | Self::FsScoped => PermissionCategory::Filesystem,
Self::NetConnect | Self::NetListen | Self::NetScoped => PermissionCategory::Network,
Self::Process | Self::Env | Self::Time | Self::Random => PermissionCategory::System,
Self::Vfs
| Self::Deterministic
| Self::Capture
| Self::MemLimited
| Self::TimeLimited
| Self::OutputLimited => PermissionCategory::Sandbox,
}
}
pub fn all_variants() -> &'static [Permission] {
&[
Self::FsRead,
Self::FsWrite,
Self::FsScoped,
Self::NetConnect,
Self::NetListen,
Self::NetScoped,
Self::Process,
Self::Env,
Self::Time,
Self::Random,
Self::Vfs,
Self::Deterministic,
Self::Capture,
Self::MemLimited,
Self::TimeLimited,
Self::OutputLimited,
]
}
}
impl fmt::Display for Permission {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PermissionSet {
permissions: BTreeSet<Permission>,
}
impl Default for PermissionSet {
fn default() -> Self {
Self::pure()
}
}
impl PermissionSet {
pub fn pure() -> Self {
Self {
permissions: BTreeSet::new(),
}
}
pub fn readonly() -> Self {
Self {
permissions: [Permission::FsRead, Permission::Env, Permission::Time]
.into_iter()
.collect(),
}
}
pub fn full() -> Self {
Self {
permissions: Permission::all_variants().iter().copied().collect(),
}
}
pub fn from_iter(iter: impl IntoIterator<Item = Permission>) -> Self {
Self {
permissions: iter.into_iter().collect(),
}
}
pub fn insert(&mut self, perm: Permission) -> bool {
self.permissions.insert(perm)
}
pub fn remove(&mut self, perm: &Permission) -> bool {
self.permissions.remove(perm)
}
pub fn contains(&self, perm: &Permission) -> bool {
self.permissions.contains(perm)
}
pub fn is_subset(&self, other: &PermissionSet) -> bool {
self.permissions.is_subset(&other.permissions)
}
pub fn is_superset(&self, other: &PermissionSet) -> bool {
self.permissions.is_superset(&other.permissions)
}
pub fn union(&self, other: &PermissionSet) -> PermissionSet {
PermissionSet {
permissions: self
.permissions
.union(&other.permissions)
.copied()
.collect(),
}
}
pub fn intersection(&self, other: &PermissionSet) -> PermissionSet {
PermissionSet {
permissions: self
.permissions
.intersection(&other.permissions)
.copied()
.collect(),
}
}
pub fn difference(&self, other: &PermissionSet) -> PermissionSet {
PermissionSet {
permissions: self
.permissions
.difference(&other.permissions)
.copied()
.collect(),
}
}
pub fn is_empty(&self) -> bool {
self.permissions.is_empty()
}
pub fn len(&self) -> usize {
self.permissions.len()
}
pub fn iter(&self) -> impl Iterator<Item = &Permission> {
self.permissions.iter()
}
pub fn by_category(&self) -> std::collections::BTreeMap<PermissionCategory, Vec<Permission>> {
let mut map = std::collections::BTreeMap::new();
for perm in &self.permissions {
map.entry(perm.category())
.or_insert_with(Vec::new)
.push(*perm);
}
map
}
}
impl fmt::Display for PermissionSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let names: Vec<&str> = self.permissions.iter().map(|p| p.name()).collect();
write!(f, "{{{}}}", names.join(", "))
}
}
impl<const N: usize> From<[Permission; N]> for PermissionSet {
fn from(arr: [Permission; N]) -> Self {
Self {
permissions: arr.into_iter().collect(),
}
}
}
impl std::iter::FromIterator<Permission> for PermissionSet {
fn from_iter<I: IntoIterator<Item = Permission>>(iter: I) -> Self {
Self {
permissions: iter.into_iter().collect(),
}
}
}
impl IntoIterator for PermissionSet {
type Item = Permission;
type IntoIter = std::collections::btree_set::IntoIter<Permission>;
fn into_iter(self) -> Self::IntoIter {
self.permissions.into_iter()
}
}
impl<'a> IntoIterator for &'a PermissionSet {
type Item = &'a Permission;
type IntoIter = std::collections::btree_set::Iter<'a, Permission>;
fn into_iter(self) -> Self::IntoIter {
self.permissions.iter()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ScopeConstraints {
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub allowed_paths: Vec<std::string::String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub allowed_hosts: Vec<std::string::String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub max_memory_bytes: Option<u64>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub max_time_ms: Option<u64>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub max_output_bytes: Option<u64>,
}
impl ScopeConstraints {
pub fn none() -> Self {
Self {
allowed_paths: Vec::new(),
allowed_hosts: Vec::new(),
max_memory_bytes: None,
max_time_ms: None,
max_output_bytes: None,
}
}
}
impl Default for ScopeConstraints {
fn default() -> Self {
Self::none()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PermissionGrant {
pub permission: Permission,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub constraints: Option<ScopeConstraints>,
}
impl PermissionGrant {
pub fn unconstrained(permission: Permission) -> Self {
Self {
permission,
constraints: None,
}
}
pub fn scoped(permission: Permission, constraints: ScopeConstraints) -> Self {
Self {
permission,
constraints: Some(constraints),
}
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AlertSeverity {
Debug = 0,
Info = 1,
Warning = 2,
Error = 3,
Critical = 4,
}
#[repr(C)]
pub struct AlertHeader {
pub severity: AlertSeverity,
pub timestamp_ms: i64,
}
pub const ABI_VERSION: u32 = 3;
pub type GetAbiVersionFn = unsafe extern "C" fn() -> u32;
#[macro_export]
macro_rules! language_runtime_plugin {
(
name: $name:expr,
version: $version:expr,
description: $description:expr,
shape_source: $shape_source:expr,
vtable: {
init: $init:expr,
register_types: $register_types:expr,
compile: $compile:expr,
invoke: $invoke:expr,
dispose_function: $dispose_function:expr,
language_id: $language_id:expr,
get_lsp_config: $get_lsp_config:expr,
free_buffer: $free_buffer:expr,
drop: $drop_fn:expr $(,)?
} $(,)?
) => {
$crate::language_runtime_plugin!(@internal
name: $name,
version: $version,
description: $description,
shape_source_opt: Some($shape_source),
vtable: {
init: $init,
register_types: $register_types,
compile: $compile,
invoke: $invoke,
dispose_function: $dispose_function,
language_id: $language_id,
get_lsp_config: $get_lsp_config,
free_buffer: $free_buffer,
drop: $drop_fn,
}
);
};
(
name: $name:expr,
version: $version:expr,
description: $description:expr,
vtable: {
init: $init:expr,
register_types: $register_types:expr,
compile: $compile:expr,
invoke: $invoke:expr,
dispose_function: $dispose_function:expr,
language_id: $language_id:expr,
get_lsp_config: $get_lsp_config:expr,
free_buffer: $free_buffer:expr,
drop: $drop_fn:expr $(,)?
} $(,)?
) => {
$crate::language_runtime_plugin!(@internal
name: $name,
version: $version,
description: $description,
shape_source_opt: None,
vtable: {
init: $init,
register_types: $register_types,
compile: $compile,
invoke: $invoke,
dispose_function: $dispose_function,
language_id: $language_id,
get_lsp_config: $get_lsp_config,
free_buffer: $free_buffer,
drop: $drop_fn,
}
);
};
(@internal
name: $name:expr,
version: $version:expr,
description: $description:expr,
shape_source_opt: $shape_source_opt:expr,
vtable: {
init: $init:expr,
register_types: $register_types:expr,
compile: $compile:expr,
invoke: $invoke:expr,
dispose_function: $dispose_function:expr,
language_id: $language_id:expr,
get_lsp_config: $get_lsp_config:expr,
free_buffer: $free_buffer:expr,
drop: $drop_fn:expr $(,)?
} $(,)?
) => {
#[unsafe(no_mangle)]
pub extern "C" fn shape_plugin_info() -> *const $crate::PluginInfo {
static INFO: $crate::PluginInfo = $crate::PluginInfo {
name: $name.as_ptr(),
version: $version.as_ptr(),
plugin_type: $crate::PluginType::DataSource,
description: $description.as_ptr(),
};
&INFO
}
#[unsafe(no_mangle)]
pub extern "C" fn shape_abi_version() -> u32 {
$crate::ABI_VERSION
}
#[unsafe(no_mangle)]
pub extern "C" fn shape_capability_manifest() -> *const $crate::CapabilityManifest {
static CAPABILITIES: [$crate::CapabilityDescriptor; 1] =
[$crate::CapabilityDescriptor {
kind: $crate::CapabilityKind::LanguageRuntime,
contract: c"shape.language_runtime".as_ptr(),
version: c"1".as_ptr(),
flags: 0,
}];
static MANIFEST: $crate::CapabilityManifest = $crate::CapabilityManifest {
capabilities: CAPABILITIES.as_ptr(),
capabilities_len: CAPABILITIES.len(),
};
&MANIFEST
}
unsafe extern "C" fn __shape_get_shape_source(
_instance: *mut ::std::ffi::c_void,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
const SOURCE: Option<&str> = $shape_source_opt;
if out_ptr.is_null() || out_len.is_null() {
return 1;
}
match SOURCE {
Some(src) => {
let mut bytes = src.as_bytes().to_vec();
let len = bytes.len();
let ptr = bytes.as_mut_ptr();
::std::mem::forget(bytes);
unsafe {
*out_ptr = ptr;
*out_len = len;
}
0
}
None => {
unsafe {
*out_ptr = ::std::ptr::null_mut();
*out_len = 0;
}
0
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn shape_language_runtime_vtable() -> *const $crate::LanguageRuntimeVTable {
static VTABLE: $crate::LanguageRuntimeVTable = $crate::LanguageRuntimeVTable {
init: Some($init),
register_types: Some($register_types),
compile: Some($compile),
invoke: Some($invoke),
dispose_function: Some($dispose_function),
language_id: Some($language_id),
get_lsp_config: Some($get_lsp_config),
free_buffer: Some($free_buffer),
drop: Some($drop_fn),
error_model: $crate::ErrorModel::Dynamic,
get_shape_source: Some(__shape_get_shape_source),
};
&VTABLE
}
#[unsafe(no_mangle)]
pub extern "C" fn shape_capability_vtable(
contract: *const u8,
contract_len: usize,
) -> *const ::std::ffi::c_void {
if contract.is_null() {
return ::std::ptr::null();
}
let contract =
unsafe { ::std::slice::from_raw_parts(contract, contract_len) };
if contract == $crate::CAPABILITY_LANGUAGE_RUNTIME.as_bytes() {
shape_language_runtime_vtable() as *const ::std::ffi::c_void
} else {
::std::ptr::null()
}
}
};
}
#[macro_export]
macro_rules! query_param {
(
name: $name:expr,
description: $desc:expr,
param_type: $ptype:expr,
required: $req:expr
) => {
$crate::QueryParam {
name: concat!($name, "\0").as_ptr() as *const core::ffi::c_char,
description: concat!($desc, "\0").as_ptr() as *const core::ffi::c_char,
param_type: $ptype,
required: $req,
default_value: core::ptr::null(),
default_value_len: 0,
allowed_values: core::ptr::null(),
allowed_values_len: 0,
nested_schema: core::ptr::null(),
}
};
}
#[macro_export]
macro_rules! output_field {
(
name: $name:expr,
field_type: $ftype:expr,
description: $desc:expr
) => {
$crate::OutputField {
name: concat!($name, "\0").as_ptr() as *const core::ffi::c_char,
field_type: $ftype,
description: concat!($desc, "\0").as_ptr() as *const core::ffi::c_char,
}
};
}
#[cfg(test)]
mod permission_tests {
use super::*;
#[test]
fn permission_name_is_dotted() {
for perm in Permission::all_variants() {
let name = perm.name();
assert!(
name.contains('.'),
"Permission name '{}' should contain a dot",
name
);
}
}
#[test]
fn permission_description_is_nonempty() {
for perm in Permission::all_variants() {
assert!(!perm.description().is_empty());
}
}
#[test]
fn permission_category_roundtrip() {
assert_eq!(
Permission::FsRead.category(),
PermissionCategory::Filesystem
);
assert_eq!(
Permission::FsWrite.category(),
PermissionCategory::Filesystem
);
assert_eq!(
Permission::NetConnect.category(),
PermissionCategory::Network
);
assert_eq!(
Permission::NetListen.category(),
PermissionCategory::Network
);
assert_eq!(Permission::Process.category(), PermissionCategory::System);
assert_eq!(Permission::Env.category(), PermissionCategory::System);
assert_eq!(Permission::Time.category(), PermissionCategory::System);
assert_eq!(Permission::Random.category(), PermissionCategory::System);
assert_eq!(Permission::Vfs.category(), PermissionCategory::Sandbox);
assert_eq!(
Permission::Deterministic.category(),
PermissionCategory::Sandbox
);
}
#[test]
fn permission_display() {
assert_eq!(format!("{}", Permission::FsRead), "fs.read");
assert_eq!(format!("{}", Permission::NetConnect), "net.connect");
}
#[test]
fn all_variants_is_exhaustive() {
assert!(Permission::all_variants().len() >= 16);
}
#[test]
fn pure_is_empty() {
let set = PermissionSet::pure();
assert!(set.is_empty());
assert_eq!(set.len(), 0);
}
#[test]
fn readonly_contains_expected() {
let set = PermissionSet::readonly();
assert!(set.contains(&Permission::FsRead));
assert!(set.contains(&Permission::Env));
assert!(set.contains(&Permission::Time));
assert!(!set.contains(&Permission::FsWrite));
assert!(!set.contains(&Permission::NetConnect));
assert_eq!(set.len(), 3);
}
#[test]
fn full_contains_all() {
let set = PermissionSet::full();
for perm in Permission::all_variants() {
assert!(set.contains(perm), "full() missing {:?}", perm);
}
}
#[test]
fn union_combines() {
let a = PermissionSet::from([Permission::FsRead, Permission::NetConnect]);
let b = PermissionSet::from([Permission::FsWrite, Permission::NetConnect]);
let u = a.union(&b);
assert_eq!(u.len(), 3);
assert!(u.contains(&Permission::FsRead));
assert!(u.contains(&Permission::FsWrite));
assert!(u.contains(&Permission::NetConnect));
}
#[test]
fn intersection_narrows() {
let a = PermissionSet::from([Permission::FsRead, Permission::NetConnect]);
let b = PermissionSet::from([Permission::FsWrite, Permission::NetConnect]);
let i = a.intersection(&b);
assert_eq!(i.len(), 1);
assert!(i.contains(&Permission::NetConnect));
}
#[test]
fn difference_subtracts() {
let a = PermissionSet::from([Permission::FsRead, Permission::FsWrite, Permission::Env]);
let b = PermissionSet::from([Permission::FsWrite]);
let d = a.difference(&b);
assert_eq!(d.len(), 2);
assert!(d.contains(&Permission::FsRead));
assert!(d.contains(&Permission::Env));
assert!(!d.contains(&Permission::FsWrite));
}
#[test]
fn subset_superset() {
let small = PermissionSet::from([Permission::FsRead]);
let big = PermissionSet::from([Permission::FsRead, Permission::FsWrite]);
assert!(small.is_subset(&big));
assert!(!big.is_subset(&small));
assert!(big.is_superset(&small));
assert!(!small.is_superset(&big));
}
#[test]
fn insert_and_remove() {
let mut set = PermissionSet::pure();
assert!(set.insert(Permission::Time));
assert!(!set.insert(Permission::Time)); assert_eq!(set.len(), 1);
assert!(set.remove(&Permission::Time));
assert!(!set.remove(&Permission::Time)); assert!(set.is_empty());
}
#[test]
fn permission_set_display() {
let set = PermissionSet::from([Permission::FsRead, Permission::Env]);
let s = format!("{}", set);
assert!(s.starts_with('{'));
assert!(s.ends_with('}'));
assert!(s.contains("fs.read"));
assert!(s.contains("sys.env"));
}
#[test]
fn by_category_groups() {
let set = PermissionSet::from([
Permission::FsRead,
Permission::FsWrite,
Permission::NetConnect,
Permission::Time,
Permission::Vfs,
]);
let cats = set.by_category();
assert_eq!(cats[&PermissionCategory::Filesystem].len(), 2);
assert_eq!(cats[&PermissionCategory::Network].len(), 1);
assert_eq!(cats[&PermissionCategory::System].len(), 1);
assert_eq!(cats[&PermissionCategory::Sandbox].len(), 1);
}
#[test]
fn collect_from_iterator() {
let perms = vec![Permission::FsRead, Permission::Env];
let set: PermissionSet = perms.into_iter().collect();
assert_eq!(set.len(), 2);
}
#[test]
fn into_iter_owned() {
let set = PermissionSet::from([Permission::FsRead, Permission::Env]);
let v: Vec<Permission> = set.into_iter().collect();
assert_eq!(v.len(), 2);
}
#[test]
fn into_iter_ref() {
let set = PermissionSet::from([Permission::FsRead, Permission::Env]);
let v: Vec<&Permission> = (&set).into_iter().collect();
assert_eq!(v.len(), 2);
}
#[test]
fn from_array() {
let set = PermissionSet::from([Permission::Process, Permission::Random]);
assert_eq!(set.len(), 2);
assert!(set.contains(&Permission::Process));
assert!(set.contains(&Permission::Random));
}
#[test]
fn unconstrained_grant() {
let g = PermissionGrant::unconstrained(Permission::FsRead);
assert_eq!(g.permission, Permission::FsRead);
assert!(g.constraints.is_none());
}
#[test]
fn scoped_grant_with_paths() {
let c = ScopeConstraints {
allowed_paths: vec!["/tmp/*".into(), "/data/**".into()],
..Default::default()
};
let g = PermissionGrant::scoped(Permission::FsScoped, c);
assert_eq!(g.permission, Permission::FsScoped);
let sc = g.constraints.unwrap();
assert_eq!(sc.allowed_paths.len(), 2);
assert!(sc.allowed_hosts.is_empty());
}
#[test]
fn scoped_grant_with_limits() {
let c = ScopeConstraints {
max_memory_bytes: Some(1024 * 1024 * 64),
max_time_ms: Some(5000),
max_output_bytes: Some(1024 * 1024),
..Default::default()
};
let g = PermissionGrant::scoped(Permission::MemLimited, c);
let sc = g.constraints.unwrap();
assert_eq!(sc.max_memory_bytes, Some(64 * 1024 * 1024));
assert_eq!(sc.max_time_ms, Some(5000));
}
#[test]
fn category_display() {
assert_eq!(format!("{}", PermissionCategory::Filesystem), "Filesystem");
assert_eq!(format!("{}", PermissionCategory::Network), "Network");
assert_eq!(format!("{}", PermissionCategory::System), "System");
assert_eq!(format!("{}", PermissionCategory::Sandbox), "Sandbox");
}
#[test]
fn permission_set_equality() {
let a = PermissionSet::from([Permission::FsRead, Permission::Env]);
let b = PermissionSet::from([Permission::Env, Permission::FsRead]);
assert_eq!(a, b);
}
#[test]
fn permission_ord_is_deterministic() {
let set = PermissionSet::from([Permission::Random, Permission::FsRead, Permission::Vfs]);
let names: Vec<&str> = set.iter().map(|p| p.name()).collect();
let mut sorted = names.clone();
sorted.sort();
let names2: Vec<&str> = set.iter().map(|p| p.name()).collect();
assert_eq!(names, names2);
}
}