Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions crates/bamltype-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -724,12 +724,12 @@ fn normalize_field_attrs(
if let Some(adapter) = compat.with_adapter {
let with_expr = quote! {
&#runtime_crate::facet_ext::WithAdapterFns {
type_ir: || <#adapter as #runtime_crate::compat::BamlAdapter<#field_ty>>::type_ir(),
register: |reg| <#adapter as #runtime_crate::compat::BamlAdapter<#field_ty>>::register(reg),
type_ir: || <#adapter as #runtime_crate::adapters::FieldCodec<#field_ty>>::type_ir(),
register: |reg| <#adapter as #runtime_crate::adapters::FieldCodec<#field_ty>>::register(reg),
apply: |partial, value, path| {
let converted = <#adapter as #runtime_crate::compat::BamlAdapter<#field_ty>>::try_from_baml(value, path)?;
let converted = <#adapter as #runtime_crate::adapters::FieldCodec<#field_ty>>::try_from_baml(value, path)?;
partial.set(converted).map_err(|err| {
#runtime_crate::compat::BamlConvertError::new(
#runtime_crate::BamlConvertError::new(
::std::vec::Vec::new(),
"compatible type",
err.to_string(),
Expand Down
15 changes: 15 additions & 0 deletions crates/bamltype/src/adapters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//! Adapter extension points for advanced field-level conversions.

use baml_types::{BamlValue, TypeIR};

use crate::BamlConvertError;

/// Registry type used by field codecs to register custom schema artifacts.
pub type AdapterSchemaRegistry = crate::schema_registry::SchemaRegistry;

/// Field-level conversion + schema representation hook.
pub trait FieldCodec<T> {
fn type_ir() -> TypeIR;
fn register(_reg: &mut AdapterSchemaRegistry) {}
fn try_from_baml(value: BamlValue, path: Vec<String>) -> Result<T, BamlConvertError>;
}
2 changes: 1 addition & 1 deletion crates/bamltype/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use facet_reflect::{HasFields, HeapValue, Partial, Peek, ReflectError, ScalarTyp
use indexmap::IndexMap;

use crate::BamlValueWithFlags;
use crate::compat::BamlConvertError;
use crate::runtime::BamlConvertError;
use crate::schema_builder::internal_name_for_shape;

/// Error during BamlValue conversion.
Expand Down
5 changes: 3 additions & 2 deletions crates/bamltype/src/facet_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

use baml_types::{BamlValue, TypeIR};

use crate::compat::{BamlConvertError, Registry};
use crate::adapters::AdapterSchemaRegistry;
use crate::runtime::BamlConvertError;

/// Field-level adapter application function.
pub type AdapterApplyFn = fn(
Expand All @@ -18,7 +19,7 @@ pub struct WithAdapterFns {
/// Schema type representation callback.
pub type_ir: fn() -> TypeIR,
/// Schema registration callback.
pub register: fn(&mut Registry),
pub register: fn(&mut AdapterSchemaRegistry),
/// Value conversion callback.
pub apply: AdapterApplyFn,
}
Expand Down
19 changes: 10 additions & 9 deletions crates/bamltype/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,17 @@ pub use schema_builder::*;
mod convert;
pub use convert::{ConvertError, from_baml_value, from_baml_value_with_flags, to_baml_value};

pub mod compat;
pub mod facet_ext;
pub use compat::{
BamlAdapter, BamlConvertError, BamlTypeInternal, BamlTypeTrait, BamlValueConvert, Registry,
ToBamlValue, default_streaming_behavior, get_field, with_constraints,
mod runtime;
pub use runtime::{
BamlConvertError, BamlTypeInternal, BamlTypeTrait, BamlValueConvert, ToBamlValue,
default_streaming_behavior, get_field,
};

pub mod adapters;
mod schema_registry;

pub mod facet_ext;

/// A bundle containing everything needed to render schemas and parse LLM output.
#[derive(Debug, Clone)]
pub struct SchemaBundle {
Expand Down Expand Up @@ -91,10 +95,7 @@ pub trait BamlSchema: for<'a> facet::Facet<'a> {
fn baml_schema() -> &'static SchemaBundle;
}

/// Back-compat trait mirroring legacy bridge's `BamlType`.
///
/// This sits alongside the `#[BamlType]` attribute macro and offers the same
/// runtime trait entry points users expect from the old API.
/// Runtime trait for types that expose BAML schema + conversion entry points.
pub trait BamlType: BamlTypeInternal + BamlValueConvert + Sized + 'static {
fn baml_output_format() -> &'static OutputFormatContent;

Expand Down
164 changes: 164 additions & 0 deletions crates/bamltype/src/runtime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
//! Facet runtime traits and conversion helpers.

use baml_types::{BamlMap, BamlValue, TypeIR, type_meta};
use facet::Facet;
use internal_baml_jinja::types::OutputFormatContent;

use crate::BamlSchema;
use crate::convert;
use crate::schema_builder::build_type_ir_from_shape;

/// Error during BamlValue ↔ Rust conversion.
#[derive(Debug, Clone)]
pub struct BamlConvertError {
pub path: Vec<String>,
pub expected: &'static str,
pub got: String,
pub message: String,
}

impl BamlConvertError {
pub fn new(
path: Vec<String>,
expected: &'static str,
got: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self {
path,
expected,
got: got.into(),
message: message.into(),
}
}

pub fn with_path(mut self, segment: impl Into<String>) -> Self {
self.path.push(segment.into());
self
}

pub fn path_string(&self) -> String {
if self.path.is_empty() {
"<root>".to_string()
} else {
self.path.join(".")
}
}
}

impl std::fmt::Display for BamlConvertError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} (expected {}, got {}) at {}",
self.message,
self.expected,
self.got,
self.path_string()
)
}
}

impl std::error::Error for BamlConvertError {}

impl From<convert::ConvertError> for BamlConvertError {
fn from(err: convert::ConvertError) -> Self {
match err {
convert::ConvertError::Adapter(inner) => inner,
other => Self {
path: Vec::new(),
expected: "compatible type",
got: other.to_string(),
message: other.to_string(),
},
}
}
}

/// Internal type metadata.
pub trait BamlTypeInternal {
fn baml_internal_name() -> &'static str;
fn baml_type_ir() -> TypeIR;
}

/// Convert from BamlValue to Rust.
pub trait BamlValueConvert: Sized {
fn try_from_baml_value(value: BamlValue, path: Vec<String>) -> Result<Self, BamlConvertError>;
}

/// Convert from Rust to BamlValue.
pub trait ToBamlValue {
fn to_baml_value(&self) -> BamlValue;
}

/// Runtime trait used by schema rendering and parsing.
///
/// Named `BamlTypeTrait` to avoid collision with the `#[BamlType]` attribute macro.
pub trait BamlTypeTrait: BamlTypeInternal + BamlValueConvert + Sized + 'static {
fn baml_output_format() -> &'static OutputFormatContent;

fn baml_internal_name() -> &'static str {
<Self as BamlTypeInternal>::baml_internal_name()
}

fn baml_type_ir() -> TypeIR {
<Self as BamlTypeInternal>::baml_type_ir()
}
}

impl<T: Facet<'static>> BamlTypeInternal for T {
fn baml_internal_name() -> &'static str {
for attr in T::SHAPE.attributes {
if attr.ns != Some("bamltype") || attr.key != "internal_name" {
continue;
}

if let Some(name) = attr.get_as::<&'static str>() {
return name;
}
}

if let Some(name) = T::SHAPE.get_builtin_attr_value::<&'static str>("internal_name") {
name
} else {
std::any::type_name::<T>()
}
}

fn baml_type_ir() -> TypeIR {
build_type_ir_from_shape(T::SHAPE)
}
}

impl<T: Facet<'static>> BamlValueConvert for T {
fn try_from_baml_value(value: BamlValue, _path: Vec<String>) -> Result<Self, BamlConvertError> {
convert::from_baml_value(value).map_err(BamlConvertError::from)
}
}

impl<T: Facet<'static>> ToBamlValue for T {
fn to_baml_value(&self) -> BamlValue {
convert::to_baml_value(self).unwrap_or(BamlValue::Null)
}
}

impl<T: BamlSchema> BamlTypeTrait for T {
fn baml_output_format() -> &'static OutputFormatContent {
&T::baml_schema().output_format
}
}

/// Default streaming behavior helper.
pub fn default_streaming_behavior() -> type_meta::base::StreamingBehavior {
type_meta::base::StreamingBehavior::default()
}

/// Lookup helper matching legacy bridge semantics (`name` then optional alias).
pub fn get_field<'a>(
map: &'a BamlMap<String, BamlValue>,
name: &str,
alias: Option<&str>,
) -> Option<&'a BamlValue> {
map.get(name)
.or_else(|| alias.and_then(|alias| map.get(alias)))
}
8 changes: 4 additions & 4 deletions crates/bamltype/src/schema_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use facet::{Attr, ConstTypeId, Def, Field, Shape, Type, UserType};
use internal_baml_jinja::types::{Class, Enum, Name, OutputFormatContent};

use crate::SchemaBundle;
use crate::compat::Registry;
use crate::facet_ext;
use crate::schema_registry::SchemaRegistry;

/// Build a SchemaBundle from a facet Shape.
pub fn build_schema_bundle(shape: &'static Shape) -> SchemaBundle {
Expand All @@ -25,7 +25,7 @@ pub fn build_schema_bundle(shape: &'static Shape) -> SchemaBundle {

/// Build a TypeIR from a facet Shape (without building full schema).
///
/// Used by the compat layer to provide `BamlTypeInternal::baml_type_ir()`
/// Used by runtime trait implementations to provide `BamlTypeInternal::baml_type_ir()`
/// for any `Facet` type.
pub fn build_type_ir_from_shape(shape: &'static Shape) -> TypeIR {
let mut builder = SchemaBuilder::new();
Expand Down Expand Up @@ -70,7 +70,7 @@ struct SchemaBuilder {
visited: HashMap<ConstTypeId, (String, TypeIR)>,

/// Collected schema elements.
registry: Registry,
registry: SchemaRegistry,

/// Track which internal names are already used (for collision handling)
used_internal_names: HashMap<String, ConstTypeId>,
Expand Down Expand Up @@ -104,7 +104,7 @@ impl SchemaBuilder {
fn new() -> Self {
Self {
visited: HashMap::new(),
registry: Registry::new(),
registry: SchemaRegistry::new(),
used_internal_names: HashMap::new(),
name_counter: 0,
}
Expand Down
Loading
Loading