array support

This commit is contained in:
Daniel Bulant 2025-07-09 21:28:08 +02:00
parent 404357fad9
commit e22fa46dc3
No known key found for this signature in database
2 changed files with 149 additions and 4 deletions

View file

@ -3,20 +3,20 @@ use quote::quote;
use syn::parse::{Parse, ParseBuffer, ParseStream};
use syn::{Error, Ident, Lit, Result, Token};
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct ConvexName {
pub path: Vec<String>,
pub id: String,
}
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct ConvexField {
pub name: ConvexName,
pub t: ConvexType,
}
// See: https://docs.convex.dev/functions/args-validation
#[derive(Clone)]
#[derive(Clone, Debug)]
pub enum ConvexType {
// Core types.
Id(String),
@ -33,6 +33,7 @@ pub enum ConvexType {
BoolLiteral(bool),
IntLiteral(i64),
// TODO: Any,
Array(Box<ConvexField>),
Optional(Box<ConvexField>),
}
@ -58,6 +59,9 @@ impl ConvexType {
| ConvexType::Optional(child) => {
child.t.print().map(|ts| quote! { Option<#ts> })
},
| ConvexType::Array(child) => {
child.t.print().map(|ts| quote! { Vec<#ts> })
}
// These depend on field.name to generate a struct name.
| ConvexType::Object(_) => None,
@ -288,6 +292,7 @@ impl ConvexField {
}
});
},
| ConvexType::Array(_) => panic!("Arrays in unions are not supported yet"),
| ConvexType::Optional(_) => panic!("Unions may not contain optional branches"),
| ConvexType::Union(_) => panic!("Unions may not directly contain other unions, put other types between them"),
@ -345,7 +350,8 @@ impl ConvexField {
}
})
},
| _ => {
| any => {
dbg!(any);
panic!(
"Internal Error: Should have a complex field like object or union"
)
@ -403,6 +409,14 @@ impl ConvexField {
rendered_fields.push(quote! {
pub #field_name: #field_type,
});
} else if let ConvexType::Array(child) = &field.t {
let struct_name = field.name.to_struct_name();
let field_type = quote! { Vec<#struct_name> };
let mut child_struct = child.print();
structs.append(&mut child_struct);
rendered_fields.push(quote! {
pub #field_name: #field_type,
});
} else {
let field_type = field.name.to_struct_name();
let mut child_struct = field.print();
@ -585,6 +599,28 @@ impl ConvexField {
};
}
},
| ConvexType::Array(next_t) => {
let next_target = Ident::new("value", Span::call_site());
let child_match = Self::print_extract_field(next_t, Some(next_target));
quote! {
let #ident = match #match_target {
| ::core::option::Option::Some(::convex::Value::Array(values)) => {
let mut result = Vec::new();
for value in values {
let value = ::core::option::Option::Some(value);
#child_match
result.push(#ident);
}
result
},
| _ => {
return Err(::anyhow::anyhow!("Expected '{}' to be an array", #error_name));
},
};
}
}
| _ => {
panic!("Unimplemented print_extract_type")
},
@ -823,6 +859,11 @@ impl ConvexField {
})))
},
| "array" => {
let t = Self::parse_validator_call(name, &inner)?;
Ok(ConvexType::Array(Box::new(ConvexField { name: name.clone(), t })))
},
| "object" => {
let object_inner;
let _ = syn::braced!(object_inner in inner);

104
tests/array.rs Normal file
View file

@ -0,0 +1,104 @@
use convex::Value;
use maplit::btreemap;
use ragkit_convex_macros::convex_model;
use serde_json::json;
#[test]
fn basic_string_array() {
convex_model!(Model { a: v.array(v.string()) });
let convex_data = Value::Object(btreemap! {
"a".into() => Value::Array(vec![Value::String("apple".into()), Value::String("banana".into())]),
});
let json_data = json!({
"a": ["apple", "banana"],
});
let model = Model::from_convex_value(&convex_data);
assert!(model.is_ok());
let model = model.unwrap();
assert_eq!(vec!["apple".to_string(), "banana".to_string()], model.a);
assert_eq!(json_data, json!(model));
}
#[test]
fn union_array() {
convex_model!(Model { a: v.array(v.union(v.string(), v.number())) });
let convex_data = Value::Object(btreemap! {
"a".into() => Value::Array(vec![Value::String("apple".into()), Value::Float64(42.)]),
});
let json_data = json!({
"a": ["apple", 42.],
});
let model = Model::from_convex_value(&convex_data);
assert!(model.is_ok());
let model = model.unwrap();
// assert_eq!(vec!["apple".to_string(), 42], model.a);
assert_eq!(json_data, json!(model));
}
#[test]
fn union_array_objects() {
convex_model!(Model {
a: v.array(
v.union(
v.object({
typ: v.literal("http"),
hostname: v.string(),
port: v.number()
}),
v.object({
typ: v.literal("tcp"),
port: v.number()
})
)
)
});
let convex_data = Value::Object(btreemap! {
"a".into() => Value::Array(vec![
Value::Object(btreemap! {
"typ".into() => Value::String("http".into()),
"hostname".into() => Value::String("example.com".into()),
"port".into() => Value::Float64(80.0),
}),
Value::Object(btreemap! {
"typ".into() => Value::String("tcp".into()),
"port".into() => Value::Float64(8080.0),
}),
]),
});
let json_data = json!({
"a": [
{
"typ": "http",
"hostname": "example.com",
"port": 80.0,
},
{
"typ": "tcp",
"port": 8080.0,
},
],
});
let model = Model::from_convex_value(&convex_data);
assert!(model.is_ok());
let model = model.unwrap();
assert_eq!(
vec![
ModelA::Variant1(ModelAVariant1 {
typ: "http".to_string(),
hostname: "example.com".to_string(),
port: 80.0,
}),
ModelA::Variant2(ModelAVariant2 {
typ: "tcp".to_string(),
port: 8080.0,
}),
],
model.a
);
assert_eq!(json_data, json!(model));
}