diff --git a/CURRENT_PLAN.md b/CURRENT_PLAN.md index fb5164df..d8dd9d5c 100644 --- a/CURRENT_PLAN.md +++ b/CURRENT_PLAN.md @@ -1,3 +1,10 @@ +> Status Update (2026-02-08): **Superseded historical plan**. +> +> Phase 1 (Bridge Root Excision) is now the active baseline: legacy bridge crates are removed from the workspace. +> The current runtime intentionally keeps `bamltype::compat`, `LegacySignature`, `LegacyPredict`, `MetaSignature`, and optimizer APIs unchanged. +> +> Phase 2 is next: remove remaining compat-trait coupling in typed paths and redesign signature/optimizer APIs to be facet-native. + Below is a “walk the codebase” integration plan that’s detailed enough to be used as a checklist while you implement. I’m going to treat `CURRENT_SPEC.md` as the source of truth, and I’ll point out the few places where the spec implies machinery you don’t currently have (notably: serializing typed demo values and prompting inputs without `serde_json::Value`). I’ll organize this into phases, but each phase is still broken down by file, exact insertion points, and what each piece does. @@ -24,20 +31,20 @@ I’ll organize this into phases, but each phase is still broken down by file, e ## Phase 0: Add dependencies and decide compatibility mode -### 0.1 Add baml-bridge dependency to dspy-rs +### 0.1 Add legacy bridge dependency to dspy-rs **File:** `crates/dspy-rs/Cargo.toml` Add: -* A path dependency to the bridge crate (crate name is likely `baml-bridge` in Cargo, imported as `baml_bridge` in code). +* A path dependency to the bridge crate (crate name is likely `legacy bridge` in Cargo, imported as `legacy_bridge` in code). * Enable the derive feature so users can `#[derive(BamlType)]`. Example: ```toml [dependencies] -baml-bridge = { path = "../baml-bridge", features = ["derive"] } +legacy bridge = { path = "../legacy bridge", features = ["derive"] } indexmap = "..." # already present ``` @@ -45,7 +52,7 @@ indexmap = "..." # already present **Side effects / gotchas** -* You’ll pull in `minijinja` and related crates through baml-bridge. This should be fine, but expect compile times to rise. +* You’ll pull in `minijinja` and related crates through legacy bridge. This should be fine, but expect compile times to rise. ### 0.2 Decide whether to keep the legacy API during migration @@ -77,20 +84,20 @@ This phase is purely internal scaffolding: new traits, new errors, new result ty Add re-exports near the existing `pub use ...` block. -You want dsrs-macros to generate code that references `dspy_rs::TypeIR`, `dspy_rs::Constraint`, etc without depending on the baml-bridge crate directly. +You want dsrs-macros to generate code that references `dspy_rs::TypeIR`, `dspy_rs::Constraint`, etc without depending on the legacy bridge crate directly. Recommended re-exports: ```rust -pub use baml_bridge; // optional: re-export the crate for power-users +pub use legacy_bridge; // optional: re-export the crate for power-users -pub use baml_bridge::BamlType; // derive macro (when feature derive enabled) -pub use baml_bridge::baml_types::{ +pub use legacy_bridge::BamlType; // derive macro (when feature derive enabled) +pub use legacy_bridge::baml_types::{ BamlValue, Constraint, ConstraintLevel, ResponseCheck, TypeIR, StreamingMode, TypeValue, }; -pub use baml_bridge::internal_baml_jinja::types::{OutputFormatContent, RenderOptions}; -pub use baml_bridge::jsonish::deserializer::deserialize_flags::Flag; -pub use baml_bridge::convert::BamlConvertError; // if not already public via bridge +pub use legacy_bridge::internal_baml_jinja::types::{OutputFormatContent, RenderOptions}; +pub use legacy_bridge::jsonish::deserializer::deserialize_flags::Flag; +pub use legacy_bridge::convert::BamlConvertError; // if not already public via bridge ``` **Why:** your new `#[derive(Signature)]` macro will generate code that constructs `Constraint`, `TypeIR`, etc. Having them in `dspy_rs` avoids fragile cross-crate paths. @@ -141,8 +148,8 @@ So you want: ```rust pub trait Signature: Send + Sync + 'static { - type Input: baml_bridge::BamlType; // from baml-bridge - type Output: baml_bridge::BamlType; // output-only internal struct + type Input: legacy_bridge::BamlType; // from legacy bridge + type Output: legacy_bridge::BamlType; // output-only internal struct fn instruction() -> &'static str; @@ -223,7 +230,7 @@ Match the spec variants; you’ll be creating these in `ChatAdapter::parse_typed #### `ConversionError` -Map cleanly from baml-bridge’s `BamlConvertError`: +Map cleanly from legacy bridge’s `BamlConvertError`: * type mismatch * missing field @@ -277,9 +284,9 @@ And implement: This is the one spec-implied piece you don’t currently have: you need to format typed inputs and typed demos without relying on `serde_json::Value` in user code. -### 2.1 Add `ToBamlValue` to baml-bridge (bridge crate) +### 2.1 Add `ToBamlValue` to legacy bridge (bridge crate) -**File:** `crates/baml-bridge/src/lib.rs` +**File:** `crates/legacy bridge/src/lib.rs` Add a new trait: @@ -291,27 +298,27 @@ pub trait ToBamlValue { Implement it for: -* `String`, `bool`, numeric primitives already supported by baml-bridge +* `String`, `bool`, numeric primitives already supported by legacy bridge * `Option` * `Vec` * `HashMap`, `BTreeMap` -**Why here:** This keeps “BAML interop” in the BAML bridge crate, and it’s accessible from `dspy-rs`. +**Why here:** This keeps “BAML interop” in the Legacy bridge (removed) crate, and it’s accessible from `dspy-rs`. ### 2.2 Extend `#[derive(BamlType)]` to also implement `ToBamlValue` -**File:** `crates/baml-bridge-derive/src/lib.rs` +**File:** `crates/legacy bridge derive/src/lib.rs` You will: * For structs: implement `ToBamlValue` by converting each field into a `BamlValue` and returning a `BamlValue::Class(name, map)` -* For enums: mirror how baml-bridge expects enums to be represented in `BamlValue` (likely `BamlValue::Enum` for unit enums, and for tagged unions either `Class` with tag field or whatever its conversion expects) +* For enums: mirror how legacy bridge expects enums to be represented in `BamlValue` (likely `BamlValue::Enum` for unit enums, and for tagged unions either `Class` with tag field or whatever its conversion expects) -This is the most sensitive part of baml-bridge changes because you must align with how `BamlValueConvert` expects values to look. +This is the most sensitive part of legacy bridge changes because you must align with how `BamlValueConvert` expects values to look. **Implementation strategy to reduce risk:** -* Look at how baml-bridge derive currently generates conversion *from* `BamlValue`. +* Look at how legacy bridge derive currently generates conversion *from* `BamlValue`. * Implement the exact inverse representation. * Keep unit enums as `BamlValue::Enum(enum_name, variant_string)` if that’s how parsing returns them. @@ -432,10 +439,10 @@ struct __QAAll { In generated code: -* `impl ::dspy_rs::baml_bridge::BamlTypeInternal for QA { ... }` -* `impl ::dspy_rs::baml_bridge::BamlValueConvert for QA { ... }` -* `impl ::dspy_rs::baml_bridge::BamlType for QA { ... }` -* `impl ::dspy_rs::baml_bridge::ToBamlValue for QA { ... }` (optional but useful) +* `impl ::dspy_rs::legacy_bridge::BamlTypeInternal for QA { ... }` +* `impl ::dspy_rs::legacy_bridge::BamlValueConvert for QA { ... }` +* `impl ::dspy_rs::legacy_bridge::BamlType for QA { ... }` +* `impl ::dspy_rs::legacy_bridge::ToBamlValue for QA { ... }` (optional but useful) Each impl can be “convert to/from __QAAll”. @@ -479,8 +486,8 @@ Example for confidence: ```rust fn __qa_confidence_type_ir() -> ::dspy_rs::TypeIR { - let ty = ::baml_type_ir(); - ::dspy_rs::baml_bridge::with_constraints(ty, vec![ + let ty = ::baml_type_ir(); + ::dspy_rs::legacy_bridge::with_constraints(ty, vec![ ::dspy_rs::Constraint::new_check("range", "this >= 0.0 && this <= 1.0"), ]) } @@ -706,24 +713,24 @@ Algorithm: * Parse via jsonish: ```rust -let parsed: baml_bridge::jsonish::BamlValueWithFlags = - baml_bridge::jsonish::from_str(S::output_format_content(), &ty, &raw_text, true) +let parsed: legacy_bridge::jsonish::BamlValueWithFlags = + legacy_bridge::jsonish::from_str(S::output_format_content(), &ty, &raw_text, true) .map_err(|e| ParseError::CoercionFailed { ... })?; ``` * Collect flags: - * Copy the recursion logic from `crates/baml-bridge/src/lib.rs` (`collect_flags_recursive`) but operate on this one field node. + * Copy the recursion logic from `crates/legacy bridge/src/lib.rs` (`collect_flags_recursive`) but operate on this one field node. * Store into `Vec`. * Convert parsed value into `BamlValue`: - * In baml-bridge they do `let baml_value_with_meta: BamlValueWithMeta = parsed.clone().into(); let baml_value: BamlValue = baml_value_with_meta.into();` + * In legacy bridge they do `let baml_value_with_meta: BamlValueWithMeta = parsed.clone().into(); let baml_value: BamlValue = baml_value_with_meta.into();` * Use same conversion. * Run constraints: - * Call `run_user_checks(&baml_value, &ty)` (import it from baml-bridge’s `jsonish::deserializer::coercer::run_user_checks` as bridge does). + * Call `run_user_checks(&baml_value, &ty)` (import it from legacy bridge’s `jsonish::deserializer::coercer::run_user_checks` as bridge does). * For each result: * If `Check`: push `ConstraintResult` into `FieldMeta.checks` @@ -748,9 +755,9 @@ let parsed: baml_bridge::jsonish::BamlValueWithFlags = * Construct a full `BamlValue` for the output struct: * `BamlValue::Class("__QAOutput", map)` - * Convert to `S::Output` using baml-bridge conversion: + * Convert to `S::Output` using legacy bridge conversion: - * `::try_from_baml_value(...)` + * `::try_from_baml_value(...)` * Return `(typed_output, metas)` **Why parse per-field:** matches spec and preserves partial success. @@ -1081,7 +1088,7 @@ Here’s the tightest “touch list” to keep you oriented. * `crates/dspy-rs/Cargo.toml` - * add `baml-bridge` dependency + * add `legacy bridge` dependency * `crates/dspy-rs/src/lib.rs` @@ -1132,13 +1139,13 @@ Here’s the tightest “touch list” to keep you oriented. * rename/gate/remove old `#[Signature]` attribute macro * generate `QAInput`, `__QAOutput`, `__QAAll`, `FieldSpec` arrays, and impls described above -### baml-bridge +### legacy bridge -* `crates/baml-bridge/src/lib.rs` +* `crates/legacy bridge/src/lib.rs` * add `ToBamlValue` trait and primitive/container impls -* `crates/baml-bridge-derive/src/lib.rs` +* `crates/legacy bridge derive/src/lib.rs` * extend derive(BamlType) to implement `ToBamlValue` for structs/enums diff --git a/CURRENT_SPEC.md b/CURRENT_SPEC.md index e6832a16..657e4793 100644 --- a/CURRENT_SPEC.md +++ b/CURRENT_SPEC.md @@ -4,6 +4,11 @@ **Status:** Draft **Last Updated:** 2026-01-08 +> Status Update (2026-02-08): +> Legacy bridge crates are removed from the workspace. +> Current typed and optimizer contracts remain unchanged in Phase 1. +> Phase 2 next: compat-trait removal from typed paths plus signature/optimizer API redesign for facet-native runtime. + --- ## 1. Overview @@ -1555,7 +1560,7 @@ Context: ### 13.3 Crate Structure -- Fold baml-bridge crates into dsrs? +- Fold legacy bridge crates into dsrs? - Keep as dependency? - Public API surface considerations? @@ -1570,7 +1575,7 @@ Context: ## 14. Implementation Order ### Phase 1: Foundation -1. Add baml-bridge crates as dependencies (or vendor) +1. Add legacy bridge crates as dependencies (or vendor) 2. Implement `BamlType` trait and primitive impls 3. Implement `#[derive(BamlType)]` macro diff --git a/Cargo.lock b/Cargo.lock index 455a6a69..b30d69d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -458,36 +458,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "baml-bridge" -version = "0.1.0" -dependencies = [ - "anyhow", - "baml-bridge-derive", - "baml-types", - "expect-test", - "indexmap", - "internal-baml-jinja", - "jsonish", - "minijinja", - "proptest", - "serde", - "serde_json", - "sha2", - "thiserror", - "trybuild", -] - -[[package]] -name = "baml-bridge-derive" -version = "0.1.0" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "baml-ids" version = "0.0.1" diff --git a/crates/baml-bridge-derive/Cargo.toml b/crates/baml-bridge-derive/Cargo.toml deleted file mode 100644 index e7e7fc09..00000000 --- a/crates/baml-bridge-derive/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "baml-bridge-derive" -version = "0.1.0" -edition = "2021" -license = "Apache-2.0" -publish = false -description = "Derive macro for baml-bridge" - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1.0" -quote = "1.0" -syn = { version = "2.0", features = ["full", "parsing", "extra-traits"] } -convert_case = "0.6.0" diff --git a/crates/baml-bridge-derive/src/lib.rs b/crates/baml-bridge-derive/src/lib.rs deleted file mode 100644 index 47b636c4..00000000 --- a/crates/baml-bridge-derive/src/lib.rs +++ /dev/null @@ -1,2411 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::{ - parse_macro_input, spanned::Spanned, Attribute, Data, DataEnum, DataStruct, DeriveInput, Expr, - ExprLit, Fields, Lit, Meta, Path, Type, -}; - -#[proc_macro_derive(BamlType, attributes(baml, serde))] -pub fn derive_baml_type(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - match expand_derive(&input) { - Ok(tokens) => tokens.into(), - Err(err) => err.to_compile_error().into(), - } -} - -fn expand_derive(input: &DeriveInput) -> syn::Result { - match &input.data { - Data::Struct(data) => derive_struct(input, data), - Data::Enum(data) => derive_enum(input, data), - Data::Union(_) => Err(syn::Error::new_spanned( - input, - "BamlType does not support `union` items; hint: use a struct or enum instead", - )), - } -} - -#[derive(Default)] -struct ContainerAttrs { - name: Option, - internal_name: Option, - rename_all: Option, - constraints: Vec, - tag: Option, - as_union: bool, - as_enum: bool, - description: Option, -} - -#[derive(Default)] -struct FieldAttrs { - alias: Option, - skip: bool, - default: bool, - with: Option, - constraints: Vec, - description: Option, - int_repr: Option, - map_key_repr: Option, -} - -#[derive(Default)] -struct VariantAttrs { - alias: Option, - description: Option, -} - -#[derive(Clone, Copy)] -enum RenameRule { - Camel, - Snake, - Pascal, - Kebab, - ScreamingSnake, - Lower, - Upper, - ScreamingKebab, -} - -#[derive(Clone, Copy)] -enum IntRepr { - String, - I64, -} - -#[derive(Clone, Copy)] -enum MapKeyRepr { - String, - Pairs, -} - -struct ConstraintSpec { - level: ConstraintLevelSpec, - label: String, - expr: String, -} - -struct MapEntryInfo { - internal_name_expr: proc_macro2::TokenStream, - rendered_name: Option, -} - -#[derive(Clone, Copy)] -enum ConstraintLevelSpec { - Check, - Assert, -} - -fn derive_struct(input: &DeriveInput, data: &DataStruct) -> syn::Result { - let name = &input.ident; - let container_attrs = parse_container_attrs(&input.attrs)?; - - let internal_name_expr = container_attrs - .internal_name - .as_ref() - .map(|name| quote! { #name }) - .unwrap_or_else(|| quote! { concat!(module_path!(), "::", stringify!(#name)) }); - let rendered_name = container_attrs - .name - .clone() - .unwrap_or_else(|| name.to_string()); - - let rename_all = container_attrs.rename_all; - - let fields = match &data.fields { - Fields::Named(fields) => fields, - Fields::Unit => { - return Err(syn::Error::new_spanned( - input, - "Unit structs are not supported for BAML outputs; hint: use a named-field struct or enum", - )) - } - Fields::Unnamed(_) => { - return Err(syn::Error::new_spanned( - input, - "Tuple structs are not supported for BAML outputs; hint: use a named-field struct", - )) - } - }; - - let mut field_defs = Vec::new(); - let mut register_calls = Vec::new(); - let mut field_inits = Vec::new(); - let mut to_value_inserts = Vec::new(); - let mut field_idents = Vec::new(); - - for field in &fields.named { - let ident = field.ident.as_ref().unwrap(); - let field_name = ident.to_string(); - let field_attrs = parse_field_attrs(&field.attrs)?; - - if field_attrs.skip { - field_inits.push(quote! { #ident: ::std::default::Default::default() }); - field_idents.push(ident); - continue; - } - - let field_desc = field_description(&field_attrs, &field.attrs); - let field_alias = field_alias(&field_attrs, rename_all, &field_name); - let rendered_field = field_alias.clone().unwrap_or_else(|| field_name.clone()); - let map_entry_info = - map_entry_info(&field_name, &rendered_field, None, None, &field_attrs)?; - - let field_type_ir = type_ir_for_field(&field.ty, &field_attrs, map_entry_info.as_ref())?; - let field_type_ir = with_constraints_tokens(field_type_ir, &field_attrs.constraints); - - let name_expr = name_tokens(&field_name, field_alias.as_deref()); - let desc_expr = match field_desc { - Some(desc) => quote! { Some(#desc.to_string()) }, - None => quote! { None }, - }; - - field_defs.push(quote! { - (#name_expr, #field_type_ir, #desc_expr, false) - }); - - register_calls.push(register_call_tokens( - &field.ty, - &field_attrs, - map_entry_info.as_ref(), - )?); - - let conversion = - field_conversion_tokens(&field.ty, &field_attrs, &field_name, field_alias.as_deref())?; - field_inits.push(quote! { #ident: #conversion }); - let to_value = field_to_value_tokens(&field.ty, &field_attrs, quote! { &self.#ident })?; - to_value_inserts.push(quote! { - fields.insert(#field_name.to_string(), #to_value); - }); - field_idents.push(ident); - } - - let type_constraints = constraints_tokens(&container_attrs.constraints); - let type_ir = quote! {{ - let mut r#type = ::baml_bridge::baml_types::TypeIR::class( - ::baml_internal_name(), - ); - r#type.meta_mut().constraints.extend(#type_constraints); - r#type - }}; - - let class_name_expr = name_tokens_expr( - quote! { ::baml_internal_name().to_string() }, - Some(&rendered_name), - ); - let class_desc = container_description(&container_attrs, &input.attrs); - let class_desc_expr = match class_desc { - Some(desc) => quote! { Some(#desc.to_string()) }, - None => quote! { None }, - }; - - let class_def = quote! { - ::baml_bridge::internal_baml_jinja::types::Class { - name: #class_name_expr, - description: #class_desc_expr, - namespace: ::baml_bridge::baml_types::StreamingMode::NonStreaming, - fields: vec![#(#field_defs),*], - constraints: #type_constraints, - streaming_behavior: ::baml_bridge::default_streaming_behavior(), - } - }; - - let register_impl = quote! { - impl ::baml_bridge::BamlTypeInternal for #name { - fn baml_internal_name() -> &'static str { - #internal_name_expr - } - - fn baml_type_ir() -> ::baml_bridge::baml_types::TypeIR { - #type_ir - } - - fn register(reg: &mut ::baml_bridge::Registry) { - if !reg.mark_type(::baml_internal_name()) { - return; - } - reg.register_class(#class_def); - #(#register_calls;)* - } - } - }; - - let value_convert_impl = quote! { - impl ::baml_bridge::BamlValueConvert for #name { - fn try_from_baml_value( - value: ::baml_bridge::baml_types::BamlValue, - path: Vec, - ) -> Result { - let map = match value { - ::baml_bridge::baml_types::BamlValue::Class(_, map) - | ::baml_bridge::baml_types::BamlValue::Map(map) => map, - other => { - return Err(::baml_bridge::BamlConvertError::new( - path, - "object", - format!("{other:?}"), - "expected an object", - )) - } - }; - - Ok(Self { - #(#field_inits),* - }) - } - } - }; - - let to_value_impl = quote! { - impl ::baml_bridge::ToBamlValue for #name { - fn to_baml_value(&self) -> ::baml_bridge::baml_types::BamlValue { - let mut fields = ::baml_bridge::baml_types::BamlMap::new(); - #(#to_value_inserts)* - ::baml_bridge::baml_types::BamlValue::Class( - ::baml_internal_name().to_string(), - fields, - ) - } - } - }; - - let output_format_impl = quote! { - impl ::baml_bridge::BamlType for #name { - fn baml_output_format() -> &'static ::baml_bridge::internal_baml_jinja::types::OutputFormatContent { - static OUTPUT_FORMAT: ::std::sync::OnceLock<::baml_bridge::internal_baml_jinja::types::OutputFormatContent> = ::std::sync::OnceLock::new(); - OUTPUT_FORMAT.get_or_init(|| { - let mut reg = ::baml_bridge::Registry::new(); - ::register(&mut reg); - reg.build(::baml_type_ir()) - }) - } - } - }; - - Ok(quote! { - #register_impl - #value_convert_impl - #to_value_impl - #output_format_impl - }) -} - -fn derive_enum(input: &DeriveInput, data: &DataEnum) -> syn::Result { - let name = &input.ident; - let container_attrs = parse_container_attrs(&input.attrs)?; - - let internal_name_expr = container_attrs - .internal_name - .as_ref() - .map(|name| quote! { #name }) - .unwrap_or_else(|| quote! { concat!(module_path!(), "::", stringify!(#name)) }); - let rendered_name = container_attrs - .name - .clone() - .unwrap_or_else(|| name.to_string()); - - let rename_all = container_attrs.rename_all; - - let mut has_data_variant = false; - for variant in &data.variants { - match &variant.fields { - Fields::Unit => {} - Fields::Named(_) => has_data_variant = true, - Fields::Unnamed(_) => return Err(syn::Error::new_spanned( - variant, - "Tuple enum variants are not supported; hint: use a unit or struct-like variant", - )), - } - } - - if container_attrs.as_enum && has_data_variant { - return Err(syn::Error::new_spanned( - input, - "as_enum is only valid for unit enums; hint: remove #[baml(as_enum)] or convert variants to unit", - )); - } - - let type_constraints = constraints_tokens(&container_attrs.constraints); - - if !has_data_variant { - derive_unit_enum( - name, - data, - &internal_name_expr, - &rendered_name, - rename_all, - &container_attrs, - &input.attrs, - type_constraints, - ) - } else { - derive_data_enum( - name, - data, - &internal_name_expr, - &rendered_name, - rename_all, - &container_attrs, - type_constraints, - ) - } -} - -#[allow(clippy::too_many_arguments)] -fn derive_unit_enum( - name: &syn::Ident, - data: &DataEnum, - internal_name_expr: &proc_macro2::TokenStream, - rendered_name: &str, - rename_all: Option, - container_attrs: &ContainerAttrs, - raw_attrs: &[Attribute], - type_constraints: proc_macro2::TokenStream, -) -> syn::Result { - if container_attrs.as_union { - return derive_unit_enum_as_union( - name, - data, - internal_name_expr, - rendered_name, - rename_all, - type_constraints, - ); - } - - let mut values = Vec::new(); - let mut match_arms = Vec::new(); - let mut to_value_match_arms = Vec::new(); - - for variant in &data.variants { - let variant_ident = &variant.ident; - let variant_name = variant_ident.to_string(); - let variant_attrs = parse_variant_attrs(&variant.attrs)?; - let rendered_variant = variant_alias(&variant_attrs, rename_all, &variant_name); - let variant_desc = variant_description(&variant_attrs, &variant.attrs); - - let name_expr = name_tokens(&variant_name, rendered_variant.as_deref()); - let desc_expr = match variant_desc { - Some(desc) => quote! { Some(#desc.to_string()) }, - None => quote! { None }, - }; - values.push(quote! { (#name_expr, #desc_expr) }); - - let match_values = match_strings_for_variant(&variant_name, rendered_variant.as_deref()); - match_arms.push(quote! { #(#match_values)|* => Ok(Self::#variant_ident) }); - let rendered_variant_value = rendered_variant - .clone() - .unwrap_or_else(|| variant_name.clone()); - to_value_match_arms.push(quote! { - Self::#variant_ident => ::baml_bridge::baml_types::BamlValue::Enum( - ::baml_internal_name().to_string(), - #rendered_variant_value.to_string(), - ) - }); - } - - let enum_name_expr = name_tokens_expr( - quote! { #internal_name_expr.to_string() }, - Some(rendered_name), - ); - let enum_desc = container_description(container_attrs, raw_attrs); - let enum_desc_expr = match enum_desc { - Some(desc) => quote! { Some(#desc.to_string()) }, - None => quote! { None }, - }; - - let type_ir = quote! {{ - let mut r#type = ::baml_bridge::baml_types::TypeIR::r#enum( - ::baml_internal_name(), - ); - r#type.meta_mut().constraints.extend(#type_constraints); - r#type - }}; - - let register_impl = quote! { - impl ::baml_bridge::BamlTypeInternal for #name { - fn baml_internal_name() -> &'static str { - #internal_name_expr - } - - fn baml_type_ir() -> ::baml_bridge::baml_types::TypeIR { - #type_ir - } - - fn register(reg: &mut ::baml_bridge::Registry) { - if !reg.mark_type(::baml_internal_name()) { - return; - } - reg.register_enum(::baml_bridge::internal_baml_jinja::types::Enum { - name: #enum_name_expr, - description: #enum_desc_expr, - values: vec![#(#values),*], - constraints: #type_constraints, - }); - } - } - }; - - let value_convert_impl = quote! { - impl ::baml_bridge::BamlValueConvert for #name { - fn try_from_baml_value( - value: ::baml_bridge::baml_types::BamlValue, - path: Vec, - ) -> Result { - let value = match value { - ::baml_bridge::baml_types::BamlValue::Enum(_, value) - | ::baml_bridge::baml_types::BamlValue::String(value) => value, - other => { - return Err(::baml_bridge::BamlConvertError::new( - path, - "enum", - format!("{other:?}"), - "expected an enum value", - )) - } - }; - - match value.as_str() { - #(#match_arms),*, - _ => Err(::baml_bridge::BamlConvertError::new( - path, - "enum", - value, - "unknown enum variant", - )), - } - } - } - }; - - let to_value_impl = quote! { - impl ::baml_bridge::ToBamlValue for #name { - fn to_baml_value(&self) -> ::baml_bridge::baml_types::BamlValue { - match self { - #(#to_value_match_arms),* - } - } - } - }; - - let output_format_impl = quote! { - impl ::baml_bridge::BamlType for #name { - fn baml_output_format() -> &'static ::baml_bridge::internal_baml_jinja::types::OutputFormatContent { - static OUTPUT_FORMAT: ::std::sync::OnceLock<::baml_bridge::internal_baml_jinja::types::OutputFormatContent> = ::std::sync::OnceLock::new(); - OUTPUT_FORMAT.get_or_init(|| { - let mut reg = ::baml_bridge::Registry::new(); - ::register(&mut reg); - reg.build(::baml_type_ir()) - }) - } - } - }; - - Ok(quote! { - #register_impl - #value_convert_impl - #to_value_impl - #output_format_impl - }) -} - -fn derive_unit_enum_as_union( - name: &syn::Ident, - data: &DataEnum, - internal_name_expr: &proc_macro2::TokenStream, - _rendered_name: &str, - rename_all: Option, - type_constraints: proc_macro2::TokenStream, -) -> syn::Result { - let mut literals = Vec::new(); - let mut match_arms = Vec::new(); - let mut to_value_match_arms = Vec::new(); - - for variant in &data.variants { - let variant_ident = &variant.ident; - let variant_name = variant_ident.to_string(); - let variant_attrs = parse_variant_attrs(&variant.attrs)?; - let rendered_variant = variant_alias(&variant_attrs, rename_all, &variant_name); - let literal = rendered_variant - .clone() - .unwrap_or_else(|| variant_name.clone()); - - literals.push( - quote! { ::baml_bridge::baml_types::TypeIR::literal_string(#literal.to_string()) }, - ); - let match_values = match_strings_for_variant(&variant_name, rendered_variant.as_deref()); - match_arms.push(quote! { #(#match_values)|* => Ok(Self::#variant_ident) }); - to_value_match_arms.push(quote! { - Self::#variant_ident => ::baml_bridge::baml_types::BamlValue::Enum( - ::baml_internal_name().to_string(), - #literal.to_string(), - ) - }); - } - - let type_ir = quote! {{ - let mut r#type = ::baml_bridge::baml_types::TypeIR::union_with_meta( - vec![#(#literals),*], - ::baml_bridge::baml_types::type_meta::IR::default(), - ); - r#type.meta_mut().constraints.extend(#type_constraints); - r#type - }}; - - let register_impl = quote! { - impl ::baml_bridge::BamlTypeInternal for #name { - fn baml_internal_name() -> &'static str { - #internal_name_expr - } - - fn baml_type_ir() -> ::baml_bridge::baml_types::TypeIR { - #type_ir - } - - fn register(_reg: &mut ::baml_bridge::Registry) {} - } - }; - - let value_convert_impl = quote! { - impl ::baml_bridge::BamlValueConvert for #name { - fn try_from_baml_value( - value: ::baml_bridge::baml_types::BamlValue, - path: Vec, - ) -> Result { - let value = match value { - ::baml_bridge::baml_types::BamlValue::Enum(_, value) - | ::baml_bridge::baml_types::BamlValue::String(value) => value, - other => { - return Err(::baml_bridge::BamlConvertError::new( - path, - "enum", - format!("{other:?}"), - "expected an enum value", - )) - } - }; - - match value.as_str() { - #(#match_arms),*, - _ => Err(::baml_bridge::BamlConvertError::new( - path, - "enum", - value, - "unknown enum variant", - )), - } - } - } - }; - - let to_value_impl = quote! { - impl ::baml_bridge::ToBamlValue for #name { - fn to_baml_value(&self) -> ::baml_bridge::baml_types::BamlValue { - match self { - #(#to_value_match_arms),* - } - } - } - }; - - let output_format_impl = quote! { - impl ::baml_bridge::BamlType for #name { - fn baml_output_format() -> &'static ::baml_bridge::internal_baml_jinja::types::OutputFormatContent { - static OUTPUT_FORMAT: ::std::sync::OnceLock<::baml_bridge::internal_baml_jinja::types::OutputFormatContent> = ::std::sync::OnceLock::new(); - OUTPUT_FORMAT.get_or_init(|| { - let mut reg = ::baml_bridge::Registry::new(); - ::register(&mut reg); - reg.build(::baml_type_ir()) - }) - } - } - }; - - Ok(quote! { - #register_impl - #value_convert_impl - #to_value_impl - #output_format_impl - }) -} - -fn derive_data_enum( - name: &syn::Ident, - data: &DataEnum, - internal_name_expr: &proc_macro2::TokenStream, - rendered_name: &str, - rename_all: Option, - container_attrs: &ContainerAttrs, - type_constraints: proc_macro2::TokenStream, -) -> syn::Result { - let tag = container_attrs - .tag - .clone() - .unwrap_or_else(|| "type".to_string()); - - let mut variant_classes = Vec::new(); - let mut register_calls = Vec::new(); - let mut match_arms = Vec::new(); - let mut to_value_match_arms = Vec::new(); - let mut union_variants = Vec::new(); - - for variant in &data.variants { - let variant_ident = &variant.ident; - let variant_name = variant_ident.to_string(); - let variant_attrs = parse_variant_attrs(&variant.attrs)?; - let rendered_variant = variant_alias(&variant_attrs, rename_all, &variant_name) - .unwrap_or_else(|| variant_name.clone()); - - let variant_rendered_name = format!("{}_{}", rendered_name, rendered_variant); - - let variant_desc = variant_description(&variant_attrs, &variant.attrs); - let variant_desc_expr = match variant_desc { - Some(desc) => quote! { Some(#desc.to_string()) }, - None => quote! { None }, - }; - - let mut fields = Vec::new(); - let mut to_value_inserts = Vec::new(); - let mut to_value_bindings = Vec::new(); - let tag_field_name = name_tokens(&tag, None); - let tag_field_type = quote! { ::baml_bridge::baml_types::TypeIR::literal_string(#rendered_variant.to_string()) }; - fields.push(quote! { (#tag_field_name, #tag_field_type, None, false) }); - - match &variant.fields { - Fields::Unit => {} - Fields::Named(variant_fields) => { - for field in &variant_fields.named { - let ident = field.ident.as_ref().unwrap(); - let field_name = ident.to_string(); - let field_attrs = parse_field_attrs(&field.attrs)?; - - if field_attrs.skip { - continue; - } - - let field_desc = field_description(&field_attrs, &field.attrs); - let field_alias = field_alias(&field_attrs, rename_all, &field_name); - let rendered_field = field_alias.clone().unwrap_or_else(|| field_name.clone()); - let map_entry_info = map_entry_info( - &field_name, - &rendered_field, - Some(&variant_name), - Some(&rendered_variant), - &field_attrs, - )?; - - let field_type_ir = - type_ir_for_field(&field.ty, &field_attrs, map_entry_info.as_ref())?; - let field_type_ir = - with_constraints_tokens(field_type_ir, &field_attrs.constraints); - - let name_expr = name_tokens(&field_name, field_alias.as_deref()); - let desc_expr = match field_desc { - Some(desc) => quote! { Some(#desc.to_string()) }, - None => quote! { None }, - }; - - fields.push(quote! { (#name_expr, #field_type_ir, #desc_expr, false) }); - register_calls.push(register_call_tokens( - &field.ty, - &field_attrs, - map_entry_info.as_ref(), - )?); - to_value_bindings.push(ident); - let to_value = - field_to_value_tokens(&field.ty, &field_attrs, quote! { &#ident })?; - to_value_inserts.push(quote! { - fields.insert(#field_name.to_string(), #to_value); - }); - } - } - Fields::Unnamed(_) => return Err(syn::Error::new_spanned( - variant, - "Tuple enum variants are not supported; hint: use a unit or struct-like variant", - )), - } - - let class_name_expr = name_tokens_expr( - quote! { - format!( - "{}__{}", - ::baml_internal_name(), - #variant_name - ) - }, - Some(&variant_rendered_name), - ); - let class_def = quote! { - ::baml_bridge::internal_baml_jinja::types::Class { - name: #class_name_expr, - description: #variant_desc_expr, - namespace: ::baml_bridge::baml_types::StreamingMode::NonStreaming, - fields: vec![#(#fields),*], - constraints: Vec::new(), - streaming_behavior: ::baml_bridge::default_streaming_behavior(), - } - }; - - variant_classes.push(class_def); - union_variants.push(quote! { - ::baml_bridge::baml_types::TypeIR::class(format!( - "{}__{}", - ::baml_internal_name(), - #variant_name - )) - }); - - let match_values = match_strings_for_variant(&variant_name, Some(&rendered_variant)); - let parse_variant = - variant_parse_tokens(variant, &variant_name, Some(&rendered_variant), rename_all)?; - match_arms.push(quote! { #(#match_values)|* => #parse_variant }); - - let tag_literal = rendered_variant.clone(); - let pattern = match &variant.fields { - Fields::Unit => quote! { Self::#variant_ident }, - Fields::Named(_) if to_value_bindings.is_empty() => { - quote! { Self::#variant_ident { .. } } - } - Fields::Named(_) => quote! { Self::#variant_ident { #(#to_value_bindings),*, .. } }, - Fields::Unnamed(_) => return Err(syn::Error::new_spanned( - variant, - "Tuple enum variants are not supported; hint: use a unit or struct-like variant", - )), - }; - - to_value_match_arms.push(quote! { - #pattern => { - let mut fields = ::baml_bridge::baml_types::BamlMap::new(); - fields.insert( - #tag.to_string(), - ::baml_bridge::baml_types::BamlValue::String(#tag_literal.to_string()), - ); - #(#to_value_inserts)* - ::baml_bridge::baml_types::BamlValue::Class( - ::baml_internal_name().to_string(), - fields, - ) - } - }); - } - - let type_ir = quote! {{ - let mut r#type = ::baml_bridge::baml_types::TypeIR::union_with_meta( - vec![#(#union_variants),*], - ::baml_bridge::baml_types::type_meta::IR::default(), - ); - r#type.meta_mut().constraints.extend(#type_constraints); - r#type - }}; - - let register_impl = quote! { - impl ::baml_bridge::BamlTypeInternal for #name { - fn baml_internal_name() -> &'static str { - #internal_name_expr - } - - fn baml_type_ir() -> ::baml_bridge::baml_types::TypeIR { - #type_ir - } - - fn register(reg: &mut ::baml_bridge::Registry) { - if !reg.mark_type(::baml_internal_name()) { - return; - } - #(reg.register_class(#variant_classes);)* - #(#register_calls;)* - } - } - }; - - let value_convert_impl = quote! { - impl ::baml_bridge::BamlValueConvert for #name { - fn try_from_baml_value( - value: ::baml_bridge::baml_types::BamlValue, - path: Vec, - ) -> Result { - let map = match value { - ::baml_bridge::baml_types::BamlValue::Class(_, map) - | ::baml_bridge::baml_types::BamlValue::Map(map) => map, - other => { - return Err(::baml_bridge::BamlConvertError::new( - path, - "object", - format!("{other:?}"), - "expected an object", - )) - } - }; - - let tag_value = match map.get(#tag) { - Some(::baml_bridge::baml_types::BamlValue::String(v)) => v.clone(), - Some(::baml_bridge::baml_types::BamlValue::Enum(_, v)) => v.clone(), - Some(other) => { - return Err(::baml_bridge::BamlConvertError::new( - path, - "string", - format!("{other:?}"), - "expected tag field to be a string", - )) - } - None => { - return Err(::baml_bridge::BamlConvertError::new( - path, - "string", - "", - "missing enum tag", - )) - } - }; - - match tag_value.as_str() { - #(#match_arms),*, - _ => Err(::baml_bridge::BamlConvertError::new( - path, - "enum", - tag_value, - "unknown enum variant", - )), - } - } - } - }; - - let to_value_impl = quote! { - impl ::baml_bridge::ToBamlValue for #name { - fn to_baml_value(&self) -> ::baml_bridge::baml_types::BamlValue { - match self { - #(#to_value_match_arms),* - } - } - } - }; - - let output_format_impl = quote! { - impl ::baml_bridge::BamlType for #name { - fn baml_output_format() -> &'static ::baml_bridge::internal_baml_jinja::types::OutputFormatContent { - static OUTPUT_FORMAT: ::std::sync::OnceLock<::baml_bridge::internal_baml_jinja::types::OutputFormatContent> = ::std::sync::OnceLock::new(); - OUTPUT_FORMAT.get_or_init(|| { - let mut reg = ::baml_bridge::Registry::new(); - ::register(&mut reg); - reg.build(::baml_type_ir()) - }) - } - } - }; - - Ok(quote! { - #register_impl - #value_convert_impl - #to_value_impl - #output_format_impl - }) -} - -fn variant_parse_tokens( - variant: &syn::Variant, - _variant_name: &str, - _rendered_variant: Option<&str>, - rename_all: Option, -) -> syn::Result { - let variant_ident = &variant.ident; - match &variant.fields { - Fields::Unit => Ok(quote! { Ok(Self::#variant_ident) }), - Fields::Named(fields) => { - let mut field_inits = Vec::new(); - for field in &fields.named { - let ident = field.ident.as_ref().unwrap(); - let field_name = ident.to_string(); - let field_attrs = parse_field_attrs(&field.attrs)?; - - if field_attrs.skip { - field_inits.push(quote! { #ident: ::std::default::Default::default() }); - continue; - } - - let field_alias = field_alias(&field_attrs, rename_all, &field_name); - let conversion = field_conversion_tokens( - &field.ty, - &field_attrs, - &field_name, - field_alias.as_deref(), - )?; - field_inits.push(quote! { #ident: #conversion }); - } - - Ok(quote! { Ok(Self::#variant_ident { #(#field_inits),* }) }) - } - Fields::Unnamed(_) => Err(syn::Error::new_spanned( - variant, - "Tuple enum variants are not supported; hint: use a unit or struct-like variant", - )), - } -} - -fn field_conversion_tokens( - ty: &Type, - attrs: &FieldAttrs, - field_name: &str, - alias: Option<&str>, -) -> syn::Result { - let missing_value = if attrs.default { - quote! { ::std::default::Default::default() } - } else if is_option_type(ty).is_some() { - quote! { None } - } else { - quote! { - return Err(::baml_bridge::BamlConvertError::new( - path, - "value", - "", - "missing required field", - )); - } - }; - - if attrs.skip { - return Ok(quote! { ::std::default::Default::default() }); - } - - let alias_token = match alias { - Some(alias) => quote! { Some(#alias) }, - None => quote! { None }, - }; - - let get_value = quote! { - ::baml_bridge::get_field(&map, #field_name, #alias_token) - }; - - let conversion = if let Some(adapter) = &attrs.with { - quote! { - <#adapter as ::baml_bridge::BamlAdapter<#ty>>::try_from_baml(value.clone(), field_path)? - } - } else if let Some(int_repr) = attrs.int_repr { - let conv = int_repr_conversion_tokens(ty, int_repr)?; - quote! {{ - let value = value.clone(); - #conv - }} - } else if let Some(map_repr) = attrs.map_key_repr { - let conv = map_key_repr_conversion_tokens(ty, map_repr)?; - quote! {{ - let value = value.clone(); - #conv - }} - } else { - quote! { - <#ty as ::baml_bridge::BamlValueConvert>::try_from_baml_value(value.clone(), field_path)? - } - }; - - Ok(quote! {{ - match #get_value { - Some(value) => { - let mut field_path = path.clone(); - field_path.push(#field_name.to_string()); - #conversion - } - None => { #missing_value } - } - }}) -} - -fn field_to_value_tokens( - ty: &Type, - attrs: &FieldAttrs, - value_expr: proc_macro2::TokenStream, -) -> syn::Result { - if let Some(int_repr) = attrs.int_repr { - let conv = int_repr_to_value_tokens(ty, int_repr)?; - return Ok(quote! {{ - let value = #value_expr; - #conv - }}); - } - - if let Some(map_repr) = attrs.map_key_repr { - let conv = map_key_repr_to_value_tokens(ty, map_repr)?; - return Ok(quote! {{ - let value = #value_expr; - #conv - }}); - } - - Ok(quote! { - ::baml_bridge::ToBamlValue::to_baml_value(#value_expr) - }) -} - -fn int_repr_to_value_tokens(ty: &Type, repr: IntRepr) -> syn::Result { - if let Some(inner) = is_option_type(ty) { - let inner_conv = int_repr_to_value_tokens(inner, repr)?; - return Ok(quote! { - match value.as_ref() { - Some(inner) => { - let value = inner; - #inner_conv - } - None => ::baml_bridge::baml_types::BamlValue::Null, - } - }); - } - if let Some(inner) = is_vec_type(ty) { - let inner_conv = int_repr_to_value_tokens(inner, repr)?; - return Ok(quote! { - ::baml_bridge::baml_types::BamlValue::List( - value - .iter() - .map(|item| { - let value = item; - #inner_conv - }) - .collect() - ) - }); - } - if let Some(inner) = is_box_type(ty) { - let inner_conv = int_repr_to_value_tokens(inner, repr)?; - return Ok(quote! {{ - let value = value.as_ref(); - #inner_conv - }}); - } - if let Some(inner) = is_arc_type(ty) { - let inner_conv = int_repr_to_value_tokens(inner, repr)?; - return Ok(quote! {{ - let value = value.as_ref(); - #inner_conv - }}); - } - if let Some(inner) = is_rc_type(ty) { - let inner_conv = int_repr_to_value_tokens(inner, repr)?; - return Ok(quote! {{ - let value = value.as_ref(); - #inner_conv - }}); - } - - if !is_large_int_type(ty) { - return Err(syn::Error::new_spanned( - ty, - "int_repr is only supported for u64/usize/i128/u128 (optionally wrapped in Option/Vec/Box/Arc/Rc); hint: remove int_repr or change the field type", - )); - } - - match repr { - IntRepr::String => Ok(quote! { - ::baml_bridge::baml_types::BamlValue::String(value.to_string()) - }), - IntRepr::I64 => Ok(quote! {{ - let value = *value as i128; - let min = i64::MIN as i128; - let max = i64::MAX as i128; - if value < min || value > max { - panic!("integer out of range for i64 representation"); - } - ::baml_bridge::baml_types::BamlValue::Int(value as i64) - }}), - } -} - -fn map_key_repr_to_value_tokens( - ty: &Type, - repr: MapKeyRepr, -) -> syn::Result { - if let Some(inner) = is_option_type(ty) { - let inner_conv = map_key_repr_to_value_tokens(inner, repr)?; - return Ok(quote! { - match value.as_ref() { - Some(inner) => { - let value = inner; - #inner_conv - } - None => ::baml_bridge::baml_types::BamlValue::Null, - } - }); - } - if let Some(inner) = is_vec_type(ty) { - let inner_conv = map_key_repr_to_value_tokens(inner, repr)?; - return Ok(quote! { - ::baml_bridge::baml_types::BamlValue::List( - value - .iter() - .map(|item| { - let value = item; - #inner_conv - }) - .collect() - ) - }); - } - if let Some(inner) = is_box_type(ty) { - let inner_conv = map_key_repr_to_value_tokens(inner, repr)?; - return Ok(quote! {{ - let value = value.as_ref(); - #inner_conv - }}); - } - if let Some(inner) = is_arc_type(ty) { - let inner_conv = map_key_repr_to_value_tokens(inner, repr)?; - return Ok(quote! {{ - let value = value.as_ref(); - #inner_conv - }}); - } - if let Some(inner) = is_rc_type(ty) { - let inner_conv = map_key_repr_to_value_tokens(inner, repr)?; - return Ok(quote! {{ - let value = value.as_ref(); - #inner_conv - }}); - } - - let (_key_ty, _value_ty) = map_types(ty).ok_or_else(|| { - syn::Error::new_spanned( - ty, - "map_key_repr only applies to map fields (HashMap/BTreeMap), optionally wrapped in Option/Vec/Box/Arc/Rc; hint: remove the attribute or change the field type", - ) - })?; - - match repr { - MapKeyRepr::String => Ok(quote! {{ - let mut map = ::baml_bridge::baml_types::BamlMap::new(); - for (key, value) in value.iter() { - map.insert( - key.to_string(), - ::baml_bridge::ToBamlValue::to_baml_value(value), - ); - } - ::baml_bridge::baml_types::BamlValue::Map(map) - }}), - MapKeyRepr::Pairs => Ok(quote! {{ - let mut entries = Vec::with_capacity(value.len()); - for (key, value) in value.iter() { - let mut entry = ::baml_bridge::baml_types::BamlMap::new(); - entry.insert( - "key".to_string(), - ::baml_bridge::ToBamlValue::to_baml_value(key), - ); - entry.insert( - "value".to_string(), - ::baml_bridge::ToBamlValue::to_baml_value(value), - ); - entries.push(::baml_bridge::baml_types::BamlValue::Map(entry)); - } - ::baml_bridge::baml_types::BamlValue::List(entries) - }}), - } -} - -fn int_repr_conversion_tokens(ty: &Type, repr: IntRepr) -> syn::Result { - if let Some(inner) = is_option_type(ty) { - let inner_conv = int_repr_conversion_tokens(inner, repr)?; - return Ok(quote! { - match value { - ::baml_bridge::baml_types::BamlValue::Null => None, - other => { - let value = other; - let field_path = field_path.clone(); - Some({ #inner_conv }) - } - } - }); - } - if let Some(inner) = is_vec_type(ty) { - let inner_conv = int_repr_conversion_tokens(inner, repr)?; - return Ok(quote! { - match value { - ::baml_bridge::baml_types::BamlValue::List(items) => { - let mut out = Vec::with_capacity(items.len()); - for (idx, item) in items.into_iter().enumerate() { - let mut item_path = field_path.clone(); - item_path.push(idx.to_string()); - let value = item; - let field_path = item_path; - out.push({ #inner_conv }); - } - out - } - other => { - return Err(::baml_bridge::BamlConvertError::new( - field_path, - "list", - format!("{other:?}"), - "expected a list", - )) - } - } - }); - } - if let Some(inner) = is_box_type(ty) { - let inner_conv = int_repr_conversion_tokens(inner, repr)?; - return Ok(quote! { ::std::boxed::Box::new({ #inner_conv }) }); - } - if let Some(inner) = is_arc_type(ty) { - let inner_conv = int_repr_conversion_tokens(inner, repr)?; - return Ok(quote! { ::std::sync::Arc::new({ #inner_conv }) }); - } - if let Some(inner) = is_rc_type(ty) { - let inner_conv = int_repr_conversion_tokens(inner, repr)?; - return Ok(quote! { ::std::rc::Rc::new({ #inner_conv }) }); - } - - if !is_large_int_type(ty) { - return Err(syn::Error::new_spanned( - ty, - "int_repr is only supported for u64/usize/i128/u128 (optionally wrapped in Option/Vec/Box/Arc/Rc); hint: remove int_repr or change the field type", - )); - } - - match repr { - IntRepr::String => Ok(quote! { - match value { - ::baml_bridge::baml_types::BamlValue::String(s) => { - s.parse::<#ty>().map_err(|_| ::baml_bridge::BamlConvertError::new( - field_path, - stringify!(#ty), - s, - "failed to parse string as integer", - ))? - } - other => { - return Err(::baml_bridge::BamlConvertError::new( - field_path, - "string", - format!("{other:?}"), - "expected string for integer representation", - )) - } - } - }), - IntRepr::I64 => Ok(quote! { - match value { - ::baml_bridge::baml_types::BamlValue::Int(v) => { - let min = <#ty>::MIN as i128; - let max = <#ty>::MAX as i128; - let v = v as i128; - if v < min || v > max { - return Err(::baml_bridge::BamlConvertError::new( - field_path, - stringify!(#ty), - v.to_string(), - "integer out of range", - )); - } - v as #ty - } - other => { - return Err(::baml_bridge::BamlConvertError::new( - field_path, - "int", - format!("{other:?}"), - "expected integer", - )) - } - } - }), - } -} - -fn map_key_repr_conversion_tokens( - ty: &Type, - repr: MapKeyRepr, -) -> syn::Result { - if let Some(inner) = is_option_type(ty) { - let inner_conv = map_key_repr_conversion_tokens(inner, repr)?; - return Ok(quote! { - match value { - ::baml_bridge::baml_types::BamlValue::Null => None, - other => { - let value = other; - let field_path = field_path.clone(); - Some({ #inner_conv }) - } - } - }); - } - if let Some(inner) = is_vec_type(ty) { - let inner_conv = map_key_repr_conversion_tokens(inner, repr)?; - return Ok(quote! { - match value { - ::baml_bridge::baml_types::BamlValue::List(items) => { - let mut out = Vec::with_capacity(items.len()); - for (idx, item) in items.into_iter().enumerate() { - let mut item_path = field_path.clone(); - item_path.push(idx.to_string()); - let value = item; - let field_path = item_path; - out.push({ #inner_conv }); - } - out - } - other => { - return Err(::baml_bridge::BamlConvertError::new( - field_path, - "list", - format!("{other:?}"), - "expected a list", - )) - } - } - }); - } - if let Some(inner) = is_box_type(ty) { - let inner_conv = map_key_repr_conversion_tokens(inner, repr)?; - return Ok(quote! { ::std::boxed::Box::new({ #inner_conv }) }); - } - if let Some(inner) = is_arc_type(ty) { - let inner_conv = map_key_repr_conversion_tokens(inner, repr)?; - return Ok(quote! { ::std::sync::Arc::new({ #inner_conv }) }); - } - if let Some(inner) = is_rc_type(ty) { - let inner_conv = map_key_repr_conversion_tokens(inner, repr)?; - return Ok(quote! { ::std::rc::Rc::new({ #inner_conv }) }); - } - - let (key_ty, value_ty) = map_types(ty).ok_or_else(|| { - syn::Error::new_spanned( - ty, - "map_key_repr only applies to map fields (HashMap/BTreeMap), optionally wrapped in Option/Vec/Box/Arc/Rc; hint: remove the attribute or change the field type", - ) - })?; - - match repr { - MapKeyRepr::String => Ok(quote! { - match value { - ::baml_bridge::baml_types::BamlValue::Map(map) => { - let mut out: #ty = ::std::default::Default::default(); - for (key, value) in map.into_iter() { - let parsed_key = key.parse::<#key_ty>().map_err(|_| { - ::baml_bridge::BamlConvertError::new( - field_path.clone(), - stringify!(#key_ty), - key.clone(), - "failed to parse map key", - ) - })?; - let mut item_path = field_path.clone(); - item_path.push(key); - let parsed_value = <#value_ty as ::baml_bridge::BamlValueConvert>::try_from_baml_value(value, item_path)?; - out.insert(parsed_key, parsed_value); - } - out - } - other => { - return Err(::baml_bridge::BamlConvertError::new( - field_path, - "map", - format!("{other:?}"), - "expected map", - )) - } - } - }), - MapKeyRepr::Pairs => Ok(quote! { - match value { - ::baml_bridge::baml_types::BamlValue::List(items) => { - let mut out: #ty = ::std::default::Default::default(); - for (idx, item) in items.into_iter().enumerate() { - let entry_map = match item { - ::baml_bridge::baml_types::BamlValue::Class(_, map) - | ::baml_bridge::baml_types::BamlValue::Map(map) => map, - other => { - return Err(::baml_bridge::BamlConvertError::new( - field_path.clone(), - "object", - format!("{other:?}"), - "expected map entry object", - )) - } - }; - - let key_value = ::baml_bridge::get_field(&entry_map, "key", None) - .cloned() - .ok_or_else(|| { - ::baml_bridge::BamlConvertError::new( - field_path.clone(), - "key", - "", - "missing map entry key", - ) - })?; - let value_value = ::baml_bridge::get_field(&entry_map, "value", None) - .cloned() - .ok_or_else(|| { - ::baml_bridge::BamlConvertError::new( - field_path.clone(), - "value", - "", - "missing map entry value", - ) - })?; - - let mut key_path = field_path.clone(); - key_path.push(idx.to_string()); - key_path.push("key".to_string()); - let parsed_key = - <#key_ty as ::baml_bridge::BamlValueConvert>::try_from_baml_value( - key_value, - key_path, - )?; - - let mut value_path = field_path.clone(); - value_path.push(idx.to_string()); - value_path.push("value".to_string()); - let parsed_value = - <#value_ty as ::baml_bridge::BamlValueConvert>::try_from_baml_value( - value_value, - value_path, - )?; - - out.insert(parsed_key, parsed_value); - } - out - } - other => { - return Err(::baml_bridge::BamlConvertError::new( - field_path, - "list", - format!("{other:?}"), - "expected list of map entries", - )) - } - } - }), - } -} - -fn register_call_tokens( - ty: &Type, - attrs: &FieldAttrs, - map_entry: Option<&MapEntryInfo>, -) -> syn::Result { - if let Some(adapter) = &attrs.with { - return Ok(quote! { <#adapter as ::baml_bridge::BamlAdapter<#ty>>::register(reg) }); - } - - if attrs.int_repr.is_some() { - return Ok(quote! {}); - } - - if let Some(map_repr) = attrs.map_key_repr { - if let Some((key_ty, value_ty)) = map_types_for_repr(ty) { - if matches!(map_repr, MapKeyRepr::Pairs) { - let entry_info = map_entry.ok_or_else(|| { - syn::Error::new_spanned( - ty, - "internal error: missing map entry metadata for map_key_repr=\"pairs\"; hint: please report a bug", - ) - })?; - let entry_class = map_entry_class_tokens(entry_info, key_ty, value_ty)?; - return Ok(quote! {{ - #entry_class - <#key_ty as ::baml_bridge::BamlTypeInternal>::register(reg); - <#value_ty as ::baml_bridge::BamlTypeInternal>::register(reg); - }}); - } - - return Ok(quote! { <#value_ty as ::baml_bridge::BamlTypeInternal>::register(reg) }); - } - } - - Ok(quote! { <#ty as ::baml_bridge::BamlTypeInternal>::register(reg) }) -} - -fn type_ir_for_field( - ty: &Type, - attrs: &FieldAttrs, - map_entry: Option<&MapEntryInfo>, -) -> syn::Result { - if let Some(adapter) = attrs.with.as_ref() { - return Ok(quote! { <#adapter as ::baml_bridge::BamlAdapter<#ty>>::type_ir() }); - } - - if let Some(int_repr) = attrs.int_repr { - return int_repr_type_ir(ty, int_repr); - } - - if let Some(map_repr) = attrs.map_key_repr { - return map_key_repr_type_ir(ty, map_repr, map_entry); - } - - match_type_ir(ty) -} - -fn int_repr_type_ir(ty: &Type, repr: IntRepr) -> syn::Result { - if let Some(inner) = is_option_type(ty) { - let inner_ir = int_repr_type_ir(inner, repr)?; - return Ok(quote! { ::baml_bridge::baml_types::TypeIR::optional(#inner_ir) }); - } - if let Some(inner) = is_vec_type(ty) { - let inner_ir = int_repr_type_ir(inner, repr)?; - return Ok(quote! { ::baml_bridge::baml_types::TypeIR::list(#inner_ir) }); - } - if let Some(inner) = is_box_type(ty) { - return int_repr_type_ir(inner, repr); - } - if let Some(inner) = is_arc_type(ty) { - return int_repr_type_ir(inner, repr); - } - if let Some(inner) = is_rc_type(ty) { - return int_repr_type_ir(inner, repr); - } - if !is_large_int_type(ty) { - return Err(syn::Error::new_spanned( - ty, - "int_repr is only supported for u64/usize/i128/u128 (optionally wrapped in Option/Vec/Box/Arc/Rc); hint: remove int_repr or change the field type", - )); - } - - match repr { - IntRepr::String => Ok(quote! { ::baml_bridge::baml_types::TypeIR::string() }), - IntRepr::I64 => Ok(quote! { ::baml_bridge::baml_types::TypeIR::int() }), - } -} - -fn map_key_repr_type_ir( - ty: &Type, - repr: MapKeyRepr, - map_entry: Option<&MapEntryInfo>, -) -> syn::Result { - if let Some(inner) = is_option_type(ty) { - let inner_ir = map_key_repr_type_ir(inner, repr, map_entry)?; - return Ok(quote! { ::baml_bridge::baml_types::TypeIR::optional(#inner_ir) }); - } - if let Some(inner) = is_vec_type(ty) { - let inner_ir = map_key_repr_type_ir(inner, repr, map_entry)?; - return Ok(quote! { ::baml_bridge::baml_types::TypeIR::list(#inner_ir) }); - } - if let Some(inner) = is_box_type(ty) { - return map_key_repr_type_ir(inner, repr, map_entry); - } - if let Some(inner) = is_arc_type(ty) { - return map_key_repr_type_ir(inner, repr, map_entry); - } - if let Some(inner) = is_rc_type(ty) { - return map_key_repr_type_ir(inner, repr, map_entry); - } - - let (_key_ty, value_ty) = map_types(ty).ok_or_else(|| { - syn::Error::new_spanned( - ty, - "map_key_repr only applies to map fields (HashMap/BTreeMap), optionally wrapped in Option/Vec/Box/Arc/Rc; hint: remove the attribute or change the field type", - ) - })?; - - match repr { - MapKeyRepr::String => Ok(quote! { - ::baml_bridge::baml_types::TypeIR::map( - ::baml_bridge::baml_types::TypeIR::string(), - <#value_ty as ::baml_bridge::BamlTypeInternal>::baml_type_ir(), - ) - }), - MapKeyRepr::Pairs => { - let entry = map_entry.ok_or_else(|| { - syn::Error::new_spanned( - ty, - "internal error: missing map entry metadata for map_key_repr=\"pairs\"; hint: please report a bug", - ) - })?; - let entry_name_expr = entry.internal_name_expr.clone(); - Ok(quote! { - ::baml_bridge::baml_types::TypeIR::list( - ::baml_bridge::baml_types::TypeIR::class(#entry_name_expr) - ) - }) - } - } -} - -fn match_type_ir(ty: &Type) -> syn::Result { - match ty { - Type::Tuple(_) => { - return Err(syn::Error::new_spanned( - ty, - "tuple types are not supported in BAML outputs; hint: use a struct with named fields or a list", - )) - } - Type::BareFn(_) => { - return Err(syn::Error::new_spanned( - ty, - "function types are not supported in BAML outputs; hint: remove the field or use #[baml(with = \"...\")] to adapt it", - )) - } - Type::TraitObject(_) => { - return Err(syn::Error::new_spanned( - ty, - "trait objects are not supported in BAML outputs; hint: use a concrete type or a custom adapter", - )) - } - _ => {} - } - - if let Some(inner) = is_option_type(ty) { - let inner_ir = match_type_ir(inner)?; - return Ok(quote! { ::baml_bridge::baml_types::TypeIR::optional(#inner_ir) }); - } - if let Some(inner) = is_vec_type(ty) { - let inner_ir = match_type_ir(inner)?; - return Ok(quote! { ::baml_bridge::baml_types::TypeIR::list(#inner_ir) }); - } - if let Some(inner) = is_box_type(ty) { - return match_type_ir(inner); - } - if let Some(inner) = is_arc_type(ty) { - return match_type_ir(inner); - } - if let Some(inner) = is_rc_type(ty) { - return match_type_ir(inner); - } - if let Some((_key, value)) = is_string_map_type(ty) { - let value_ir = match_type_ir(value)?; - return Ok( - quote! { ::baml_bridge::baml_types::TypeIR::map(::baml_bridge::baml_types::TypeIR::string(), #value_ir) }, - ); - } - if let Some((key, _)) = map_types(ty) { - if !is_string_type(key) { - return Err(syn::Error::new_spanned( - ty, - "map keys must be String for object maps; hint: use HashMap or add #[baml(map_key_repr = \"string\"|\"pairs\")], or use a custom adapter", - )); - } - } - - if is_serde_json_value(ty) { - return Err(syn::Error::new_spanned( - ty, - "serde_json::Value is not supported without a #[baml(with = \"...\")] adapter; hint: use a concrete type or provide a custom adapter", - )); - } - - let ident = type_ident(ty).ok_or_else(|| { - syn::Error::new_spanned( - ty, - "unsupported type for BamlType; hint: derive BamlType for it or use #[baml(with = \"...\")]", - ) - })?; - - let ident_str = ident.to_string(); - let ir = match ident_str.as_str() { - "String" => quote! { ::baml_bridge::baml_types::TypeIR::string() }, - "bool" => quote! { ::baml_bridge::baml_types::TypeIR::bool() }, - "f32" | "f64" => quote! { ::baml_bridge::baml_types::TypeIR::float() }, - "i8" | "i16" | "i32" | "i64" | "isize" => quote! { ::baml_bridge::baml_types::TypeIR::int() }, - "u8" | "u16" | "u32" => quote! { ::baml_bridge::baml_types::TypeIR::int() }, - "u64" | "usize" | "i128" | "u128" => { - return Err(syn::Error::new_spanned( - ty, - "unsupported integer width for BAML outputs; hint: use #[baml(int_repr = \"string\"|\"i64\")] or a smaller integer type", - )) - } - _ => quote! { <#ty as ::baml_bridge::BamlTypeInternal>::baml_type_ir() }, - }; - - Ok(ir) -} - -fn with_constraints_tokens( - r#type: proc_macro2::TokenStream, - constraints: &[ConstraintSpec], -) -> proc_macro2::TokenStream { - if constraints.is_empty() { - return r#type; - } - let constraint_tokens = constraints_tokens(constraints); - quote! { ::baml_bridge::with_constraints(#r#type, #constraint_tokens) } -} - -fn constraints_tokens(constraints: &[ConstraintSpec]) -> proc_macro2::TokenStream { - let tokens = constraints.iter().map(|spec| { - let label = &spec.label; - let expr = &spec.expr; - match spec.level { - ConstraintLevelSpec::Check => { - quote! { ::baml_bridge::baml_types::Constraint::new_check(#label, #expr) } - } - ConstraintLevelSpec::Assert => { - quote! { ::baml_bridge::baml_types::Constraint::new_assert(#label, #expr) } - } - } - }); - quote! { vec![#(#tokens),*] } -} - -fn name_tokens(name: &str, alias: Option<&str>) -> proc_macro2::TokenStream { - name_tokens_expr(quote! { #name.to_string() }, alias) -} - -fn name_tokens_expr( - real_expr: proc_macro2::TokenStream, - alias: Option<&str>, -) -> proc_macro2::TokenStream { - match alias { - Some(alias) => quote! { - ::baml_bridge::internal_baml_jinja::types::Name::new_with_alias(#real_expr, Some(#alias.to_string())) - }, - None => quote! { - ::baml_bridge::internal_baml_jinja::types::Name::new(#real_expr) - }, - } -} - -fn field_alias( - attrs: &FieldAttrs, - rename_all: Option, - field_name: &str, -) -> Option { - if let Some(alias) = &attrs.alias { - return Some(alias.clone()); - } - rename_all.map(|rule| apply_rename(rule, field_name)) -} - -fn map_entry_info( - field_name: &str, - rendered_field: &str, - variant_name: Option<&str>, - variant_rendered: Option<&str>, - attrs: &FieldAttrs, -) -> syn::Result> { - if !matches!(attrs.map_key_repr, Some(MapKeyRepr::Pairs)) { - return Ok(None); - } - - let suffix = match variant_name { - Some(variant) => format!("{variant}__{field_name}__Entry"), - None => format!("{field_name}__Entry"), - }; - - let internal_name_expr = quote! { - format!( - "{}::{}", - ::baml_internal_name(), - #suffix - ) - }; - - let rendered_entry = match variant_rendered { - Some(variant) => format!("{variant}{rendered_field}Entry"), - None => format!("{rendered_field}Entry"), - }; - - Ok(Some(MapEntryInfo { - internal_name_expr, - rendered_name: Some(rendered_entry), - })) -} - -fn map_entry_class_tokens( - entry: &MapEntryInfo, - key_ty: &Type, - value_ty: &Type, -) -> syn::Result { - let key_ir = match_type_ir(key_ty)?; - let value_ir = match_type_ir(value_ty)?; - let name_expr = name_tokens_expr( - entry.internal_name_expr.clone(), - entry.rendered_name.as_deref(), - ); - let key_name = name_tokens("key", None); - let value_name = name_tokens("value", None); - - Ok(quote! { - reg.register_class(::baml_bridge::internal_baml_jinja::types::Class { - name: #name_expr, - description: None, - namespace: ::baml_bridge::baml_types::StreamingMode::NonStreaming, - fields: vec![ - (#key_name, #key_ir, None, false), - (#value_name, #value_ir, None, false), - ], - constraints: Vec::new(), - streaming_behavior: ::baml_bridge::default_streaming_behavior(), - }); - }) -} - -fn variant_alias( - attrs: &VariantAttrs, - rename_all: Option, - name: &str, -) -> Option { - if let Some(alias) = &attrs.alias { - return Some(alias.clone()); - } - rename_all.map(|rule| apply_rename(rule, name)) -} - -fn match_strings_for_variant(name: &str, alias: Option<&str>) -> Vec { - let mut values = vec![quote! { #name }]; - if let Some(alias) = alias { - if alias != name { - values.push(quote! { #alias }); - } - } - values -} - -fn field_description(attrs: &FieldAttrs, raw_attrs: &[Attribute]) -> Option { - if let Some(desc) = &attrs.description { - return Some(desc.clone()); - } - extract_doc(raw_attrs) -} - -fn variant_description(attrs: &VariantAttrs, raw_attrs: &[Attribute]) -> Option { - if let Some(desc) = &attrs.description { - return Some(desc.clone()); - } - extract_doc(raw_attrs) -} - -fn container_description(attrs: &ContainerAttrs, raw_attrs: &[Attribute]) -> Option { - if let Some(desc) = &attrs.description { - return Some(desc.clone()); - } - extract_doc(raw_attrs) -} - -fn parse_container_attrs(attrs: &[Attribute]) -> syn::Result { - let mut out = ContainerAttrs::default(); - for attr in attrs { - if attr.path().is_ident("baml") { - parse_baml_meta(attr, |meta| { - match meta { - Meta::NameValue(meta) if meta.path.is_ident("name") => { - out.name = Some(parse_string_expr(&meta.value, meta.span())?); - Ok(()) - } - Meta::NameValue(meta) if meta.path.is_ident("internal_name") => { - out.internal_name = Some(parse_string_expr(&meta.value, meta.span())?); - Ok(()) - } - Meta::NameValue(meta) if meta.path.is_ident("rename_all") => { - out.rename_all = Some(parse_rename_rule(&meta.value, meta.span())?); - Ok(()) - } - Meta::NameValue(meta) if meta.path.is_ident("tag") => { - out.tag = Some(parse_string_expr(&meta.value, meta.span())?); - Ok(()) - } - Meta::NameValue(meta) if meta.path.is_ident("description") => { - out.description = parse_optional_string(&meta.value, meta.span())?; - Ok(()) - } - Meta::List(meta) if meta.path.is_ident("check") => { - out.constraints.push(parse_constraint(meta, ConstraintLevelSpec::Check)?); - Ok(()) - } - Meta::List(meta) if meta.path.is_ident("assert") => { - out.constraints.push(parse_constraint(meta, ConstraintLevelSpec::Assert)?); - Ok(()) - } - Meta::Path(path) if path.is_ident("as_union") => { - out.as_union = true; - Ok(()) - } - Meta::Path(path) if path.is_ident("as_enum") => { - out.as_enum = true; - Ok(()) - } - _ => Err(syn::Error::new_spanned( - meta, - "unsupported #[baml(...)] attribute; hint: check the supported keys in the bridge docs", - )), - } - })?; - } - - if attr.path().is_ident("serde") { - parse_serde_meta(attr, |meta| { - match meta { - Meta::NameValue(meta) if meta.path.is_ident("rename") => { - if out.name.is_none() { - out.name = Some(parse_string_expr(&meta.value, meta.span())?); - } - Ok(()) - } - Meta::NameValue(meta) if meta.path.is_ident("rename_all") => { - if out.rename_all.is_none() { - out.rename_all = Some(parse_rename_rule(&meta.value, meta.span())?); - } - Ok(()) - } - Meta::NameValue(meta) if meta.path.is_ident("tag") => { - if out.tag.is_none() { - out.tag = Some(parse_string_expr(&meta.value, meta.span())?); - } - Ok(()) - } - Meta::Path(path) if path.is_ident("untagged") => Err(syn::Error::new_spanned( - path, - "serde(untagged) is not supported; hint: use #[baml(tag = \"...\")] for data enums", - )), - Meta::Path(path) if path.is_ident("flatten") => Err(syn::Error::new_spanned( - path, - "serde(flatten) is not supported; hint: model fields explicitly", - )), - _ => Ok(()), - } - })?; - } - } - Ok(out) -} - -fn parse_field_attrs(attrs: &[Attribute]) -> syn::Result { - let mut out = FieldAttrs::default(); - for attr in attrs { - if attr.path().is_ident("baml") { - parse_baml_meta(attr, |meta| { - match meta { - Meta::NameValue(meta) if meta.path.is_ident("alias") => { - out.alias = Some(parse_string_expr(&meta.value, meta.span())?); - Ok(()) - } - Meta::NameValue(meta) if meta.path.is_ident("description") => { - out.description = parse_optional_string(&meta.value, meta.span())?; - Ok(()) - } - Meta::NameValue(meta) if meta.path.is_ident("with") => { - let path_str = parse_string_expr(&meta.value, meta.span())?; - out.with = Some(syn::parse_str::(&path_str)?); - Ok(()) - } - Meta::NameValue(meta) if meta.path.is_ident("int_repr") => { - out.int_repr = Some(parse_int_repr(&meta.value, meta.span())?); - Ok(()) - } - Meta::NameValue(meta) if meta.path.is_ident("map_key_repr") => { - out.map_key_repr = Some(parse_map_key_repr(&meta.value, meta.span())?); - Ok(()) - } - Meta::List(meta) if meta.path.is_ident("check") => { - out.constraints.push(parse_constraint(meta, ConstraintLevelSpec::Check)?); - Ok(()) - } - Meta::List(meta) if meta.path.is_ident("assert") => { - out.constraints.push(parse_constraint(meta, ConstraintLevelSpec::Assert)?); - Ok(()) - } - Meta::Path(path) if path.is_ident("skip") => { - out.skip = true; - Ok(()) - } - Meta::Path(path) if path.is_ident("default") => { - out.default = true; - Ok(()) - } - _ => Err(syn::Error::new_spanned( - meta, - "unsupported #[baml(...)] attribute; hint: check the supported keys in the bridge docs", - )), - } - })?; - } - - if attr.path().is_ident("serde") { - parse_serde_meta(attr, |meta| { - match meta { - Meta::NameValue(meta) if meta.path.is_ident("rename") => { - if out.alias.is_none() { - out.alias = Some(parse_string_expr(&meta.value, meta.span())?); - } - Ok(()) - } - Meta::Path(path) if path.is_ident("skip") => { - out.skip = true; - Ok(()) - } - Meta::Path(path) if path.is_ident("default") => { - out.default = true; - Ok(()) - } - Meta::NameValue(meta) if meta.path.is_ident("default") => Err(syn::Error::new_spanned( - meta, - "serde(default = \"path\") is not supported; hint: use #[baml(default)] or Default::default", - )), - Meta::Path(path) if path.is_ident("flatten") => Err(syn::Error::new_spanned( - path, - "serde(flatten) is not supported; hint: model fields explicitly", - )), - _ => Ok(()), - } - })?; - } - } - Ok(out) -} - -fn parse_variant_attrs(attrs: &[Attribute]) -> syn::Result { - let mut out = VariantAttrs::default(); - for attr in attrs { - if attr.path().is_ident("baml") { - parse_baml_meta(attr, |meta| { - match meta { - Meta::NameValue(meta) if meta.path.is_ident("alias") => { - out.alias = Some(parse_string_expr(&meta.value, meta.span())?); - Ok(()) - } - Meta::NameValue(meta) if meta.path.is_ident("description") => { - out.description = parse_optional_string(&meta.value, meta.span())?; - Ok(()) - } - _ => Err(syn::Error::new_spanned( - meta, - "unsupported #[baml(...)] attribute; hint: check the supported keys in the bridge docs", - )), - } - })?; - } - - if attr.path().is_ident("serde") { - parse_serde_meta(attr, |meta| { - match meta { - Meta::NameValue(meta) if meta.path.is_ident("rename") => { - if out.alias.is_none() { - out.alias = Some(parse_string_expr(&meta.value, meta.span())?); - } - Ok(()) - } - Meta::Path(path) if path.is_ident("skip") => Err(syn::Error::new_spanned( - path, - "serde(skip) is not supported on enum variants; hint: remove the variant or use a separate enum", - )), - _ => Ok(()), - } - })?; - } - } - Ok(out) -} - -fn parse_baml_meta( - attr: &Attribute, - mut handle: impl FnMut(Meta) -> syn::Result<()>, -) -> syn::Result<()> { - let metas = parse_meta_list(attr)?; - for meta in metas { - handle(meta)?; - } - Ok(()) -} - -fn parse_serde_meta( - attr: &Attribute, - mut handle: impl FnMut(Meta) -> syn::Result<()>, -) -> syn::Result<()> { - let metas = parse_meta_list(attr)?; - for meta in metas { - handle(meta)?; - } - Ok(()) -} - -fn parse_meta_list(attr: &Attribute) -> syn::Result> { - match attr - .parse_args_with(syn::punctuated::Punctuated::::parse_terminated) - { - Ok(list) => Ok(list.into_iter().collect()), - Err(err) => Err(err), - } -} - -fn parse_constraint( - meta: syn::MetaList, - level: ConstraintLevelSpec, -) -> syn::Result { - let nested = meta - .parse_args_with(syn::punctuated::Punctuated::::parse_terminated)?; - let mut label = None; - let mut expr = None; - - for meta in nested { - match meta { - Meta::NameValue(meta) if meta.path.is_ident("label") => { - label = Some(parse_string_expr(&meta.value, meta.span())?); - } - Meta::NameValue(meta) if meta.path.is_ident("expr") => { - expr = Some(parse_string_expr(&meta.value, meta.span())?); - } - _ => { - return Err(syn::Error::new_spanned( - meta, - "unsupported constraint attribute; hint: use #[baml(check(...))] or #[baml(assert(...))]", - )); - } - } - } - - let label = label.ok_or_else(|| { - syn::Error::new( - meta.span(), - "constraint missing label; hint: use label = \"...\"", - ) - })?; - let expr = expr.ok_or_else(|| { - syn::Error::new( - meta.span(), - "constraint missing expr; hint: use expr = \"...\"", - ) - })?; - - Ok(ConstraintSpec { level, label, expr }) -} - -fn parse_string_expr(expr: &Expr, span: proc_macro2::Span) -> syn::Result { - match expr { - Expr::Lit(ExprLit { - lit: Lit::Str(s), .. - }) => Ok(s.value()), - _ => Err(syn::Error::new( - span, - "expected string literal; hint: wrap the value in quotes", - )), - } -} - -fn parse_optional_string(expr: &Expr, span: proc_macro2::Span) -> syn::Result> { - if let Expr::Path(path) = expr { - if path.path.is_ident("None") { - return Ok(None); - } - } - Ok(Some(parse_string_expr(expr, span)?)) -} - -fn parse_rename_rule(expr: &Expr, span: proc_macro2::Span) -> syn::Result { - let value = parse_string_expr(expr, span)?; - let rule = match value.as_str() { - "camelCase" => RenameRule::Camel, - "snake_case" => RenameRule::Snake, - "PascalCase" => RenameRule::Pascal, - "kebab-case" => RenameRule::Kebab, - "SCREAMING_SNAKE_CASE" => RenameRule::ScreamingSnake, - "lowercase" => RenameRule::Lower, - "UPPERCASE" => RenameRule::Upper, - "SCREAMING-KEBAB-CASE" => RenameRule::ScreamingKebab, - _ => { - return Err(syn::Error::new(span, "unsupported rename_all value")); - } - }; - Ok(rule) -} - -fn parse_int_repr(expr: &Expr, span: proc_macro2::Span) -> syn::Result { - let value = parse_string_expr(expr, span)?; - match value.as_str() { - "string" => Ok(IntRepr::String), - "i64" => Ok(IntRepr::I64), - _ => Err(syn::Error::new( - span, - "int_repr must be \"string\" or \"i64\"", - )), - } -} - -fn parse_map_key_repr(expr: &Expr, span: proc_macro2::Span) -> syn::Result { - let value = parse_string_expr(expr, span)?; - match value.as_str() { - "string" => Ok(MapKeyRepr::String), - "pairs" => Ok(MapKeyRepr::Pairs), - _ => Err(syn::Error::new( - span, - "map_key_repr must be \"string\" or \"pairs\"", - )), - } -} - -fn type_ident(ty: &Type) -> Option<&syn::Ident> { - match ty { - Type::Path(path) if path.qself.is_none() => path.path.segments.last().map(|s| &s.ident), - _ => None, - } -} - -fn is_option_type(ty: &Type) -> Option<&Type> { - if let Type::Path(path) = ty { - if let Some(segment) = path.path.segments.last() { - if segment.ident == "Option" { - if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { - if let Some(syn::GenericArgument::Type(inner)) = args.args.first() { - return Some(inner); - } - } - } - } - } - None -} - -fn is_vec_type(ty: &Type) -> Option<&Type> { - if let Type::Path(path) = ty { - if let Some(segment) = path.path.segments.last() { - if segment.ident == "Vec" { - if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { - if let Some(syn::GenericArgument::Type(inner)) = args.args.first() { - return Some(inner); - } - } - } - } - } - None -} - -fn is_box_type(ty: &Type) -> Option<&Type> { - extract_single_arg(ty, "Box") -} - -fn is_arc_type(ty: &Type) -> Option<&Type> { - extract_single_arg(ty, "Arc") -} - -fn is_rc_type(ty: &Type) -> Option<&Type> { - extract_single_arg(ty, "Rc") -} - -fn extract_single_arg<'a>(ty: &'a Type, ident: &str) -> Option<&'a Type> { - if let Type::Path(path) = ty { - if let Some(segment) = path.path.segments.last() { - if segment.ident == ident { - if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { - if let Some(syn::GenericArgument::Type(inner)) = args.args.first() { - return Some(inner); - } - } - } - } - } - None -} - -fn is_string_map_type(ty: &Type) -> Option<(&Type, &Type)> { - if let Some((key, value)) = map_types(ty) { - if is_string_type(key) { - return Some((key, value)); - } - } - None -} - -fn map_types(ty: &Type) -> Option<(&Type, &Type)> { - if let Type::Path(path) = ty { - if let Some(segment) = path.path.segments.last() { - if segment.ident == "HashMap" || segment.ident == "BTreeMap" { - if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { - let mut iter = args.args.iter(); - let key = match iter.next() { - Some(syn::GenericArgument::Type(t)) => t, - _ => return None, - }; - let value = match iter.next() { - Some(syn::GenericArgument::Type(t)) => t, - _ => return None, - }; - return Some((key, value)); - } - } - } - } - None -} - -fn map_types_for_repr(ty: &Type) -> Option<(&Type, &Type)> { - if let Some((key, value)) = map_types(ty) { - return Some((key, value)); - } - if let Some(inner) = is_option_type(ty) { - return map_types_for_repr(inner); - } - if let Some(inner) = is_vec_type(ty) { - return map_types_for_repr(inner); - } - if let Some(inner) = is_box_type(ty) { - return map_types_for_repr(inner); - } - if let Some(inner) = is_arc_type(ty) { - return map_types_for_repr(inner); - } - if let Some(inner) = is_rc_type(ty) { - return map_types_for_repr(inner); - } - None -} - -fn is_string_type(ty: &Type) -> bool { - type_ident(ty) - .map(|ident| ident == "String") - .unwrap_or(false) -} - -fn is_large_int_type(ty: &Type) -> bool { - match type_ident(ty).map(|ident| ident.to_string()) { - Some(name) => matches!(name.as_str(), "u64" | "usize" | "i128" | "u128"), - None => false, - } -} - -fn is_serde_json_value(ty: &Type) -> bool { - if let Type::Path(path) = ty { - if let Some(segment) = path.path.segments.last() { - if segment.ident == "Value" { - return path - .path - .segments - .iter() - .any(|seg| seg.ident == "serde_json"); - } - } - } - false -} - -fn apply_rename(rule: RenameRule, name: &str) -> String { - use convert_case::{Case, Casing}; - let case = match rule { - RenameRule::Camel => Case::Camel, - RenameRule::Snake => Case::Snake, - RenameRule::Pascal => Case::Pascal, - RenameRule::Kebab => Case::Kebab, - RenameRule::ScreamingSnake => Case::UpperSnake, - RenameRule::Lower => Case::Lower, - RenameRule::Upper => Case::Upper, - RenameRule::ScreamingKebab => Case::UpperKebab, - }; - name.to_case(case) -} - -fn extract_doc(attrs: &[Attribute]) -> Option { - let mut lines = Vec::new(); - for attr in attrs { - if !attr.path().is_ident("doc") { - continue; - } - if let Meta::NameValue(meta) = &attr.meta { - if let Expr::Lit(ExprLit { - lit: Lit::Str(s), .. - }) = &meta.value - { - let mut value = s.value(); - if value.starts_with(' ') { - value.remove(0); - } - lines.push(value); - } - } - } - - if lines.is_empty() { - return None; - } - - let joined = lines.join("\n"); - let trimmed = joined.trim_end().to_string(); - if trimmed.trim().is_empty() { - None - } else { - Some(trimmed) - } -} diff --git a/crates/baml-bridge/Cargo.toml b/crates/baml-bridge/Cargo.toml deleted file mode 100644 index 88a3f0fa..00000000 --- a/crates/baml-bridge/Cargo.toml +++ /dev/null @@ -1,44 +0,0 @@ -[package] -name = "baml-bridge" -version = "0.1.0" -edition = "2021" -license = "Apache-2.0" -publish = false -description = "BAML bridge integration utilities" - -[dependencies] -anyhow = "1.0" -baml-types = { path = "../../vendor/baml/crates/baml-types" } -internal-baml-jinja = { path = "../../vendor/baml/crates/internal-baml-jinja" } -jsonish = { path = "../../vendor/baml/crates/jsonish" } -minijinja = { git = "https://github.com/boundaryml/minijinja.git", branch = "main", default-features = false, features = [ - "macros", - "builtins", - "debug", - "preserve_order", - "adjacent_loop_items", - "unicode", - "json", - "unstable_machinery", - "unstable_machinery_serde", - "custom_syntax", - "internal_debug", - "deserialization", - "serde", -] } -thiserror = "2.0.17" -sha2 = "0.10.9" -indexmap = { version = "2.1.0", features = ["serde"] } - -baml-bridge-derive = { path = "../baml-bridge-derive", optional = true } - -[features] -default = ["derive"] -derive = ["dep:baml-bridge-derive"] - -[dev-dependencies] -expect-test = "1.5.0" -proptest = "1.5.0" -serde = { version = "1", features = ["alloc", "derive", "rc"] } -serde_json = { version = "1", features = ["float_roundtrip", "preserve_order"] } -trybuild = "1.0.96" diff --git a/crates/baml-bridge/README.md b/crates/baml-bridge/README.md deleted file mode 100644 index a12c4c75..00000000 --- a/crates/baml-bridge/README.md +++ /dev/null @@ -1,79 +0,0 @@ -# baml-bridge - -A small set of crates that vendors the minimal BAML components needed for: -- schema rendering via `OutputFormatContent::render()` -- fuzzy LLM output parsing via `jsonish::from_str()` -- a thin Rust integration layer (`BamlType`, render/parse helpers) - -## Layout - -- `crates/baml-bridge` - public API surface (BamlType trait + helpers) -- `crates/baml-bridge-derive` - proc-macro for `#[derive(BamlType)]` -- `crates/baml-types` - TypeIR, constraints, BamlValue (yoinked) -- `crates/internal-baml-jinja` - output_format renderer only (trimmed) -- `crates/jsonish` - jsonish parser and coercer (trimmed) -- `crates/internal-baml-diagnostics` - dependency for `baml-types` -- `crates/baml-ids` - dependency for `baml-types` -- `crates/bstd` - dependency for `jsonish` - -## Quick start - -``` -cargo check -p baml-bridge -cargo run -p baml-bridge --example manual -``` - -## Example - -```rust -use baml_bridge::{parse_llm_output, render_schema, BamlType, HoistClasses, RenderOptions}; - -/// A user record returned by the model. -#[derive(Debug, Clone, PartialEq, BamlType)] -struct User { - /// Full name for display. - #[baml(alias = "fullName")] - name: String, - age: i64, -} - -fn main() { - let schema = render_schema::(RenderOptions::default()) - .expect("render failed") - .unwrap_or_default(); - println!("{schema}"); - - let raw = r#"{ "fullName": "Ada Lovelace", "age": 36 }"#; - let parsed = parse_llm_output::(raw, true).expect("parse failed"); - println!("{:?}", parsed.value); -} -``` - -## Attribute quick notes - -- `#[baml(map_key_repr = "string")]` parses non-String map keys from JSON objects. -- `#[baml(map_key_repr = "pairs")]` represents maps as `[{ key: ..., value: ... }]`. -- `RenderOptions::hoist_classes(HoistClasses::All)` hoists all classes for compact unions. - -## Yoink sources - -These crates are copied from the BAML engine repo under: -- `engine/baml-lib/baml-types` -- `engine/baml-lib/jinja-runtime` -- `engine/baml-lib/jsonish` -- `engine/baml-lib/diagnostics` -- `engine/baml-ids` -- `engine/bstd` - -We only trimmed modules that depend on the full compiler/runtime stack. The core -rendering and parsing logic is kept intact. - -## Notes - -- `internal-baml-jinja` is trimmed to `output_format::types` only. -- `jsonish` excludes streaming helpers and internal tests that depend on - `internal-baml-core`. - -## TODOs - -See `TODO.md`. diff --git a/crates/baml-bridge/UPSTREAM.md b/crates/baml-bridge/UPSTREAM.md deleted file mode 100644 index 2b83d48d..00000000 --- a/crates/baml-bridge/UPSTREAM.md +++ /dev/null @@ -1,17 +0,0 @@ -# Upstream reference - -Source repo: /Users/darin/vendor/github.com/BoundaryML/baml -Commit: 51662aa4fa0620a7ecba1699f2cf1626deaaed95 - -Yoinked crates: -- engine/baml-lib/baml-types -- engine/baml-lib/jinja-runtime -- engine/baml-lib/jsonish -- engine/baml-lib/diagnostics -- engine/baml-ids -- engine/bstd - -Local trims: -- internal-baml-jinja: keep only output_format renderer (lib.rs + output_format/mod.rs). -- jsonish: drop helpers/tests modules and streaming helpers from compilation; replace - internal-baml-core hooks with a local jinja predicate evaluator. diff --git a/crates/baml-bridge/examples/manual.rs b/crates/baml-bridge/examples/manual.rs deleted file mode 100644 index cfb4d7eb..00000000 --- a/crates/baml-bridge/examples/manual.rs +++ /dev/null @@ -1,23 +0,0 @@ -use baml_bridge::internal_baml_jinja::types::RenderOptions; -use baml_bridge::{parse_llm_output, render_schema, BamlType}; - -/// A user record returned by the model. -#[derive(Debug, Clone, PartialEq, BamlType)] -struct User { - /// Full name for display. - #[baml(alias = "fullName")] - name: String, - /// Age in years. - age: i64, -} - -fn main() { - let schema = render_schema::(RenderOptions::default()) - .expect("render failed") - .unwrap_or_default(); - println!("Schema:\n{}", schema); - - let raw = r#"{ "name": "Ada Lovelace", "age": 36 }"#; - let parsed = parse_llm_output::(raw, true).expect("parse failed"); - println!("Parsed: {:?}", parsed.value); -} diff --git a/crates/baml-bridge/src/convert.rs b/crates/baml-bridge/src/convert.rs deleted file mode 100644 index 44882b2e..00000000 --- a/crates/baml-bridge/src/convert.rs +++ /dev/null @@ -1,312 +0,0 @@ -use std::{ - collections::{BTreeMap, HashMap}, - fmt, - rc::Rc, - sync::Arc, -}; - -use baml_types::{BamlMap, BamlValue}; - -#[derive(Debug, Clone)] -pub struct BamlConvertError { - pub path: Vec, - pub expected: &'static str, - pub got: String, - pub message: String, -} - -impl BamlConvertError { - pub fn new( - path: Vec, - expected: &'static str, - got: impl Into, - message: impl Into, - ) -> Self { - Self { - path, - expected, - got: got.into(), - message: message.into(), - } - } - - pub fn with_path(mut self, segment: impl Into) -> Self { - self.path.push(segment.into()); - self - } - - pub fn path_string(&self) -> String { - if self.path.is_empty() { - "".to_string() - } else { - self.path.join(".") - } - } -} - -impl fmt::Display for BamlConvertError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{} (expected {}, got {}) at {}", - self.message, - self.expected, - self.got, - self.path_string() - ) - } -} - -impl std::error::Error for BamlConvertError {} - -pub trait BamlValueConvert: Sized { - fn try_from_baml_value(value: BamlValue, path: Vec) -> Result; -} - -impl BamlValueConvert for String { - fn try_from_baml_value(value: BamlValue, path: Vec) -> Result { - match value { - BamlValue::String(s) => Ok(s), - other => Err(BamlConvertError::new( - path, - "string", - format!("{other:?}"), - "expected a string", - )), - } - } -} - -impl BamlValueConvert for bool { - fn try_from_baml_value(value: BamlValue, path: Vec) -> Result { - match value { - BamlValue::Bool(v) => Ok(v), - other => Err(BamlConvertError::new( - path, - "bool", - format!("{other:?}"), - "expected a boolean", - )), - } - } -} - -impl BamlValueConvert for f64 { - fn try_from_baml_value(value: BamlValue, path: Vec) -> Result { - match value { - BamlValue::Float(v) => Ok(v), - BamlValue::Int(v) => Ok(v as f64), - other => Err(BamlConvertError::new( - path, - "float", - format!("{other:?}"), - "expected a float", - )), - } - } -} - -impl BamlValueConvert for f32 { - fn try_from_baml_value(value: BamlValue, path: Vec) -> Result { - let path_clone = path.clone(); - let v = f64::try_from_baml_value(value, path)?; - if v > f32::MAX as f64 || v < f32::MIN as f64 { - return Err(BamlConvertError::new( - path_clone, - "float", - v.to_string(), - "float out of range", - )); - } - Ok(v as f32) - } -} - -impl BamlValueConvert for i64 { - fn try_from_baml_value(value: BamlValue, path: Vec) -> Result { - match value { - BamlValue::Int(v) => Ok(v), - other => Err(BamlConvertError::new( - path, - "int", - format!("{other:?}"), - "expected an integer", - )), - } - } -} - -macro_rules! impl_signed_int { - ($ty:ty) => { - impl BamlValueConvert for $ty { - fn try_from_baml_value( - value: BamlValue, - path: Vec, - ) -> Result { - let v = i64::try_from_baml_value(value, path.clone())?; - if v < <$ty>::MIN as i64 || v > <$ty>::MAX as i64 { - return Err(BamlConvertError::new( - path, - stringify!($ty), - v.to_string(), - "integer out of range", - )); - } - Ok(v as $ty) - } - } - }; -} - -impl_signed_int!(i8); -impl_signed_int!(i16); -impl_signed_int!(i32); -impl_signed_int!(isize); - -macro_rules! impl_unsigned_int { - ($ty:ty) => { - impl BamlValueConvert for $ty { - fn try_from_baml_value( - value: BamlValue, - path: Vec, - ) -> Result { - let v = i64::try_from_baml_value(value, path.clone())?; - if v < 0 || v > <$ty>::MAX as i64 { - return Err(BamlConvertError::new( - path, - stringify!($ty), - v.to_string(), - "integer out of range", - )); - } - Ok(v as $ty) - } - } - }; -} - -impl_unsigned_int!(u8); -impl_unsigned_int!(u16); -impl_unsigned_int!(u32); - -impl BamlValueConvert for Option -where - T: BamlValueConvert, -{ - fn try_from_baml_value(value: BamlValue, path: Vec) -> Result { - match value { - BamlValue::Null => Ok(None), - other => Ok(Some(T::try_from_baml_value(other, path)?)), - } - } -} - -impl BamlValueConvert for Vec -where - T: BamlValueConvert, -{ - fn try_from_baml_value(value: BamlValue, path: Vec) -> Result { - match value { - BamlValue::List(items) => items - .into_iter() - .enumerate() - .map(|(idx, item)| { - let mut item_path = path.clone(); - item_path.push(idx.to_string()); - T::try_from_baml_value(item, item_path) - }) - .collect(), - other => Err(BamlConvertError::new( - path, - "list", - format!("{other:?}"), - "expected a list", - )), - } - } -} - -impl BamlValueConvert for Box -where - T: BamlValueConvert, -{ - fn try_from_baml_value(value: BamlValue, path: Vec) -> Result { - Ok(Box::new(T::try_from_baml_value(value, path)?)) - } -} - -impl BamlValueConvert for Arc -where - T: BamlValueConvert, -{ - fn try_from_baml_value(value: BamlValue, path: Vec) -> Result { - Ok(Arc::new(T::try_from_baml_value(value, path)?)) - } -} - -impl BamlValueConvert for Rc -where - T: BamlValueConvert, -{ - fn try_from_baml_value(value: BamlValue, path: Vec) -> Result { - Ok(Rc::new(T::try_from_baml_value(value, path)?)) - } -} - -impl BamlValueConvert for HashMap -where - V: BamlValueConvert, -{ - fn try_from_baml_value(value: BamlValue, path: Vec) -> Result { - map_to_collection(value, path, |map| map.into_iter().collect()) - } -} - -impl BamlValueConvert for BTreeMap -where - V: BamlValueConvert, -{ - fn try_from_baml_value(value: BamlValue, path: Vec) -> Result { - map_to_collection(value, path, |map| map.into_iter().collect()) - } -} - -fn map_to_collection( - value: BamlValue, - path: Vec, - to_collection: impl FnOnce(HashMap) -> C, -) -> Result -where - V: BamlValueConvert, -{ - let map = match value { - BamlValue::Map(map) => map, - other => { - return Err(BamlConvertError::new( - path, - "map", - format!("{other:?}"), - "expected a map", - )) - } - }; - - let mut out = HashMap::new(); - for (key, value) in map.into_iter() { - let mut item_path = path.clone(); - item_path.push(key.clone()); - let converted = V::try_from_baml_value(value, item_path)?; - out.insert(key, converted); - } - - Ok(to_collection(out)) -} - -pub fn get_field<'a>( - map: &'a BamlMap, - name: &str, - alias: Option<&str>, -) -> Option<&'a BamlValue> { - map.get(name) - .or_else(|| alias.and_then(|alias| map.get(alias))) -} diff --git a/crates/baml-bridge/src/lib.rs b/crates/baml-bridge/src/lib.rs deleted file mode 100644 index 9bd87d19..00000000 --- a/crates/baml-bridge/src/lib.rs +++ /dev/null @@ -1,596 +0,0 @@ -pub use baml_types; -pub use internal_baml_jinja; -pub use internal_baml_jinja::types::{HoistClasses, MapStyle, RenderOptions}; -pub use jsonish; - -#[cfg(feature = "derive")] -pub use baml_bridge_derive::BamlType; - -mod convert; -mod registry; -mod to_value; - -use std::{ - collections::{BTreeMap, HashMap, HashSet}, - rc::Rc, - sync::Arc, -}; - -use baml_types::{BamlValue, Constraint, ConstraintLevel, ResponseCheck, TypeIR}; -use internal_baml_jinja::types::OutputFormatContent; -use jsonish::deserializer::{ - coercer::{run_user_checks, ParsingError}, - deserialize_flags::{DeserializerConditions, Flag}, - types::BamlValueWithFlags, -}; -use sha2::{Digest, Sha256}; - -pub use convert::{get_field, BamlConvertError, BamlValueConvert}; -pub use registry::{default_streaming_behavior, Registry}; -pub use to_value::ToBamlValue; - -pub trait BamlTypeInternal { - fn baml_internal_name() -> &'static str; - fn baml_type_ir() -> TypeIR; - fn register(reg: &mut Registry); -} - -pub trait BamlType: BamlTypeInternal + BamlValueConvert + Sized + 'static { - fn baml_internal_name() -> &'static str { - ::baml_internal_name() - } - - fn baml_type_ir() -> TypeIR { - ::baml_type_ir() - } - - fn baml_output_format() -> &'static OutputFormatContent; - - fn try_from_baml_value(value: BamlValue) -> Result { - ::try_from_baml_value(value, Vec::new()) - } -} - -pub trait BamlAdapter { - fn type_ir() -> TypeIR; - fn register(_reg: &mut Registry) {} - fn try_from_baml(value: BamlValue, path: Vec) -> Result; -} - -#[derive(Debug, Clone)] -pub struct Parsed { - pub value: T, - pub baml_value: BamlValue, - pub flags: Vec, - pub checks: Vec, - pub explanations: Vec, -} - -#[derive(Debug, thiserror::Error)] -pub enum BamlParseError { - #[error("jsonish parse error: {0}")] - Jsonish(#[from] anyhow::Error), - #[error("constraint asserts failed")] - ConstraintAssertsFailed { failed: Vec }, - #[error("conversion error: {0}")] - Convert(#[from] BamlConvertError), -} - -pub fn render_schema( - options: RenderOptions, -) -> Result, minijinja::Error> { - T::baml_output_format().render(options) -} - -pub fn parse_llm_output( - raw: &str, - is_done: bool, -) -> Result, BamlParseError> { - let output_format = T::baml_output_format(); - let parsed = match jsonish::from_str(output_format, &output_format.target, raw, is_done) { - Ok(parsed) => parsed, - Err(err) => { - if has_assert_failure(&err) { - let failed = collect_assert_constraints(output_format); - return Err(BamlParseError::ConstraintAssertsFailed { failed }); - } - return Err(BamlParseError::Jsonish(err)); - } - }; - - let baml_value_with_meta: baml_types::BamlValueWithMeta = - parsed.clone().into(); - let baml_value: BamlValue = baml_value_with_meta.into(); - - let value = ::try_from_baml_value(baml_value.clone())?; - - let mut flags = Vec::new(); - collect_flags(&parsed, &mut flags); - - let mut checks = Vec::new(); - collect_checks(&parsed, &mut checks)?; - - let mut explanations = Vec::new(); - parsed.explanation_impl(vec!["".to_string()], &mut explanations); - - let mut failed_asserts = Vec::new(); - collect_assert_failures(&parsed, &mut failed_asserts)?; - if !failed_asserts.is_empty() { - return Err(BamlParseError::ConstraintAssertsFailed { - failed: failed_asserts, - }); - } - - Ok(Parsed { - value, - baml_value, - flags, - checks, - explanations, - }) -} - -pub fn with_constraints(mut r#type: TypeIR, constraints: Vec) -> TypeIR { - r#type.meta_mut().constraints.extend(constraints); - r#type -} - -pub fn schema_fingerprint( - output_format: &OutputFormatContent, - options: RenderOptions, -) -> Result { - let rendered = output_format.render(options)?.unwrap_or_default(); - let mut hasher = Sha256::new(); - hasher.update(rendered.as_bytes()); - hasher.update(output_format.target.to_string().as_bytes()); - Ok(format!("{:x}", hasher.finalize())) -} - -fn collect_flags_recursive(value: &BamlValueWithFlags, flags: &mut Vec) { - match value { - BamlValueWithFlags::String(v) => { - collect_from_conditions(&v.flags, flags); - } - BamlValueWithFlags::Int(v) => { - collect_from_conditions(&v.flags, flags); - } - BamlValueWithFlags::Float(v) => { - collect_from_conditions(&v.flags, flags); - } - BamlValueWithFlags::Bool(v) => { - collect_from_conditions(&v.flags, flags); - } - BamlValueWithFlags::Enum(_, _, v) => { - collect_from_conditions(&v.flags, flags); - } - BamlValueWithFlags::Media(_, v) => { - collect_from_conditions(&v.flags, flags); - } - BamlValueWithFlags::List(conds, _, items) => { - collect_from_conditions(conds, flags); - for item in items { - collect_flags_recursive(item, flags); - } - } - BamlValueWithFlags::Map(conds, _, items) => { - collect_from_conditions(conds, flags); - for (_, (entry_flags, entry_value)) in items { - collect_from_conditions(entry_flags, flags); - collect_flags_recursive(entry_value, flags); - } - } - BamlValueWithFlags::Class(_, conds, _, fields) => { - collect_from_conditions(conds, flags); - for (_, field_value) in fields { - collect_flags_recursive(field_value, flags); - } - } - BamlValueWithFlags::Null(_, conds) => { - collect_from_conditions(conds, flags); - } - } -} - -fn collect_from_conditions(conditions: &DeserializerConditions, flags: &mut Vec) { - flags.extend(conditions.flags.iter().cloned()); -} - -fn collect_flags(value: &BamlValueWithFlags, flags: &mut Vec) { - collect_flags_recursive(value, flags); -} - -fn collect_checks( - value: &BamlValueWithFlags, - checks: &mut Vec, -) -> Result<(), BamlParseError> { - let baml_value = baml_value_from_flags(value); - let results = run_user_checks(&baml_value, value.field_type()).map_err(BamlParseError::from)?; - - for result in results { - if let Some(check) = ResponseCheck::from_check_result(result) { - checks.push(check); - } - } - - match value { - BamlValueWithFlags::List(_, _, items) => { - for item in items { - collect_checks(item, checks)?; - } - } - BamlValueWithFlags::Map(_, _, items) => { - for (_, (_, entry_value)) in items { - collect_checks(entry_value, checks)?; - } - } - BamlValueWithFlags::Class(_, _, _, fields) => { - for (_, field_value) in fields { - collect_checks(field_value, checks)?; - } - } - _ => {} - } - - Ok(()) -} - -fn collect_assert_failures( - value: &BamlValueWithFlags, - failed: &mut Vec, -) -> Result<(), BamlParseError> { - let baml_value = baml_value_from_flags(value); - let results = run_user_checks(&baml_value, value.field_type()).map_err(BamlParseError::from)?; - - for (constraint, ok) in results { - if constraint.level == ConstraintLevel::Assert && !ok { - failed.push(ResponseCheck { - name: constraint.label.unwrap_or_else(|| "assert".to_string()), - expression: constraint.expression.0, - status: "failed".to_string(), - }); - } - } - - match value { - BamlValueWithFlags::List(_, _, items) => { - for item in items { - collect_assert_failures(item, failed)?; - } - } - BamlValueWithFlags::Map(_, _, items) => { - for (_, (_, entry_value)) in items { - collect_assert_failures(entry_value, failed)?; - } - } - BamlValueWithFlags::Class(_, _, _, fields) => { - for (_, field_value) in fields { - collect_assert_failures(field_value, failed)?; - } - } - _ => {} - } - - Ok(()) -} - -fn collect_assert_constraints(of: &OutputFormatContent) -> Vec { - let mut failed = Vec::new(); - let mut seen = HashSet::new(); - - collect_assert_constraints_in_type(&of.target, &mut failed, &mut seen); - - for class in of.classes.values() { - for constraint in &class.constraints { - push_assert_constraint(constraint, &mut failed, &mut seen); - } - for (_, field_type, _, _) in &class.fields { - collect_assert_constraints_in_type(field_type, &mut failed, &mut seen); - } - } - - for r#enum in of.enums.values() { - for constraint in &r#enum.constraints { - push_assert_constraint(constraint, &mut failed, &mut seen); - } - } - - failed -} - -fn collect_assert_constraints_in_type( - r#type: &TypeIR, - failed: &mut Vec, - seen: &mut HashSet<(String, String)>, -) { - for constraint in &r#type.meta().constraints { - push_assert_constraint(constraint, failed, seen); - } - - match r#type { - TypeIR::List(inner, _) => collect_assert_constraints_in_type(inner, failed, seen), - TypeIR::Map(key, value, _) => { - collect_assert_constraints_in_type(key, failed, seen); - collect_assert_constraints_in_type(value, failed, seen); - } - TypeIR::Union(union, _) => { - for item in union.iter_include_null() { - collect_assert_constraints_in_type(item, failed, seen); - } - } - TypeIR::Tuple(items, _) => { - for item in items { - collect_assert_constraints_in_type(item, failed, seen); - } - } - TypeIR::Arrow(arrow, _) => { - for param in &arrow.param_types { - collect_assert_constraints_in_type(param, failed, seen); - } - collect_assert_constraints_in_type(&arrow.return_type, failed, seen); - } - TypeIR::Top(_) - | TypeIR::Primitive(..) - | TypeIR::Enum { .. } - | TypeIR::Literal(..) - | TypeIR::Class { .. } - | TypeIR::RecursiveTypeAlias { .. } => {} - } -} - -fn push_assert_constraint( - constraint: &Constraint, - failed: &mut Vec, - seen: &mut HashSet<(String, String)>, -) { - if constraint.level != ConstraintLevel::Assert { - return; - } - - let name = constraint - .label - .clone() - .unwrap_or_else(|| "assert".to_string()); - let expr = constraint.expression.0.clone(); - if seen.insert((name.clone(), expr.clone())) { - failed.push(ResponseCheck { - name, - expression: expr, - status: "failed".to_string(), - }); - } -} - -fn has_assert_failure(err: &anyhow::Error) -> bool { - err.to_string().contains("Assertions failed.") -} - -fn baml_value_from_flags(value: &BamlValueWithFlags) -> BamlValue { - match value { - BamlValueWithFlags::String(v) => BamlValue::String(v.value.clone()), - BamlValueWithFlags::Int(v) => BamlValue::Int(v.value), - BamlValueWithFlags::Float(v) => BamlValue::Float(v.value), - BamlValueWithFlags::Bool(v) => BamlValue::Bool(v.value), - BamlValueWithFlags::Enum(name, _, v) => BamlValue::Enum(name.clone(), v.value.clone()), - BamlValueWithFlags::Media(_, v) => BamlValue::Media(v.value.clone()), - BamlValueWithFlags::List(_, _, items) => { - BamlValue::List(items.iter().map(baml_value_from_flags).collect()) - } - BamlValueWithFlags::Map(_, _, items) => BamlValue::Map( - items - .iter() - .map(|(k, (_, v))| (k.clone(), baml_value_from_flags(v))) - .collect(), - ), - BamlValueWithFlags::Class(name, _, _, fields) => BamlValue::Class( - name.clone(), - fields - .iter() - .map(|(k, v)| (k.clone(), baml_value_from_flags(v))) - .collect(), - ), - BamlValueWithFlags::Null(_, _) => BamlValue::Null, - } -} - -impl BamlTypeInternal for String { - fn baml_internal_name() -> &'static str { - std::any::type_name::() - } - - fn baml_type_ir() -> TypeIR { - TypeIR::string() - } - - fn register(_reg: &mut Registry) {} -} - -impl BamlTypeInternal for bool { - fn baml_internal_name() -> &'static str { - std::any::type_name::() - } - - fn baml_type_ir() -> TypeIR { - TypeIR::bool() - } - - fn register(_reg: &mut Registry) {} -} - -impl BamlTypeInternal for f64 { - fn baml_internal_name() -> &'static str { - std::any::type_name::() - } - - fn baml_type_ir() -> TypeIR { - TypeIR::float() - } - - fn register(_reg: &mut Registry) {} -} - -impl BamlTypeInternal for f32 { - fn baml_internal_name() -> &'static str { - std::any::type_name::() - } - - fn baml_type_ir() -> TypeIR { - TypeIR::float() - } - - fn register(_reg: &mut Registry) {} -} - -impl BamlTypeInternal for i64 { - fn baml_internal_name() -> &'static str { - std::any::type_name::() - } - - fn baml_type_ir() -> TypeIR { - TypeIR::int() - } - - fn register(_reg: &mut Registry) {} -} - -macro_rules! impl_baml_int_internal { - ($ty:ty) => { - impl BamlTypeInternal for $ty { - fn baml_internal_name() -> &'static str { - std::any::type_name::() - } - - fn baml_type_ir() -> TypeIR { - TypeIR::int() - } - - fn register(_reg: &mut Registry) {} - } - }; -} - -impl_baml_int_internal!(i8); -impl_baml_int_internal!(i16); -impl_baml_int_internal!(i32); -impl_baml_int_internal!(isize); -impl_baml_int_internal!(u8); -impl_baml_int_internal!(u16); -impl_baml_int_internal!(u32); - -impl BamlTypeInternal for Option -where - T: BamlTypeInternal, -{ - fn baml_internal_name() -> &'static str { - std::any::type_name::() - } - - fn baml_type_ir() -> TypeIR { - TypeIR::optional(T::baml_type_ir()) - } - - fn register(reg: &mut Registry) { - T::register(reg); - } -} - -impl BamlTypeInternal for Vec -where - T: BamlTypeInternal, -{ - fn baml_internal_name() -> &'static str { - std::any::type_name::() - } - - fn baml_type_ir() -> TypeIR { - TypeIR::list(T::baml_type_ir()) - } - - fn register(reg: &mut Registry) { - T::register(reg); - } -} - -impl BamlTypeInternal for Box -where - T: BamlTypeInternal, -{ - fn baml_internal_name() -> &'static str { - std::any::type_name::() - } - - fn baml_type_ir() -> TypeIR { - T::baml_type_ir() - } - - fn register(reg: &mut Registry) { - T::register(reg); - } -} - -impl BamlTypeInternal for Arc -where - T: BamlTypeInternal, -{ - fn baml_internal_name() -> &'static str { - std::any::type_name::() - } - - fn baml_type_ir() -> TypeIR { - T::baml_type_ir() - } - - fn register(reg: &mut Registry) { - T::register(reg); - } -} - -impl BamlTypeInternal for Rc -where - T: BamlTypeInternal, -{ - fn baml_internal_name() -> &'static str { - std::any::type_name::() - } - - fn baml_type_ir() -> TypeIR { - T::baml_type_ir() - } - - fn register(reg: &mut Registry) { - T::register(reg); - } -} - -impl BamlTypeInternal for HashMap -where - V: BamlTypeInternal, -{ - fn baml_internal_name() -> &'static str { - std::any::type_name::() - } - - fn baml_type_ir() -> TypeIR { - TypeIR::map(TypeIR::string(), V::baml_type_ir()) - } - - fn register(reg: &mut Registry) { - V::register(reg); - } -} - -impl BamlTypeInternal for BTreeMap -where - V: BamlTypeInternal, -{ - fn baml_internal_name() -> &'static str { - std::any::type_name::() - } - - fn baml_type_ir() -> TypeIR { - TypeIR::map(TypeIR::string(), V::baml_type_ir()) - } - - fn register(reg: &mut Registry) { - V::register(reg); - } -} diff --git a/crates/baml-bridge/src/registry.rs b/crates/baml-bridge/src/registry.rs deleted file mode 100644 index bfe9b77d..00000000 --- a/crates/baml-bridge/src/registry.rs +++ /dev/null @@ -1,210 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use baml_types::{type_meta, StreamingMode, TypeIR}; -use indexmap::{IndexMap, IndexSet}; -use internal_baml_jinja::types::{Class, Enum, OutputFormatContent}; - -#[derive(Debug, Default)] -pub struct Registry { - enums: IndexMap, - classes: IndexMap<(String, StreamingMode), Class>, - class_deps: IndexMap>, - structural_recursive_aliases: IndexMap, - registered: HashSet, -} - -impl Registry { - pub fn new() -> Self { - Self::default() - } - - pub fn mark_type(&mut self, name: &str) -> bool { - if self.registered.contains(name) { - return false; - } - self.registered.insert(name.to_string()); - true - } - - pub fn register_enum(&mut self, r#enum: Enum) { - let name = r#enum.name.real_name().to_string(); - self.enums.entry(name).or_insert(r#enum); - } - - pub fn register_class(&mut self, class: Class) { - let name = class.name.real_name().to_string(); - let mode = class.namespace; - let key = (name.clone(), mode); - let entry = self.classes.entry(key).or_insert_with(|| class); - - let deps = self.class_deps.entry(name).or_default(); - for (_, field_type, _, _) in &entry.fields { - collect_class_refs(field_type, deps); - } - } - - pub fn register_structural_alias(&mut self, name: String, alias: TypeIR) { - self.structural_recursive_aliases.insert(name, alias); - } - - pub fn build(self, target: TypeIR) -> OutputFormatContent { - let recursive_classes = compute_recursive_classes(&self.class_deps); - - let mut enums = self.enums.into_iter().collect::>(); - enums.sort_by(|(a, _), (b, _)| a.cmp(b)); - let enums = enums.into_iter().map(|(_, v)| v).collect::>(); - - let mut classes = self.classes.into_iter().collect::>(); - classes.sort_by(|(a, _), (b, _)| { - let (a_name, a_mode) = a; - let (b_name, b_mode) = b; - match a_name.cmp(b_name) { - std::cmp::Ordering::Equal => mode_rank(*a_mode).cmp(&mode_rank(*b_mode)), - other => other, - } - }); - let classes = classes.into_iter().map(|(_, v)| v).collect::>(); - - OutputFormatContent::target(target) - .enums(enums) - .classes(classes) - .recursive_classes(recursive_classes) - .structural_recursive_aliases(self.structural_recursive_aliases) - .build() - } -} - -fn collect_class_refs(field_type: &TypeIR, deps: &mut IndexSet) { - match field_type { - TypeIR::Class { name, .. } => { - deps.insert(name.clone()); - } - TypeIR::RecursiveTypeAlias { name, .. } => { - deps.insert(name.clone()); - } - TypeIR::List(inner, _) => collect_class_refs(inner, deps), - TypeIR::Map(key, value, _) => { - collect_class_refs(key, deps); - collect_class_refs(value, deps); - } - TypeIR::Union(union, _) => { - for item in union.iter_include_null() { - collect_class_refs(item, deps); - } - } - TypeIR::Tuple(items, _) => { - for item in items { - collect_class_refs(item, deps); - } - } - TypeIR::Arrow(arrow, _) => { - for param in &arrow.param_types { - collect_class_refs(param, deps); - } - collect_class_refs(&arrow.return_type, deps); - } - TypeIR::Primitive(..) | TypeIR::Enum { .. } | TypeIR::Literal(..) | TypeIR::Top(..) => {} - } -} - -fn compute_recursive_classes(class_deps: &IndexMap>) -> IndexSet { - let mut index = 0usize; - let mut indices: HashMap = HashMap::new(); - let mut lowlink: HashMap = HashMap::new(); - let mut stack: Vec = Vec::new(); - let mut on_stack: HashSet = HashSet::new(); - let mut recursive = HashSet::new(); - - #[allow(clippy::too_many_arguments)] - fn strongconnect( - node: &str, - index: &mut usize, - indices: &mut HashMap, - lowlink: &mut HashMap, - stack: &mut Vec, - on_stack: &mut HashSet, - deps: &IndexMap>, - recursive: &mut HashSet, - ) { - let node_key = node.to_string(); - indices.insert(node_key.clone(), *index); - lowlink.insert(node_key.clone(), *index); - *index += 1; - stack.push(node_key.clone()); - on_stack.insert(node_key.clone()); - - if let Some(children) = deps.get(node) { - for child in children.iter() { - if !indices.contains_key(child) { - strongconnect( - child, index, indices, lowlink, stack, on_stack, deps, recursive, - ); - let lowlink_child = lowlink.get(child).copied().unwrap_or(0); - let lowlink_node = lowlink.get(&node_key).copied().unwrap_or(0); - lowlink.insert(node_key.clone(), lowlink_node.min(lowlink_child)); - } else if on_stack.contains(child) { - let index_child = indices.get(child).copied().unwrap_or(0); - let lowlink_node = lowlink.get(&node_key).copied().unwrap_or(0); - lowlink.insert(node_key.clone(), lowlink_node.min(index_child)); - } - } - } - - let node_index = indices.get(&node_key).copied().unwrap_or(0); - let node_lowlink = lowlink.get(&node_key).copied().unwrap_or(0); - if node_lowlink == node_index { - let mut scc = Vec::new(); - while let Some(w) = stack.pop() { - on_stack.remove(&w); - scc.push(w.clone()); - if w == node_key { - break; - } - } - - if scc.len() > 1 { - for name in scc { - recursive.insert(name); - } - } else if let Some(name) = scc.first() { - if deps - .get(name) - .map(|edges| edges.contains(name)) - .unwrap_or(false) - { - recursive.insert(name.clone()); - } - } - } - } - - for node in class_deps.keys() { - if !indices.contains_key(node) { - strongconnect( - node, - &mut index, - &mut indices, - &mut lowlink, - &mut stack, - &mut on_stack, - class_deps, - &mut recursive, - ); - } - } - - let mut sorted = recursive.into_iter().collect::>(); - sorted.sort(); - IndexSet::from_iter(sorted) -} - -pub fn default_streaming_behavior() -> type_meta::base::StreamingBehavior { - type_meta::base::StreamingBehavior::default() -} - -fn mode_rank(mode: StreamingMode) -> u8 { - match mode { - StreamingMode::NonStreaming => 0, - StreamingMode::Streaming => 1, - } -} diff --git a/crates/baml-bridge/src/to_value.rs b/crates/baml-bridge/src/to_value.rs deleted file mode 100644 index 145cd5b9..00000000 --- a/crates/baml-bridge/src/to_value.rs +++ /dev/null @@ -1,203 +0,0 @@ -use std::{ - collections::{BTreeMap, HashMap}, - rc::Rc, - sync::Arc, -}; - -use baml_types::{BamlMap, BamlValue}; - -pub trait ToBamlValue { - fn to_baml_value(&self) -> BamlValue; -} - -impl ToBamlValue for &T { - fn to_baml_value(&self) -> BamlValue { - (*self).to_baml_value() - } -} - -impl ToBamlValue for BamlValue { - fn to_baml_value(&self) -> BamlValue { - self.clone() - } -} - -impl ToBamlValue for String { - fn to_baml_value(&self) -> BamlValue { - BamlValue::String(self.clone()) - } -} - -impl ToBamlValue for &str { - fn to_baml_value(&self) -> BamlValue { - BamlValue::String((*self).to_string()) - } -} - -impl ToBamlValue for bool { - fn to_baml_value(&self) -> BamlValue { - BamlValue::Bool(*self) - } -} - -impl ToBamlValue for i64 { - fn to_baml_value(&self) -> BamlValue { - BamlValue::Int(*self) - } -} - -macro_rules! impl_signed_int { - ($ty:ty) => { - impl ToBamlValue for $ty { - fn to_baml_value(&self) -> BamlValue { - BamlValue::Int(*self as i64) - } - } - }; -} - -macro_rules! impl_unsigned_int { - ($ty:ty) => { - impl ToBamlValue for $ty { - fn to_baml_value(&self) -> BamlValue { - BamlValue::Int(*self as i64) - } - } - }; -} - -impl_signed_int!(i8); -impl_signed_int!(i16); -impl_signed_int!(i32); -impl_signed_int!(isize); - -impl_unsigned_int!(u8); -impl_unsigned_int!(u16); -impl_unsigned_int!(u32); - -impl ToBamlValue for f32 { - fn to_baml_value(&self) -> BamlValue { - BamlValue::Float(*self as f64) - } -} - -impl ToBamlValue for f64 { - fn to_baml_value(&self) -> BamlValue { - BamlValue::Float(*self) - } -} - -impl ToBamlValue for baml_types::BamlMedia { - fn to_baml_value(&self) -> BamlValue { - BamlValue::Media(self.clone()) - } -} - -impl ToBamlValue for Option { - fn to_baml_value(&self) -> BamlValue { - match self { - Some(value) => value.to_baml_value(), - None => BamlValue::Null, - } - } -} - -impl ToBamlValue for Vec { - fn to_baml_value(&self) -> BamlValue { - BamlValue::List(self.iter().map(|value| value.to_baml_value()).collect()) - } -} - -impl ToBamlValue for HashMap { - fn to_baml_value(&self) -> BamlValue { - let mut map: BamlMap = BamlMap::new(); - for (key, value) in self { - map.insert(key.clone(), value.to_baml_value()); - } - BamlValue::Map(map) - } -} - -impl ToBamlValue for BTreeMap { - fn to_baml_value(&self) -> BamlValue { - let mut map: BamlMap = BamlMap::new(); - for (key, value) in self { - map.insert(key.clone(), value.to_baml_value()); - } - BamlValue::Map(map) - } -} - -impl ToBamlValue for Box { - fn to_baml_value(&self) -> BamlValue { - (**self).to_baml_value() - } -} - -impl ToBamlValue for Arc { - fn to_baml_value(&self) -> BamlValue { - (**self).to_baml_value() - } -} - -impl ToBamlValue for Rc { - fn to_baml_value(&self) -> BamlValue { - (**self).to_baml_value() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::collections::BTreeMap; - use std::rc::Rc; - use std::sync::Arc; - - #[test] - fn test_primitive_to_baml_value() { - assert_eq!("hello".to_baml_value(), BamlValue::String("hello".into())); - assert_eq!(true.to_baml_value(), BamlValue::Bool(true)); - assert_eq!(42i32.to_baml_value(), BamlValue::Int(42)); - assert_eq!((-7i16).to_baml_value(), BamlValue::Int(-7)); - assert_eq!(3.5f32.to_baml_value(), BamlValue::Float(3.5)); - assert_eq!(2.25f64.to_baml_value(), BamlValue::Float(2.25)); - } - - #[test] - fn test_container_to_baml_value() { - let some: Option = Some(1); - let none: Option = None; - assert_eq!(some.to_baml_value(), BamlValue::Int(1)); - assert_eq!(none.to_baml_value(), BamlValue::Null); - - let vec = vec!["a", "b"]; - assert_eq!( - vec.to_baml_value(), - BamlValue::List(vec![ - BamlValue::String("a".into()), - BamlValue::String("b".into()) - ]) - ); - - let mut map: HashMap = HashMap::new(); - map.insert("answer".to_string(), 42); - let mut expected: BamlMap = BamlMap::new(); - expected.insert("answer".to_string(), BamlValue::Int(42)); - assert_eq!(map.to_baml_value(), BamlValue::Map(expected)); - - let mut tree: BTreeMap = BTreeMap::new(); - tree.insert("alpha".to_string(), 1); - let mut expected_tree: BamlMap = BamlMap::new(); - expected_tree.insert("alpha".to_string(), BamlValue::Int(1)); - assert_eq!(tree.to_baml_value(), BamlValue::Map(expected_tree)); - - let boxed: Box = Box::new(7); - assert_eq!(boxed.to_baml_value(), BamlValue::Int(7)); - - let shared = Arc::new("shared".to_string()); - assert_eq!(shared.to_baml_value(), BamlValue::String("shared".into())); - - let rc_value = Rc::new(true); - assert_eq!(rc_value.to_baml_value(), BamlValue::Bool(true)); - } -} diff --git a/crates/baml-bridge/tests/fuzz.rs b/crates/baml-bridge/tests/fuzz.rs deleted file mode 100644 index 0fb7d15a..00000000 --- a/crates/baml-bridge/tests/fuzz.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::collections::HashMap; - -use baml_bridge::jsonish::deserializer::deserialize_flags::Flag; -use baml_bridge::{parse_llm_output, BamlType}; - -#[derive(Debug, Clone, PartialEq, BamlType)] -struct FuzzUser { - name: String, - age: u32, - nickname: Option, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -struct FuzzExplain { - values: HashMap, -} - -#[test] -fn markdown_fence_parses_and_sets_flag() { - let raw = "```json\n{ \"name\": \"Ada\", \"age\": 36 }\n```"; - let parsed = parse_llm_output::(raw, true).expect("parse"); - - assert_eq!(parsed.value.name, "Ada"); - assert!(parsed - .flags - .iter() - .any(|flag| matches!(flag, Flag::ObjectFromMarkdown(_)))); -} - -#[test] -fn trailing_comma_parses() { - let raw = r#"{ "name": "Ada", "age": 36, }"#; - let parsed = parse_llm_output::(raw, true).expect("parse"); - assert_eq!(parsed.value.age, 36); -} - -#[test] -fn extra_keys_are_ignored() { - let raw = r#"{ "name": "Ada", "age": 36, "extra": "ignored" }"#; - let parsed = parse_llm_output::(raw, true).expect("parse"); - assert_eq!(parsed.value.name, "Ada"); - assert_eq!(parsed.value.nickname, None); -} - -#[test] -fn explanations_surface_on_map_parse_error() { - let raw = r#"{ "values": { "ok": 1, "bad": "oops" } }"#; - let parsed = parse_llm_output::(raw, true).expect("parse"); - assert_eq!(parsed.value.values.get("ok"), Some(&1)); - assert!(!parsed.explanations.is_empty()); -} diff --git a/crates/baml-bridge/tests/golden.rs b/crates/baml-bridge/tests/golden.rs deleted file mode 100644 index 9b9aa22e..00000000 --- a/crates/baml-bridge/tests/golden.rs +++ /dev/null @@ -1,63 +0,0 @@ -use baml_bridge::{render_schema, BamlType, HoistClasses, RenderOptions}; -use expect_test::expect; - -/// Golden user docs. -#[derive(Debug, Clone, PartialEq, BamlType)] -struct GoldenUser { - /// Full name for display. - #[baml(alias = "fullName")] - name: String, - age: i64, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -#[baml(tag = "kind")] -enum GoldenShape { - /// A circle. - Circle { - radius: f64, - }, - Square { - side: f64, - }, -} - -#[test] -fn schema_snapshot_user_default() { - let schema = render_schema::(RenderOptions::default()) - .expect("render failed") - .unwrap_or_default(); - - expect![[r#"Answer in JSON using this schema: -{ - // Golden user docs. - - // Full name for display. - fullName: string, - age: int, -}"#]] - .assert_eq(&schema); -} - -#[test] -fn schema_snapshot_shape_hoisted() { - let schema = render_schema::(RenderOptions::hoist_classes(HoistClasses::All)) - .expect("render failed") - .unwrap_or_default(); - - expect![[r#"GoldenShape_Circle { - // A circle. - - kind: "Circle", - radius: float, -} - -GoldenShape_Square { - kind: "Square", - side: float, -} - -Answer in JSON using any of these schemas: -GoldenShape_Circle or GoldenShape_Square"#]] - .assert_eq(&schema); -} diff --git a/crates/baml-bridge/tests/integration.rs b/crates/baml-bridge/tests/integration.rs deleted file mode 100644 index a3c42284..00000000 --- a/crates/baml-bridge/tests/integration.rs +++ /dev/null @@ -1,300 +0,0 @@ -use std::collections::HashMap; - -use baml_bridge::baml_types::StreamingMode; -use baml_bridge::RenderOptions; -use baml_bridge::{parse_llm_output, render_schema, BamlParseError, BamlType}; - -/// A user profile returned by the model. -/// -/// ## Notes -/// - `fullName` should be the display name. -#[derive(Debug, Clone, PartialEq, BamlType)] -struct DocUser { - /// Full name as displayed in the UI. - #[baml(alias = "fullName")] - name: String, - age: i64, -} - -/// This should be ignored. -#[derive(Debug, Clone, PartialEq, BamlType)] -#[baml(description = "Override description.")] -struct OverrideUser { - value: String, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -enum Color { - /// Red hot. - Red, - #[baml(alias = "green")] - Green, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -#[baml(tag = "type")] -enum Shape { - /// A circle, defined by its radius. - Circle { - /// Radius in meters. - radius: f64, - }, - Rectangle { - width: f64, - height: f64, - }, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -struct BigIntString { - #[baml(int_repr = "string")] - id: u64, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -struct BigIntOption { - #[baml(int_repr = "i64")] - id: Option, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -struct MapKeys { - #[baml(map_key_repr = "string")] - values: HashMap, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -struct MapKeysOption { - #[baml(map_key_repr = "string")] - values: Option>, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -struct MapKeyPairs { - #[baml(map_key_repr = "pairs")] - values: HashMap, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -struct Node { - value: i64, - next: Option>, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -#[baml(rename_all = "camelCase")] -struct RenameAllUser { - full_name: String, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -struct CheckedValue { - #[baml(check(label = "positive", expr = "this > 0"))] - value: i64, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -struct AssertedValue { - #[baml(assert(label = "positive", expr = "this > 0"))] - value: i64, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -struct Unsigned32 { - value: u32, -} - -mod collision_a { - use super::*; - - #[derive(Debug, Clone, PartialEq, BamlType)] - pub struct User { - pub name: String, - } -} - -mod collision_b { - use super::*; - - #[derive(Debug, Clone, PartialEq, BamlType)] - pub struct User { - pub name: String, - } -} - -#[test] -fn doc_comments_render() { - let schema = render_schema::(RenderOptions::default()) - .expect("render failed") - .unwrap_or_default(); - - assert!(schema.contains(" // A user profile returned by the model.")); - assert!(schema.contains(" // \n // ## Notes")); - assert!(schema.contains(" // Full name as displayed in the UI.")); -} - -#[test] -fn description_override_wins() { - let schema = render_schema::(RenderOptions::default()) - .expect("render failed") - .unwrap_or_default(); - - assert!(schema.contains(" // Override description.")); - assert!(!schema.contains(" // This should be ignored.")); -} - -#[test] -fn enum_variant_descriptions_render() { - let schema = render_schema::(RenderOptions::default()) - .expect("render failed") - .unwrap_or_default(); - - assert!(schema.contains("Red: Red hot.")); -} - -#[test] -fn unit_enum_alias_parses() { - let parsed = parse_llm_output::(r#""green""#, true).expect("parse failed"); - assert_eq!(parsed.value, Color::Green); -} - -#[test] -fn data_enum_tagged_parses() { - let raw = r#"{ "type": "Circle", "radius": 2.5 }"#; - let parsed = parse_llm_output::(raw, true).expect("parse failed"); - assert_eq!(parsed.value, Shape::Circle { radius: 2.5 }); -} - -#[test] -fn data_enum_docs_render() { - let schema = render_schema::(RenderOptions::default()) - .expect("render failed") - .unwrap_or_default(); - - assert!(schema.contains("// A circle, defined by its radius.")); - assert!(schema.contains("// Radius in meters.")); -} - -#[test] -fn int_repr_string_parses() { - let raw = r#"{ "id": "18446744073709551615" }"#; - let parsed = parse_llm_output::(raw, true).expect("parse failed"); - assert_eq!(parsed.value.id, u64::MAX); -} - -#[test] -fn int_repr_option_parses() { - let raw = r#"{ "id": 42 }"#; - let parsed = parse_llm_output::(raw, true).expect("parse failed"); - assert_eq!(parsed.value.id, Some(42)); - - let raw = r#"{ "id": null }"#; - let parsed = parse_llm_output::(raw, true).expect("parse failed"); - assert_eq!(parsed.value.id, None); -} - -#[test] -fn map_key_repr_string_parses() { - let raw = r#"{ "values": { "1": "a", "2": "b" } }"#; - let parsed = parse_llm_output::(raw, true).expect("parse failed"); - let mut expected = HashMap::new(); - expected.insert(1_u32, "a".to_string()); - expected.insert(2_u32, "b".to_string()); - assert_eq!(parsed.value.values, expected); -} - -#[test] -fn map_key_repr_option_parses() { - let raw = r#"{ "values": { "10": "x" } }"#; - let parsed = parse_llm_output::(raw, true).expect("parse failed"); - let mut expected = HashMap::new(); - expected.insert(10_u32, "x".to_string()); - assert_eq!(parsed.value.values, Some(expected)); -} - -#[test] -fn map_key_repr_pairs_parses() { - let raw = r#"{ "values": [ { "key": 1, "value": "a" }, { "key": 2, "value": "b" } ] }"#; - let parsed = parse_llm_output::(raw, true).expect("parse failed"); - let mut expected = HashMap::new(); - expected.insert(1_u32, "a".to_string()); - expected.insert(2_u32, "b".to_string()); - assert_eq!(parsed.value.values, expected); -} - -#[test] -fn map_key_repr_pairs_registers_entry_class() { - let entry_name = format!("{}::values__Entry", MapKeyPairs::baml_internal_name()); - let of = MapKeyPairs::baml_output_format(); - let class = of - .classes - .get(&(entry_name, StreamingMode::NonStreaming)) - .expect("entry class missing"); - - assert_eq!(class.name.rendered_name(), "valuesEntry"); -} - -#[test] -fn recursion_is_detected() { - let of = Node::baml_output_format(); - assert!(of.recursive_classes.contains(Node::baml_internal_name())); -} - -#[test] -fn rename_all_applies() { - let raw = r#"{ "fullName": "Ada" }"#; - let parsed = parse_llm_output::(raw, true).expect("parse failed"); - assert_eq!(parsed.value.full_name, "Ada"); -} - -#[test] -fn constraint_checks_are_reported() { - let raw = r#"{ "value": 3 }"#; - let parsed = parse_llm_output::(raw, true).expect("parse failed"); - assert!(parsed - .checks - .iter() - .any(|check| check.name == "positive" && check.status == "succeeded")); -} - -#[test] -fn constraint_asserts_fail() { - let raw = r#"{ "value": -1 }"#; - let err = parse_llm_output::(raw, true).expect_err("expected assert failure"); - match err { - BamlParseError::ConstraintAssertsFailed { failed } => { - assert!(failed.iter().any(|check| check.name == "positive")); - } - other => panic!("unexpected error: {other:?}"), - } -} - -#[test] -fn unsigned_bounds_enforced() { - let raw = r#"{ "value": -1 }"#; - let err = parse_llm_output::(raw, true).expect_err("expected range error"); - assert!(matches!(err, BamlParseError::Convert(_))); - - let raw = r#"{ "value": 4294967296 }"#; - let err = parse_llm_output::(raw, true).expect_err("expected range error"); - assert!(matches!(err, BamlParseError::Convert(_))); -} - -#[test] -fn internal_names_are_unique() { - let a_name = collision_a::User::baml_internal_name(); - let b_name = collision_b::User::baml_internal_name(); - assert_ne!(a_name, b_name); - - let a_class = collision_a::User::baml_output_format() - .classes - .get(&(a_name.to_string(), StreamingMode::NonStreaming)) - .expect("class missing"); - let b_class = collision_b::User::baml_output_format() - .classes - .get(&(b_name.to_string(), StreamingMode::NonStreaming)) - .expect("class missing"); - - assert_eq!(a_class.name.rendered_name(), "User"); - assert_eq!(b_class.name.rendered_name(), "User"); -} diff --git a/crates/baml-bridge/tests/property.rs b/crates/baml-bridge/tests/property.rs deleted file mode 100644 index 5908314c..00000000 --- a/crates/baml-bridge/tests/property.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::collections::HashMap; - -use baml_bridge::{parse_llm_output, BamlType}; -use proptest::collection::{hash_map, vec}; -use proptest::prelude::*; -use proptest::string::string_regex; - -#[derive(Debug, Clone, PartialEq, BamlType, serde::Serialize, serde::Deserialize)] -struct RoundTripUser { - name: String, - age: u32, - active: bool, - tags: Vec, - meta: HashMap, - nickname: Option, -} - -fn arb_string() -> impl Strategy { - string_regex("[a-zA-Z0-9 _-]{0,12}").expect("valid regex") -} - -fn arb_user() -> impl Strategy { - ( - arb_string(), - 0_u32..1000_u32, - any::(), - vec(arb_string(), 0..5), - hash_map(arb_string(), -1000_i64..1000_i64, 0..5), - proptest::option::of(arb_string()), - ) - .prop_map(|(name, age, active, tags, meta, nickname)| RoundTripUser { - name, - age, - active, - tags, - meta, - nickname, - }) -} - -proptest! { - #![proptest_config(ProptestConfig { cases: 64, .. ProptestConfig::default() })] - - #[test] - fn round_trip_user(user in arb_user()) { - let json = serde_json::to_string(&user).expect("serialize"); - let parsed = parse_llm_output::(&json, true).expect("parse"); - prop_assert_eq!(parsed.value, user); - } -} diff --git a/crates/baml-bridge/tests/to_value_roundtrip.rs b/crates/baml-bridge/tests/to_value_roundtrip.rs deleted file mode 100644 index 09df8a8b..00000000 --- a/crates/baml-bridge/tests/to_value_roundtrip.rs +++ /dev/null @@ -1,168 +0,0 @@ -use std::collections::{BTreeMap, HashMap}; - -use baml_bridge::baml_types::BamlValue; -use baml_bridge::{BamlType, BamlValueConvert, ToBamlValue}; - -#[derive(Debug, Clone, PartialEq, BamlType)] -struct RoundtripStruct { - name: String, - count: i32, - tags: Vec, - meta: Option, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -enum UnitEnum { - Alpha, - #[baml(alias = "beta")] - Beta, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -#[baml(tag = "kind")] -enum DataEnum { - Message { body: String, count: i64 }, - Empty, -} - -#[derive(Debug, Clone, PartialEq, BamlType)] -struct NestedStruct { - title: String, - items: Option>, - metadata: HashMap>, -} - -#[test] -fn roundtrip_primitives() { - let samples = ["", "hello", "with spaces"]; - for value in samples { - let baml = value.to_baml_value(); - let recovered = String::try_from_baml_value(baml, Vec::new()).expect("string roundtrip"); - assert_eq!(value, recovered); - } - - for value in [0i32, 1, -1, i32::MAX, i32::MIN] { - let baml = value.to_baml_value(); - let recovered = i32::try_from_baml_value(baml, Vec::new()).expect("i32 roundtrip"); - assert_eq!(value, recovered); - } - - for value in [0.0f64, 1.25, -2.5] { - let baml = value.to_baml_value(); - let recovered = f64::try_from_baml_value(baml, Vec::new()).expect("f64 roundtrip"); - assert_eq!(value, recovered); - } - - for value in [0.5f32, 2.25, -3.0] { - let baml = value.to_baml_value(); - let recovered = f32::try_from_baml_value(baml, Vec::new()).expect("f32 roundtrip"); - assert_eq!(value, recovered); - } - - let value = true; - let baml = value.to_baml_value(); - let recovered = bool::try_from_baml_value(baml, Vec::new()).expect("bool roundtrip"); - assert_eq!(value, recovered); -} - -#[test] -fn roundtrip_containers() { - let some: Option = Some(42); - let baml = some.to_baml_value(); - let recovered = Option::::try_from_baml_value(baml, Vec::new()).expect("option some"); - assert_eq!(some, recovered); - - let none: Option = None; - let baml = none.to_baml_value(); - let recovered = Option::::try_from_baml_value(baml, Vec::new()).expect("option none"); - assert_eq!(none, recovered); - - let list = vec!["a".to_string(), "b".to_string()]; - let baml = list.to_baml_value(); - let recovered = Vec::::try_from_baml_value(baml, Vec::new()).expect("vec roundtrip"); - assert_eq!(list, recovered); - - let mut map = HashMap::new(); - map.insert("answer".to_string(), 42i32); - let baml = map.to_baml_value(); - let recovered = - HashMap::::try_from_baml_value(baml, Vec::new()).expect("hashmap roundtrip"); - assert_eq!(map, recovered); - - let mut tree = BTreeMap::new(); - tree.insert("left".to_string(), 1i64); - tree.insert("right".to_string(), 2i64); - let baml = tree.to_baml_value(); - let recovered = - BTreeMap::::try_from_baml_value(baml, Vec::new()).expect("btreemap roundtrip"); - assert_eq!(tree, recovered); -} - -#[test] -fn roundtrip_struct() { - let value = RoundtripStruct { - name: "example".to_string(), - count: 7, - tags: vec!["tag".to_string()], - meta: Some("meta".to_string()), - }; - let baml = value.to_baml_value(); - let recovered = ::try_from_baml_value(baml, Vec::new()) - .expect("struct roundtrip"); - assert_eq!(value, recovered); -} - -#[test] -fn roundtrip_unit_enum() { - let value = UnitEnum::Beta; - let baml = value.to_baml_value(); - match &baml { - BamlValue::Enum(_, variant) => assert_eq!(variant, "beta"), - other => panic!("expected enum value, got {other:?}"), - } - let recovered = ::try_from_baml_value(baml, Vec::new()) - .expect("enum roundtrip"); - assert_eq!(value, recovered); -} - -#[test] -fn roundtrip_data_enum() { - let value = DataEnum::Message { - body: "hello".to_string(), - count: 3, - }; - let baml = value.to_baml_value(); - match &baml { - BamlValue::Class(_, map) | BamlValue::Map(map) => match map.get("kind") { - Some(BamlValue::String(tag)) => assert_eq!(tag, "Message"), - other => panic!("expected tag field, got {other:?}"), - }, - other => panic!("expected class value, got {other:?}"), - } - let recovered = ::try_from_baml_value(baml, Vec::new()) - .expect("data enum roundtrip"); - assert_eq!(value, recovered); -} - -#[test] -fn roundtrip_nested() { - let mut metadata = HashMap::new(); - metadata.insert("alpha".to_string(), Some(1)); - metadata.insert("beta".to_string(), None); - - let value = NestedStruct { - title: "nested".to_string(), - items: Some(vec![RoundtripStruct { - name: "child".to_string(), - count: 2, - tags: vec!["x".to_string(), "y".to_string()], - meta: None, - }]), - metadata, - }; - - let baml = value.to_baml_value(); - let recovered = ::try_from_baml_value(baml, Vec::new()) - .expect("nested roundtrip"); - assert_eq!(value, recovered); -} diff --git a/crates/baml-bridge/tests/ui.rs b/crates/baml-bridge/tests/ui.rs deleted file mode 100644 index 365834bd..00000000 --- a/crates/baml-bridge/tests/ui.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[test] -fn ui_compile_failures() { - let t = trybuild::TestCases::new(); - t.compile_fail("tests/ui/*.rs"); -} diff --git a/crates/baml-bridge/tests/ui/as_enum_data_enum.rs b/crates/baml-bridge/tests/ui/as_enum_data_enum.rs deleted file mode 100644 index 1a11d419..00000000 --- a/crates/baml-bridge/tests/ui/as_enum_data_enum.rs +++ /dev/null @@ -1,9 +0,0 @@ -use baml_bridge::BamlType; - -#[derive(BamlType)] -#[baml(as_enum)] -enum Bad { - A { value: i64 }, -} - -fn main() {} diff --git a/crates/baml-bridge/tests/ui/as_enum_data_enum.stderr b/crates/baml-bridge/tests/ui/as_enum_data_enum.stderr deleted file mode 100644 index 9ae230f7..00000000 --- a/crates/baml-bridge/tests/ui/as_enum_data_enum.stderr +++ /dev/null @@ -1,8 +0,0 @@ -error: as_enum is only valid for unit enums; hint: remove #[baml(as_enum)] or convert variants to unit - --> tests/ui/as_enum_data_enum.rs:4:1 - | -4 | / #[baml(as_enum)] -5 | | enum Bad { -6 | | A { value: i64 }, -7 | | } - | |_^ diff --git a/crates/baml-bridge/tests/ui/function_type.rs b/crates/baml-bridge/tests/ui/function_type.rs deleted file mode 100644 index 98059c01..00000000 --- a/crates/baml-bridge/tests/ui/function_type.rs +++ /dev/null @@ -1,8 +0,0 @@ -use baml_bridge::BamlType; - -#[derive(BamlType)] -struct Bad { - callback: fn(i32) -> i32, -} - -fn main() {} diff --git a/crates/baml-bridge/tests/ui/function_type.stderr b/crates/baml-bridge/tests/ui/function_type.stderr deleted file mode 100644 index 56869b0f..00000000 --- a/crates/baml-bridge/tests/ui/function_type.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: function types are not supported in BAML outputs; hint: remove the field or use #[baml(with = "...")] to adapt it - --> tests/ui/function_type.rs:5:15 - | -5 | callback: fn(i32) -> i32, - | ^^^^^^^^^^^^^^ diff --git a/crates/baml-bridge/tests/ui/large_int_without_repr.rs b/crates/baml-bridge/tests/ui/large_int_without_repr.rs deleted file mode 100644 index 7cf74301..00000000 --- a/crates/baml-bridge/tests/ui/large_int_without_repr.rs +++ /dev/null @@ -1,8 +0,0 @@ -use baml_bridge::BamlType; - -#[derive(BamlType)] -struct Bad { - value: u64, -} - -fn main() {} diff --git a/crates/baml-bridge/tests/ui/large_int_without_repr.stderr b/crates/baml-bridge/tests/ui/large_int_without_repr.stderr deleted file mode 100644 index 9d62076e..00000000 --- a/crates/baml-bridge/tests/ui/large_int_without_repr.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: unsupported integer width for BAML outputs; hint: use #[baml(int_repr = "string"|"i64")] or a smaller integer type - --> tests/ui/large_int_without_repr.rs:5:12 - | -5 | value: u64, - | ^^^ diff --git a/crates/baml-bridge/tests/ui/map_key_non_string.rs b/crates/baml-bridge/tests/ui/map_key_non_string.rs deleted file mode 100644 index 5ab35542..00000000 --- a/crates/baml-bridge/tests/ui/map_key_non_string.rs +++ /dev/null @@ -1,9 +0,0 @@ -use baml_bridge::BamlType; -use std::collections::HashMap; - -#[derive(BamlType)] -struct Bad { - values: HashMap, -} - -fn main() {} diff --git a/crates/baml-bridge/tests/ui/map_key_non_string.stderr b/crates/baml-bridge/tests/ui/map_key_non_string.stderr deleted file mode 100644 index 53044558..00000000 --- a/crates/baml-bridge/tests/ui/map_key_non_string.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: map keys must be String for object maps; hint: use HashMap or add #[baml(map_key_repr = "string"|"pairs")], or use a custom adapter - --> tests/ui/map_key_non_string.rs:6:13 - | -6 | values: HashMap, - | ^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/baml-bridge/tests/ui/map_key_repr_non_map.rs b/crates/baml-bridge/tests/ui/map_key_repr_non_map.rs deleted file mode 100644 index 61c949d8..00000000 --- a/crates/baml-bridge/tests/ui/map_key_repr_non_map.rs +++ /dev/null @@ -1,9 +0,0 @@ -use baml_bridge::BamlType; - -#[derive(BamlType)] -struct Bad { - #[baml(map_key_repr = "string")] - value: Vec, -} - -fn main() {} diff --git a/crates/baml-bridge/tests/ui/map_key_repr_non_map.stderr b/crates/baml-bridge/tests/ui/map_key_repr_non_map.stderr deleted file mode 100644 index e91f3f89..00000000 --- a/crates/baml-bridge/tests/ui/map_key_repr_non_map.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: map_key_repr only applies to map fields (HashMap/BTreeMap), optionally wrapped in Option/Vec/Box/Arc/Rc; hint: remove the attribute or change the field type - --> tests/ui/map_key_repr_non_map.rs:6:16 - | -6 | value: Vec, - | ^^^^^^ diff --git a/crates/baml-bridge/tests/ui/serde_default_path.rs b/crates/baml-bridge/tests/ui/serde_default_path.rs deleted file mode 100644 index 87d930cb..00000000 --- a/crates/baml-bridge/tests/ui/serde_default_path.rs +++ /dev/null @@ -1,13 +0,0 @@ -use baml_bridge::BamlType; - -#[derive(BamlType)] -struct Bad { - #[serde(default = "default_age")] - age: i64, -} - -fn default_age() -> i64 { - 0 -} - -fn main() {} diff --git a/crates/baml-bridge/tests/ui/serde_default_path.stderr b/crates/baml-bridge/tests/ui/serde_default_path.stderr deleted file mode 100644 index 8aaa9b07..00000000 --- a/crates/baml-bridge/tests/ui/serde_default_path.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: serde(default = "path") is not supported; hint: use #[baml(default)] or Default::default - --> tests/ui/serde_default_path.rs:5:13 - | -5 | #[serde(default = "default_age")] - | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/baml-bridge/tests/ui/serde_flatten.rs b/crates/baml-bridge/tests/ui/serde_flatten.rs deleted file mode 100644 index 16a0b502..00000000 --- a/crates/baml-bridge/tests/ui/serde_flatten.rs +++ /dev/null @@ -1,10 +0,0 @@ -use baml_bridge::BamlType; -use std::collections::HashMap; - -#[derive(BamlType)] -struct Bad { - #[serde(flatten)] - extras: HashMap, -} - -fn main() {} diff --git a/crates/baml-bridge/tests/ui/serde_flatten.stderr b/crates/baml-bridge/tests/ui/serde_flatten.stderr deleted file mode 100644 index afbab798..00000000 --- a/crates/baml-bridge/tests/ui/serde_flatten.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: serde(flatten) is not supported; hint: model fields explicitly - --> tests/ui/serde_flatten.rs:6:13 - | -6 | #[serde(flatten)] - | ^^^^^^^ diff --git a/crates/baml-bridge/tests/ui/serde_json_value.rs b/crates/baml-bridge/tests/ui/serde_json_value.rs deleted file mode 100644 index 942abae1..00000000 --- a/crates/baml-bridge/tests/ui/serde_json_value.rs +++ /dev/null @@ -1,8 +0,0 @@ -use baml_bridge::BamlType; - -#[derive(BamlType)] -struct Bad { - value: serde_json::Value, -} - -fn main() {} diff --git a/crates/baml-bridge/tests/ui/serde_json_value.stderr b/crates/baml-bridge/tests/ui/serde_json_value.stderr deleted file mode 100644 index ee40cf12..00000000 --- a/crates/baml-bridge/tests/ui/serde_json_value.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: serde_json::Value is not supported without a #[baml(with = "...")] adapter; hint: use a concrete type or provide a custom adapter - --> tests/ui/serde_json_value.rs:5:12 - | -5 | value: serde_json::Value, - | ^^^^^^^^^^^^^^^^^ diff --git a/crates/baml-bridge/tests/ui/serde_skip_variant.rs b/crates/baml-bridge/tests/ui/serde_skip_variant.rs deleted file mode 100644 index 23c2feba..00000000 --- a/crates/baml-bridge/tests/ui/serde_skip_variant.rs +++ /dev/null @@ -1,10 +0,0 @@ -use baml_bridge::BamlType; - -#[derive(BamlType)] -enum Bad { - #[serde(skip)] - A, - B, -} - -fn main() {} diff --git a/crates/baml-bridge/tests/ui/serde_skip_variant.stderr b/crates/baml-bridge/tests/ui/serde_skip_variant.stderr deleted file mode 100644 index 98818a2f..00000000 --- a/crates/baml-bridge/tests/ui/serde_skip_variant.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: serde(skip) is not supported on enum variants; hint: remove the variant or use a separate enum - --> tests/ui/serde_skip_variant.rs:5:13 - | -5 | #[serde(skip)] - | ^^^^ diff --git a/crates/baml-bridge/tests/ui/serde_untagged.rs b/crates/baml-bridge/tests/ui/serde_untagged.rs deleted file mode 100644 index 4802f0a0..00000000 --- a/crates/baml-bridge/tests/ui/serde_untagged.rs +++ /dev/null @@ -1,10 +0,0 @@ -use baml_bridge::BamlType; - -#[derive(BamlType)] -#[serde(untagged)] -enum Bad { - A { x: i64 }, - B { y: i64 }, -} - -fn main() {} diff --git a/crates/baml-bridge/tests/ui/serde_untagged.stderr b/crates/baml-bridge/tests/ui/serde_untagged.stderr deleted file mode 100644 index ab19cbb0..00000000 --- a/crates/baml-bridge/tests/ui/serde_untagged.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: serde(untagged) is not supported; hint: use #[baml(tag = "...")] for data enums - --> tests/ui/serde_untagged.rs:4:9 - | -4 | #[serde(untagged)] - | ^^^^^^^^ diff --git a/crates/baml-bridge/tests/ui/trait_object.rs b/crates/baml-bridge/tests/ui/trait_object.rs deleted file mode 100644 index 0628674f..00000000 --- a/crates/baml-bridge/tests/ui/trait_object.rs +++ /dev/null @@ -1,8 +0,0 @@ -use baml_bridge::BamlType; - -#[derive(BamlType)] -struct Bad { - value: Box, -} - -fn main() {} diff --git a/crates/baml-bridge/tests/ui/trait_object.stderr b/crates/baml-bridge/tests/ui/trait_object.stderr deleted file mode 100644 index a1ca7d07..00000000 --- a/crates/baml-bridge/tests/ui/trait_object.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: trait objects are not supported in BAML outputs; hint: use a concrete type or a custom adapter - --> tests/ui/trait_object.rs:5:16 - | -5 | value: Box, - | ^^^^^^^^^^^^^^^^^^^ diff --git a/crates/baml-bridge/tests/ui/tuple_enum_variant.rs b/crates/baml-bridge/tests/ui/tuple_enum_variant.rs deleted file mode 100644 index d45b9751..00000000 --- a/crates/baml-bridge/tests/ui/tuple_enum_variant.rs +++ /dev/null @@ -1,8 +0,0 @@ -use baml_bridge::BamlType; - -#[derive(BamlType)] -enum Bad { - One(u32), -} - -fn main() {} diff --git a/crates/baml-bridge/tests/ui/tuple_enum_variant.stderr b/crates/baml-bridge/tests/ui/tuple_enum_variant.stderr deleted file mode 100644 index d4aeeff0..00000000 --- a/crates/baml-bridge/tests/ui/tuple_enum_variant.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: Tuple enum variants are not supported; hint: use a unit or struct-like variant - --> tests/ui/tuple_enum_variant.rs:5:5 - | -5 | One(u32), - | ^^^^^^^^ diff --git a/crates/baml-bridge/tests/ui/tuple_struct.rs b/crates/baml-bridge/tests/ui/tuple_struct.rs deleted file mode 100644 index 21848f6b..00000000 --- a/crates/baml-bridge/tests/ui/tuple_struct.rs +++ /dev/null @@ -1,6 +0,0 @@ -use baml_bridge::BamlType; - -#[derive(BamlType)] -struct Bad(u32); - -fn main() {} diff --git a/crates/baml-bridge/tests/ui/tuple_struct.stderr b/crates/baml-bridge/tests/ui/tuple_struct.stderr deleted file mode 100644 index 4aff7977..00000000 --- a/crates/baml-bridge/tests/ui/tuple_struct.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: Tuple structs are not supported for BAML outputs; hint: use a named-field struct - --> tests/ui/tuple_struct.rs:4:1 - | -4 | struct Bad(u32); - | ^^^^^^^^^^^^^^^^ diff --git a/crates/baml-bridge/tests/ui/unit_struct.rs b/crates/baml-bridge/tests/ui/unit_struct.rs deleted file mode 100644 index 568c922f..00000000 --- a/crates/baml-bridge/tests/ui/unit_struct.rs +++ /dev/null @@ -1,6 +0,0 @@ -use baml_bridge::BamlType; - -#[derive(BamlType)] -struct Bad; - -fn main() {} diff --git a/crates/baml-bridge/tests/ui/unit_struct.stderr b/crates/baml-bridge/tests/ui/unit_struct.stderr deleted file mode 100644 index 7504516d..00000000 --- a/crates/baml-bridge/tests/ui/unit_struct.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: Unit structs are not supported for BAML outputs; hint: use a named-field struct or enum - --> tests/ui/unit_struct.rs:4:1 - | -4 | struct Bad; - | ^^^^^^^^^^^ diff --git a/crates/bamltype-derive/src/lib.rs b/crates/bamltype-derive/src/lib.rs index fef235ec..cedaa9a4 100644 --- a/crates/bamltype-derive/src/lib.rs +++ b/crates/bamltype-derive/src/lib.rs @@ -717,7 +717,7 @@ fn normalize_field_attrs( if compat.skip { out.push(syn::parse_quote!(#[facet(skip)])); } - // Keep baml-bridge compatibility: skipped fields deserialize from Default. + // Keep legacy bridge compatibility: skipped fields deserialize from Default. if compat.default || compat.skip { out.push(syn::parse_quote!(#[facet(default)])); } diff --git a/crates/bamltype/src/compat.rs b/crates/bamltype/src/compat.rs index d229113c..b8376cd5 100644 --- a/crates/bamltype/src/compat.rs +++ b/crates/bamltype/src/compat.rs @@ -1,4 +1,4 @@ -//! Compatibility layer providing baml-bridge-compatible traits and helpers. +//! Compatibility layer providing legacy bridge-compatible traits and helpers. use std::collections::{HashMap, HashSet}; @@ -11,7 +11,7 @@ use crate::BamlSchema; use crate::convert; use crate::schema_builder::build_type_ir_from_shape; -/// Error during BamlValue ↔ Rust conversion (baml-bridge compatible). +/// Error during BamlValue ↔ Rust conversion (legacy bridge compatible). #[derive(Debug, Clone)] pub struct BamlConvertError { pub path: Vec, @@ -78,7 +78,7 @@ impl From for BamlConvertError { } } -/// Registry for schema elements (baml-bridge compatible). +/// Registry for schema elements (legacy bridge compatible). #[derive(Debug, Default)] pub struct Registry { enums: IndexMap, @@ -149,19 +149,19 @@ impl Registry { } } -/// Internal type metadata (baml-bridge compatible). +/// Internal type metadata (legacy bridge compatible). pub trait BamlTypeInternal { fn baml_internal_name() -> &'static str; fn baml_type_ir() -> TypeIR; fn register(_reg: &mut Registry) {} } -/// Convert from BamlValue to Rust (baml-bridge compatible). +/// Convert from BamlValue to Rust (legacy bridge compatible). pub trait BamlValueConvert: Sized { fn try_from_baml_value(value: BamlValue, path: Vec) -> Result; } -/// Convert from Rust to BamlValue (baml-bridge compatible). +/// Convert from Rust to BamlValue (legacy bridge compatible). pub trait ToBamlValue { fn to_baml_value(&self) -> BamlValue; } @@ -173,7 +173,7 @@ pub trait BamlAdapter { fn try_from_baml(value: BamlValue, path: Vec) -> Result; } -/// Full BamlType trait (baml-bridge compatible). +/// Full BamlType trait (legacy bridge compatible). /// /// Named `BamlTypeTrait` to avoid collision with the `#[BamlType]` attribute macro. pub trait BamlTypeTrait: BamlTypeInternal + BamlValueConvert + Sized + 'static { @@ -230,18 +230,18 @@ impl BamlTypeTrait for T { } } -/// Add constraints to a TypeIR (baml-bridge compatible). +/// Add constraints to a TypeIR (legacy bridge compatible). pub fn with_constraints(mut type_ir: TypeIR, constraints: Vec) -> TypeIR { type_ir.meta_mut().constraints.extend(constraints); type_ir } -/// Default streaming behavior helper (baml-bridge compatible). +/// Default streaming behavior helper (legacy bridge compatible). pub fn default_streaming_behavior() -> type_meta::base::StreamingBehavior { type_meta::base::StreamingBehavior::default() } -/// Lookup helper matching baml-bridge semantics (`name` then optional alias). +/// Lookup helper matching legacy bridge semantics (`name` then optional alias). pub fn get_field<'a>( map: &'a BamlMap, name: &str, diff --git a/crates/bamltype/src/lib.rs b/crates/bamltype/src/lib.rs index a7da9ebc..3a809aa8 100644 --- a/crates/bamltype/src/lib.rs +++ b/crates/bamltype/src/lib.rs @@ -33,7 +33,7 @@ use jsonish::deserializer::{ }; use sha2::{Digest, Sha256}; -// Re-export underlying crates for consumers (replaces baml-bridge re-exports) +// Re-export underlying crates for consumers (replaces legacy bridge re-exports) pub use baml_types; pub use internal_baml_jinja; pub use internal_baml_jinja::types::{HoistClasses, MapStyle, RenderOptions}; @@ -91,7 +91,7 @@ pub trait BamlSchema: for<'a> facet::Facet<'a> { fn baml_schema() -> &'static SchemaBundle; } -/// Back-compat trait mirroring baml-bridge's `BamlType`. +/// 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. @@ -117,7 +117,7 @@ impl BamlType for T { } } -/// Parsed output bundle matching baml-bridge behavior. +/// Parsed output bundle matching legacy bridge behavior. #[derive(Debug, Clone)] pub struct Parsed { pub value: T, @@ -153,7 +153,7 @@ pub enum ParseError { CoercionError(String), } -/// Render the BAML schema for a type, matching baml-bridge signature. +/// Render the BAML schema for a type, matching legacy bridge signature. pub fn render_schema( options: RenderOptions, ) -> Result, minijinja::Error> { diff --git a/crates/bamltype/src/schema_builder.rs b/crates/bamltype/src/schema_builder.rs index 8b38aeac..c48b7b76 100644 --- a/crates/bamltype/src/schema_builder.rs +++ b/crates/bamltype/src/schema_builder.rs @@ -51,7 +51,7 @@ pub fn internal_name_for_shape(shape: &'static Shape) -> String { /// /// Facet currently stores container rename in `Shape::rename` for some type kinds, /// but for others it may only be present in builtin attrs. Prefer the explicit -/// shape field, then fall back to builtin attr lookup for parity with baml-bridge. +/// shape field, then fall back to builtin attr lookup for parity with legacy bridge. fn rendered_name_for_shape(shape: &'static Shape) -> String { if shape.rename.is_some() { return shape.effective_name().to_string(); diff --git a/docs/adr/2026-01-09-baml-bridge-typed-api-integration.md b/docs/adr/2026-01-09-baml-bridge-typed-api-integration.md index e0d2bec0..517addc4 100644 --- a/docs/adr/2026-01-09-baml-bridge-typed-api-integration.md +++ b/docs/adr/2026-01-09-baml-bridge-typed-api-integration.md @@ -1,11 +1,15 @@ -# ADR-001: BAML-Bridge Typed DSPy-RS Integration Decisions +# ADR-001: legacy bridge Typed DSPy-RS Integration Decisions Status: Accepted Date: 2026-01-09 +Update (2026-02-08): The legacy bridge crates referenced in this ADR are removed from the workspace. +This ADR remains historical context for prior migration decisions. +Phase 2 follow-up is planned: remove remaining compat-trait coupling and redesign signature/optimizer APIs around facet-native paths. + ## Context DSPy-RS is migrating from an untyped HashMap-based API to a typed Rust API backed by -BAML-Bridge. The integration required a set of architectural decisions to balance +legacy bridge. The integration required a set of architectural decisions to balance macro ergonomics, runtime behavior, and backward compatibility with existing modules and optimizers. @@ -112,7 +116,7 @@ Context: Spec suggested adding `to_baml_value()` to `BamlType`. Decision: Keep `ToBamlValue` as a standalone trait. Rationale: -- Avoids modifying baml-bridge core traits. +- Avoids modifying legacy bridge core traits. - Maintains clear separation of "to" vs "from" conversions. ### 9) No PromptParts Trait