diff --git a/docs/docs/snippets/supported-types.mdx b/docs/docs/snippets/supported-types.mdx index 2f8456fce..d19bfebf0 100644 --- a/docs/docs/snippets/supported-types.mdx +++ b/docs/docs/snippets/supported-types.mdx @@ -217,12 +217,12 @@ A mapping of strings to elements of another type. **Example**: `map` - +{/* For TS users: `map` will generate a `Record` type annotation, but using any other type for the key will generate a `Map`, e.g. `map` in BAML will generate a `Map` type annotation in TypeScript. - + */} ### ❌ Set diff --git a/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs b/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs index bc487e8b1..bb48d0dae 100644 --- a/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs +++ b/engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs @@ -1,7 +1,5 @@ use baml_types::{BamlMap, BamlMediaType, BamlValue, FieldType, TypeValue}; use core::result::Result; -use log::kv; -use std::ops::Deref; use crate::ir::IntermediateRepr; @@ -68,7 +66,7 @@ pub fn validate_arg( .and_then(|v| v.as_str()) .unwrap_or_default() .to_string(); - Some(BamlValue::Media(baml_types::BamlMedia::url( + Ok(BamlValue::Media(baml_types::BamlMedia::url( BamlMediaType::Image, s.to_string(), Some(media_type_str), @@ -79,7 +77,7 @@ pub fn validate_arg( .and_then(|v| v.as_str()) .unwrap_or_default() .to_string(); - Some(BamlValue::Media(baml_types::BamlMedia::base64( + Ok(BamlValue::Media(baml_types::BamlMedia::base64( BamlMediaType::Image, s.to_string(), media_type_str, @@ -106,7 +104,7 @@ pub fn validate_arg( .and_then(|v| v.as_str()) .unwrap_or_default() .to_string(); - Some(BamlValue::Media(baml_types::BamlMedia::url( + Ok(BamlValue::Media(baml_types::BamlMedia::url( BamlMediaType::Audio, s.to_string(), Some(media_type_str), @@ -117,7 +115,7 @@ pub fn validate_arg( .and_then(|v| v.as_str()) .unwrap_or_default() .to_string(); - Some(BamlValue::Media(baml_types::BamlMedia::base64( + Ok(BamlValue::Media(baml_types::BamlMedia::base64( BamlMediaType::Audio, s.to_string(), media_type_str, diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations.rs index e18bf6621..df3a3da89 100644 --- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations.rs +++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations.rs @@ -1,10 +1,10 @@ mod classes; mod clients; -mod common; mod configurations; mod cycle; mod enums; mod functions; +mod types; mod variants; use super::context::Context; diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/classes.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/classes.rs index ef9370875..946963d63 100644 --- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/classes.rs +++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/classes.rs @@ -1,6 +1,6 @@ use crate::validate::validation_pipeline::context::Context; -use super::common::validate_type_exists; +use super::types::validate_type; pub(super) fn validate(ctx: &mut Context<'_>) { for cls in ctx.db.walk_classes() { @@ -8,11 +8,11 @@ pub(super) fn validate(ctx: &mut Context<'_>) { for c in cls.static_fields() { let field = c.ast_field(); - validate_type_exists(ctx, &field.field_type); + validate_type(ctx, &field.field_type); } for c in cls.dynamic_fields() { let field = c.ast_field(); - validate_type_exists(ctx, &field.field_type); + validate_type(ctx, &field.field_type); } } } diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/common.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/common.rs deleted file mode 100644 index 1c41220fa..000000000 --- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/common.rs +++ /dev/null @@ -1,26 +0,0 @@ -use internal_baml_diagnostics::DatamodelError; -use internal_baml_schema_ast::ast::{FieldType, Identifier, WithName, WithSpan}; - -use crate::validate::validation_pipeline::context::Context; - -fn errors_with_names<'a>(ctx: &'a mut Context<'_>, idn: &Identifier) { - // Push the error with the appropriate message - ctx.push_error(DatamodelError::new_type_not_found_error( - idn.name(), - ctx.db.valid_type_names(), - idn.span().clone(), - )); -} - -pub(crate) fn validate_type_exists(ctx: &mut Context<'_>, field_type: &FieldType) { - field_type - .flat_idns() - .iter() - .for_each(|f| match ctx.db.find_type(f) { - Some(_) => {} - None => match f { - Identifier::Primitive(..) => {} - _ => errors_with_names(ctx, f), - }, - }); -} diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/functions.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/functions.rs index e066a479f..649b6d60f 100644 --- a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/functions.rs +++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/functions.rs @@ -3,13 +3,13 @@ use internal_baml_schema_ast::ast::{WithIdentifier, WithName, WithSpan}; use crate::validate::validation_pipeline::context::Context; -use super::common::validate_type_exists; +use super::types::validate_type; pub(super) fn validate(ctx: &mut Context<'_>) { for func in ctx.db.walk_old_functions() { for args in func.walk_input_args().chain(func.walk_output_args()) { let arg = args.ast_arg(); - validate_type_exists(ctx, &arg.1.field_type) + validate_type(ctx, &arg.1.field_type) } // Check if the function has multiple impls, if it does, @@ -114,7 +114,7 @@ pub(super) fn validate(ctx: &mut Context<'_>) { for func in ctx.db.walk_new_functions() { for args in func.walk_input_args().chain(func.walk_output_args()) { let arg = args.ast_arg(); - validate_type_exists(ctx, &arg.1.field_type) + validate_type(ctx, &arg.1.field_type) } // Ensure the client is correct. diff --git a/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs new file mode 100644 index 000000000..42754a94b --- /dev/null +++ b/engine/baml-lib/baml-core/src/validate/validation_pipeline/validations/types.rs @@ -0,0 +1,66 @@ +use baml_types::TypeValue; +use internal_baml_diagnostics::DatamodelError; +use internal_baml_schema_ast::ast::{FieldArity, FieldType, Identifier, WithName, WithSpan}; + +use crate::validate::validation_pipeline::context::Context; + +fn errors_with_names<'a>(ctx: &'a mut Context<'_>, idn: &Identifier) { + // Push the error with the appropriate message + ctx.push_error(DatamodelError::new_type_not_found_error( + idn.name(), + ctx.db.valid_type_names(), + idn.span().clone(), + )); +} + +/// Called for each type in the baml_src tree, validates that it is well-formed. +/// +/// Operates in two passes: +/// +/// 1. Verify that the type is resolveable (for REF types) +/// 2. Verify that the type is well-formed/allowed in the language +pub(crate) fn validate_type(ctx: &mut Context<'_>, field_type: &FieldType) { + validate_type_exists(ctx, field_type); + validate_type_allowed(ctx, field_type); +} + +fn validate_type_exists(ctx: &mut Context<'_>, field_type: &FieldType) { + field_type + .flat_idns() + .iter() + .for_each(|f| match ctx.db.find_type(f) { + Some(_) => {} + None => match f { + Identifier::Primitive(..) => {} + _ => errors_with_names(ctx, f), + }, + }); +} + +fn validate_type_allowed(ctx: &mut Context<'_>, field_type: &FieldType) { + match field_type { + FieldType::Map(kv_types, _) => { + match &kv_types.0 { + FieldType::Identifier( + FieldArity::Required, + Identifier::Primitive(TypeValue::String, _), + ) => {} + key_type => { + ctx.push_error(DatamodelError::new_validation_error( + "Maps may only have strings as keys", + key_type.span().clone(), + )); + } + } + validate_type_allowed(ctx, &kv_types.1); + // TODO:assert key_type is string or int or null + } + FieldType::Identifier(_, _) => {} + FieldType::List(field_type, _, _) => validate_type_allowed(ctx, field_type), + FieldType::Tuple(_, field_types, _) | FieldType::Union(_, field_types, _) => { + for field_type in field_types { + validate_type_allowed(ctx, field_type); + } + } + } +} diff --git a/engine/baml-lib/baml/tests/validation_files/class/map_types.baml b/engine/baml-lib/baml/tests/validation_files/class/map_types.baml index be9c63e61..da3597ad0 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/map_types.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/map_types.baml @@ -7,52 +7,58 @@ class MapDummy { class MapFields { a1 map a2 map - a2 map + a3 map b1 map b2 map b3 map + b4 map + b5 map + + c1 string | map + c2 string | map + c3 string | map } -// error: Error validating: This line is not a valid field or attribute definition. A valid class property looks like: 'myProperty string[] @description("This is a description")' -// --> class/map_types.baml:8 -// | -// 7 | class MapFields { -// 8 | a1 map -// 9 | a2 map -// | -// error: Error validating: This line is not a valid field or attribute definition. A valid class property looks like: 'myProperty string[] @description("This is a description")' -// --> class/map_types.baml:9 -// | -// 8 | a1 map -// 9 | a2 map -// 10 | a2 map -// | -// error: Error validating: This line is not a valid field or attribute definition. A valid class property looks like: 'myProperty string[] @description("This is a description")' -// --> class/map_types.baml:10 -// | -// 9 | a2 map -// 10 | a2 map -// 11 | -// | -// error: Error validating: This line is not a valid field or attribute definition. A valid class property looks like: 'myProperty string[] @description("This is a description")' +// error: Error validating: Maps may only have strings as keys // --> class/map_types.baml:12 // | // 11 | // 12 | b1 map -// 13 | b2 map // | -// error: Error validating: This line is not a valid field or attribute definition. A valid class property looks like: 'myProperty string[] @description("This is a description")' +// error: Error validating: Maps may only have strings as keys // --> class/map_types.baml:13 // | // 12 | b1 map // 13 | b2 map -// 14 | b3 map // | -// error: Error validating: This line is not a valid field or attribute definition. A valid class property looks like: 'myProperty string[] @description("This is a description")' +// error: Error validating: Maps may only have strings as keys // --> class/map_types.baml:14 // | // 13 | b2 map // 14 | b3 map -// 15 | } +// | +// error: Error validating: Maps may only have strings as keys +// --> class/map_types.baml:15 +// | +// 14 | b3 map +// 15 | b4 map +// | +// error: Error validating: Maps may only have strings as keys +// --> class/map_types.baml:16 +// | +// 15 | b4 map +// 16 | b5 map +// | +// error: Error validating: Maps may only have strings as keys +// --> class/map_types.baml:19 +// | +// 18 | c1 string | map +// 19 | c2 string | map +// | +// error: Error validating: Maps may only have strings as keys +// --> class/map_types.baml:20 +// | +// 19 | c2 string | map +// 20 | c3 string | map // | diff --git a/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml b/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml index 8c1885e90..1f1886770 100644 --- a/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml +++ b/engine/baml-lib/baml/tests/validation_files/class/secure_types.baml @@ -17,207 +17,45 @@ class ComplexTypes { o (((int | string) | bool[]), (float, double) | long_long_identifier_123) } -// error: Type `apple_pie` does not exist. Did you mean one of these: `ComplexTypes`, `float`, `bool`, `string`, `int`? +// error: Error validating: This line is not a valid field or attribute definition. A valid class property looks like: 'myProperty string[] @description("This is a description")' // --> class/secure_types.baml:3 // | // 2 | class ComplexTypes { // 3 | a {string[]: (int | bool[]) | apple_pie[][]} -// | -// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? -// --> class/secure_types.baml:4 -// | -// 3 | a {string[]: (int | bool[]) | apple_pie[][]} // 4 | b (int, {bool: string?}, (char | float)[][] | long_word_123.foobar[]) // | -// error: Type `long_word_123.foobar` does not exist. Did you mean one of these: `float`, `bool`, `ComplexTypes`, `string`, `int`? +// error: Error validating: This line is not a valid field or attribute definition. A valid class property looks like: 'myProperty string[] @description("This is a description")' // --> class/secure_types.baml:4 // | // 3 | a {string[]: (int | bool[]) | apple_pie[][]} // 4 | b (int, {bool: string?}, (char | float)[][] | long_word_123.foobar[]) -// | -// error: Type `apple123_456_pie` does not exist. Did you mean one of these: `ComplexTypes`, `float`, `bool`, `string`, `int`? -// --> class/secure_types.baml:5 -// | -// 4 | b (int, {bool: string?}, (char | float)[][] | long_word_123.foobar[]) // 5 | c apple123_456_pie | (stringer, bool[], (int | char))[] // | -// error: Type `stringer` does not exist. Did you mean one of these: `string`, `int`, `float`, `bool`, `ComplexTypes`? -// --> class/secure_types.baml:5 -// | -// 4 | b (int, {bool: string?}, (char | float)[][] | long_word_123.foobar[]) -// 5 | c apple123_456_pie | (stringer, bool[], (int | char))[] -// | -// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? -// --> class/secure_types.baml:5 -// | -// 4 | b (int, {bool: string?}, (char | float)[][] | long_word_123.foobar[]) -// 5 | c apple123_456_pie | (stringer, bool[], (int | char))[] -// | -// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? +// error: Error validating: This line is not a valid field or attribute definition. A valid class property looks like: 'myProperty string[] @description("This is a description")' // --> class/secure_types.baml:6 // | // 5 | c apple123_456_pie | (stringer, bool[], (int | char))[] // 6 | d {int[][]: ((int | float) | char[])} -// | -// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? -// --> class/secure_types.baml:7 -// | -// 6 | d {int[][]: ((int | float) | char[])} // 7 | e ((int, string | char) | ((float, double) | long[], bool)[][][]) // | -// error: Type `double` does not exist. Did you mean one of these: `bool`, `string`, `int`, `float`, `ComplexTypes`? -// --> class/secure_types.baml:7 -// | -// 6 | d {int[][]: ((int | float) | char[])} -// 7 | e ((int, string | char) | ((float, double) | long[], bool)[][][]) -// | -// error: Type `long` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? -// --> class/secure_types.baml:7 -// | -// 6 | d {int[][]: ((int | float) | char[])} -// 7 | e ((int, string | char) | ((float, double) | long[], bool)[][][]) -// | -// error: Type `VeryLongWord_With_123_Numbers` does not exist. -// --> class/secure_types.baml:8 -// | -// 7 | e ((int, string | char) | ((float, double) | long[], bool)[][][]) -// 8 | f VeryLongWord_With_123_Numbers[][][][] -// | -// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? -// --> class/secure_types.baml:9 -// | -// 8 | f VeryLongWord_With_123_Numbers[][][][] -// 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[] -// | -// error: Type `tuple_inside_tuple` does not exist. Did you mean one of these: `ComplexTypes`, `int`, `string`, `float`, `bool`? -// --> class/secure_types.baml:9 -// | -// 8 | f VeryLongWord_With_123_Numbers[][][][] -// 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[] -// | -// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? +// error: Error validating: This line is not a valid field or attribute definition. A valid class property looks like: 'myProperty string[] @description("This is a description")' // --> class/secure_types.baml:10 // | // 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[] // 10 | h (((int | string)[]) | {bool[][]: char[]}) -// | -// error: Type `apple` does not exist. Did you mean one of these: `bool`, `int`, `float`, `string`, `ComplexTypes`? -// --> class/secure_types.baml:11 -// | -// 10 | h (((int | string)[]) | {bool[][]: char[]}) -// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[] -// | -// error: Type `banana` does not exist. Did you mean one of these: `string`, `int`, `float`, `bool`, `ComplexTypes`? -// --> class/secure_types.baml:11 -// | -// 10 | h (((int | string)[]) | {bool[][]: char[]}) -// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[] -// | -// error: Type `cherry` does not exist. Did you mean one of these: `string`, `int`, `float`, `bool`, `ComplexTypes`? -// --> class/secure_types.baml:11 -// | -// 10 | h (((int | string)[]) | {bool[][]: char[]}) -// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[] -// | -// error: Type `date_fruit` does not exist. Did you mean one of these: `string`, `float`, `int`, `bool`, `ComplexTypes`? -// --> class/secure_types.baml:11 -// | -// 10 | h (((int | string)[]) | {bool[][]: char[]}) -// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[] -// | -// error: Type `eggplant_vegetable` does not exist. Did you mean one of these: `ComplexTypes`, `string`, `int`, `float`, `bool`? -// --> class/secure_types.baml:11 -// | -// 10 | h (((int | string)[]) | {bool[][]: char[]}) -// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[] -// | -// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? -// --> class/secure_types.baml:12 -// | -// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[] -// 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[]) -// | -// error: Type `double` does not exist. Did you mean one of these: `bool`, `string`, `int`, `float`, `ComplexTypes`? -// --> class/secure_types.baml:12 -// | // 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[] -// 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[]) -// | -// error: Type `long` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? -// --> class/secure_types.baml:13 -// | -// 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[]) -// 13 | k {string[]: (int | long[])} | {float[][]: double[][]} // | -// error: Type `double` does not exist. Did you mean one of these: `bool`, `string`, `int`, `float`, `ComplexTypes`? +// error: Error validating: This line is not a valid field or attribute definition. A valid class property looks like: 'myProperty string[] @description("This is a description")' // --> class/secure_types.baml:13 // | // 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[]) // 13 | k {string[]: (int | long[])} | {float[][]: double[][]} -// | -// error: Type `AlphaNumeric_123_456_789` does not exist. -// --> class/secure_types.baml:14 -// | -// 13 | k {string[]: (int | long[])} | {float[][]: double[][]} -// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[] -// | -// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`? -// --> class/secure_types.baml:14 -// | -// 13 | k {string[]: (int | long[])} | {float[][]: double[][]} // 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[] // | -// error: Type `tuple_1` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`? -// --> class/secure_types.baml:15 -// | -// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[] -// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[] -// | -// error: Type `tuple_2` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`? -// --> class/secure_types.baml:15 -// | -// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[] -// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[] -// | -// error: Type `tuple_3` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`? -// --> class/secure_types.baml:15 -// | -// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[] -// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[] -// | -// error: Type `tuple_4` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`? -// --> class/secure_types.baml:15 -// | -// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[] -// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[] -// | -// error: Type `tuple_5` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`? -// --> class/secure_types.baml:15 -// | -// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[] -// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[] -// | -// error: Type `another_key` does not exist. Did you mean one of these: `string`, `int`, `ComplexTypes`, `float`, `bool`? +// error: Error validating: This line is not a valid field or attribute definition. A valid class property looks like: 'myProperty string[] @description("This is a description")' // --> class/secure_types.baml:16 // | // 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[] // 16 | n {complex_key_type[]: {another_key: (int | string[])}} -// | -// error: Type `complex_key_type` does not exist. Did you mean one of these: `ComplexTypes`, `float`, `bool`, `int`, `string`? -// --> class/secure_types.baml:16 -// | -// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[] -// 16 | n {complex_key_type[]: {another_key: (int | string[])}} -// | -// error: Type `double` does not exist. Did you mean one of these: `bool`, `string`, `int`, `float`, `ComplexTypes`? -// --> class/secure_types.baml:17 -// | -// 16 | n {complex_key_type[]: {another_key: (int | string[])}} -// 17 | o (((int | string) | bool[]), (float, double) | long_long_identifier_123) -// | -// error: Type `long_long_identifier_123` does not exist. Did you mean `ComplexTypes`? -// --> class/secure_types.baml:17 -// | -// 16 | n {complex_key_type[]: {another_key: (int | string[])}} // 17 | o (((int | string) | bool[]), (float, double) | long_long_identifier_123) // | diff --git a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_array.rs b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_array.rs index 0c96ea380..d22496147 100644 --- a/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_array.rs +++ b/engine/baml-lib/jsonish/src/deserializer/coercer/coerce_array.rs @@ -35,7 +35,7 @@ pub(super) fn coerce_array( for (i, item) in arr.iter().enumerate() { match inner.coerce(&ctx.enter_scope(&format!("{i}")), inner, Some(item)) { Ok(v) => items.push(v), - // TODO(vbv): this seems like a bug - it causes more penalization the deeper into an array it is? + // TODO(vbv): document why we penalize in proportion to how deep into an array a parse error is Err(e) => flags.add_flag(Flag::ArrayItemParseError(i, e)), } } diff --git a/engine/baml-lib/jsonish/src/tests/mod.rs b/engine/baml-lib/jsonish/src/tests/mod.rs index 90835441f..99010f4af 100644 --- a/engine/baml-lib/jsonish/src/tests/mod.rs +++ b/engine/baml-lib/jsonish/src/tests/mod.rs @@ -8,6 +8,7 @@ mod test_basics; mod test_class; mod test_enum; mod test_lists; +mod test_maps; mod test_partials; mod test_unions; diff --git a/engine/baml-lib/jsonish/src/tests/test_lists.rs b/engine/baml-lib/jsonish/src/tests/test_lists.rs index a51451997..4b7a56a36 100644 --- a/engine/baml-lib/jsonish/src/tests/test_lists.rs +++ b/engine/baml-lib/jsonish/src/tests/test_lists.rs @@ -38,7 +38,7 @@ test_deserializer!( class Foo { a int b string - }"#, + }"#, r#"[{"a": 1, "b": "hello"}, {"a": 2, "b": "world"}]"#, FieldType::List(FieldType::Class("Foo".to_string()).into()), [{"a": 1, "b": "hello"}, {"a": 2, "b": "world"}] diff --git a/engine/baml-lib/jsonish/src/tests/test_maps.rs b/engine/baml-lib/jsonish/src/tests/test_maps.rs new file mode 100644 index 000000000..ba734ec23 --- /dev/null +++ b/engine/baml-lib/jsonish/src/tests/test_maps.rs @@ -0,0 +1,45 @@ +use super::*; + +test_deserializer!( + test_map, + "", + r#"{"a": "b"}"#, + FieldType::map(FieldType::string(), FieldType::string()).into(), + {"a": "b"} +); + +test_deserializer!( + test_map_with_quotes, + "", + r#"{"\"a\"": "\"b\""}"#, + FieldType::map(FieldType::string(), FieldType::string()).into(), + {"\"a\"": "\"b\""} +); + +test_deserializer!( + test_map_with_extra_text, + "", + r#"{"a": "b"} is the output."#, + FieldType::map(FieldType::string(), FieldType::string()).into(), + {"a": "b"} +); + +test_deserializer!( + test_map_with_invalid_extra_text, + "", + r#"{a: b} is the output."#, + FieldType::map(FieldType::string(), FieldType::string()).into(), + {"a": "b"} +); + +test_deserializer!( + test_map_with_object_values, + r#" + class Foo { + a int + b string + }"#, + r#"{first: {"a": 1, "b": "hello"}, 'second': {"a": 2, "b": "world"}}"#, + FieldType::map(FieldType::string(), FieldType::class("Foo")).into(), + {"first":{"a": 1, "b": "hello"}, "second":{"a": 2, "b": "world"}} +);