Skip to main content
Glama

Convex MCP server

Official
by get-convex
lib.rs19.9 kB
#![feature(try_blocks)] #![feature(never_type)] #![feature(type_alias_impl_trait)] #![feature(iter_from_coroutine)] #![feature(iterator_try_collect)] #![feature(coroutines)] #![feature(let_chains)] #![feature(impl_trait_in_assoc_type)] mod array; pub mod base32; pub mod base64; mod bytes; mod document_id; pub mod export; mod field_name; mod field_path; pub mod id_v6; mod json; mod map; mod metrics; pub mod numeric; mod object; pub mod serde; mod set; pub mod sha256; mod size; pub mod sorting; mod string; mod table_mapping; mod table_name; pub mod walk; // Helper modules we'll eventually factor out. pub mod heap_size; pub mod utils; mod macros; #[cfg(test)] mod tests; use std::{ collections::{ BTreeMap, BTreeSet, }, fmt::{ self, Display, }, hash::{ Hash, Hasher, }, }; use anyhow::{ bail, Error, }; use heap_size::HeapSize; pub use paste::paste; pub use sync_types::identifier; use walk::ConvexValueWalker; pub use crate::{ array::ConvexArray, bytes::ConvexBytes, document_id::{ DeveloperDocumentId, InternalDocumentId, InternalId, ResolvedDocumentId, }, field_name::{ FieldName, FieldType, IdentifierFieldName, }, field_path::FieldPath, json::{ bytes::JsonBytes, float::JsonFloat, integer::JsonInteger, json_deserialize, json_packed_value::JsonPackedValue, object as json_object, value as json_value, }, map::ConvexMap, object::{ remove_boolean, remove_int64, remove_nullable_int64, remove_nullable_object, remove_nullable_string, remove_nullable_vec, remove_nullable_vec_of_strings, remove_object, remove_string, remove_vec, remove_vec_of_strings, ConvexObject, MAX_OBJECT_FIELDS, }, set::ConvexSet, size::{ check_nesting_for_documents, check_user_size, Size, MAX_DOCUMENT_NESTING, MAX_NESTING, MAX_SIZE, MAX_USER_SIZE, VALUE_TOO_LARGE_SHORT_MSG, }, sorting::values_to_bytes, string::ConvexString, table_mapping::{ NamespacedTableMapping, TableMapping, TableMappingValue, TableNamespace, }, table_name::{ TableName, TableNumber, TableType, TabletId, TabletIdAndTableNumber, METADATA_PREFIX, }, }; #[cfg(any(test, feature = "testing"))] pub mod testing { pub use sync_types::testing::assert_roundtrips; } /// The various types that can be stored as a field in a [`ConvexObject`]. #[derive(Clone, Debug)] pub enum ConvexValue { /// Sentinel `Null` value. Null, /// 64-bit signed integer. Int64(i64), /// IEEE754 double-precision floating point number with NaNs, negative zero, /// and subnormal numbers supported. Float64(f64), /// Boolean value. Boolean(bool), /// Text strings, represented as a sequence of Unicode scalar values. Scalar /// values must be encoded as codepoints without surrogate pairs: The /// sequence of codepoints may not contain *any* surrogate pairs, even /// if they are correctly paired up. String(ConvexString), /// Arbitrary binary data. Bytes(ConvexBytes), /// Arrays of (potentially heterogeneous) [`ConvexValue`]s. Array(ConvexArray), /// Set of (potentially heterogeneous) [`ConvexValue`]s. Set(ConvexSet), /// Map of (potentially heterogenous) keys and values. Map(ConvexMap), /// Nested object with [`FieldName`] keys and (potentially heterogenous) /// values. Object(ConvexObject), } impl ConvexValue { /// Returns a string description of the type of this Value. pub fn type_name(&self) -> &'static str { let Ok(ty) = self.walk(); ty.type_name() } } impl From<ConvexObject> for ConvexValue { fn from(o: ConvexObject) -> ConvexValue { ConvexValue::Object(o) } } impl From<ResolvedDocumentId> for ConvexValue { fn from(value: ResolvedDocumentId) -> Self { DeveloperDocumentId::from(value).into() } } impl From<DeveloperDocumentId> for ConvexValue { fn from(value: DeveloperDocumentId) -> Self { ConvexValue::String( value .encode() .try_into() .expect("Could not create Value::String for ID string"), ) } } impl TryFrom<ConvexValue> for DeveloperDocumentId { type Error = anyhow::Error; fn try_from(value: ConvexValue) -> Result<Self, Self::Error> { if let ConvexValue::String(s) = value { DeveloperDocumentId::decode(&s).map_err(|e| anyhow::anyhow!(e)) } else { Err(anyhow::anyhow!("Value is not an ID")) } } } impl From<i64> for ConvexValue { fn from(i: i64) -> Self { Self::Int64(i) } } impl From<f64> for ConvexValue { fn from(i: f64) -> Self { Self::Float64(i) } } impl From<bool> for ConvexValue { fn from(i: bool) -> Self { Self::Boolean(i) } } impl TryFrom<String> for ConvexValue { type Error = anyhow::Error; fn try_from(i: String) -> anyhow::Result<Self> { Ok(Self::String(ConvexString::try_from(i)?)) } } impl<T: TryInto<ConvexValue, Error = anyhow::Error>> TryFrom<Option<T>> for ConvexValue { type Error = anyhow::Error; fn try_from(v: Option<T>) -> anyhow::Result<Self> { Ok(match v { None => ConvexValue::Null, Some(v) => v.try_into()?, }) } } impl<'a> TryFrom<&'a str> for ConvexValue { type Error = anyhow::Error; fn try_from(i: &'a str) -> anyhow::Result<Self> { Ok(Self::String(i.to_owned().try_into()?)) } } impl TryFrom<Vec<u8>> for ConvexValue { type Error = anyhow::Error; fn try_from(i: Vec<u8>) -> anyhow::Result<Self> { Ok(Self::Bytes(ConvexBytes::try_from(i)?)) } } impl TryFrom<Vec<ConvexValue>> for ConvexValue { type Error = anyhow::Error; fn try_from(i: Vec<ConvexValue>) -> anyhow::Result<Self> { Ok(Self::Array(i.try_into()?)) } } impl TryFrom<BTreeSet<ConvexValue>> for ConvexValue { type Error = anyhow::Error; fn try_from(i: BTreeSet<ConvexValue>) -> anyhow::Result<Self> { Ok(Self::Set(i.try_into()?)) } } impl TryFrom<BTreeMap<ConvexValue, ConvexValue>> for ConvexValue { type Error = anyhow::Error; fn try_from(i: BTreeMap<ConvexValue, ConvexValue>) -> anyhow::Result<Self> { Ok(Self::Map(i.try_into()?)) } } impl TryFrom<BTreeMap<FieldName, ConvexValue>> for ConvexValue { type Error = anyhow::Error; fn try_from(i: BTreeMap<FieldName, ConvexValue>) -> anyhow::Result<Self> { Ok(Self::Object(i.try_into()?)) } } impl TryFrom<ConvexValue> for bool { type Error = Error; fn try_from(v: ConvexValue) -> anyhow::Result<Self> { match v { ConvexValue::Boolean(b) => Ok(b), _ => bail!("Value must be a Boolean"), } } } impl TryFrom<ConvexValue> for i64 { type Error = Error; fn try_from(v: ConvexValue) -> anyhow::Result<Self> { match v { ConvexValue::Int64(i) => Ok(i), _ => bail!("Value must be an integer"), } } } impl TryFrom<ConvexValue> for ConvexString { type Error = Error; fn try_from(v: ConvexValue) -> anyhow::Result<Self> { match v { ConvexValue::String(s) => Ok(s), _ => bail!("Value must be a string"), } } } impl TryFrom<ConvexValue> for String { type Error = Error; fn try_from(v: ConvexValue) -> anyhow::Result<Self> { Ok(ConvexString::try_from(v)?.into()) } } impl TryFrom<ConvexValue> for ConvexArray { type Error = Error; fn try_from(v: ConvexValue) -> anyhow::Result<Self> { match v { ConvexValue::Array(a) => Ok(a), _ => bail!("Value must be an Array"), } } } impl TryFrom<ConvexValue> for ConvexObject { type Error = Error; fn try_from(v: ConvexValue) -> anyhow::Result<Self> { match v { ConvexValue::Object(o) => Ok(o), _ => bail!("Value must be an Object"), } } } impl Display for ConvexValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ConvexValue::Null => write!(f, "null"), ConvexValue::Int64(n) => write!(f, "{n}"), ConvexValue::Float64(n) => write!(f, "{n:?}"), ConvexValue::Boolean(b) => write!(f, "{b:?}"), ConvexValue::String(s) => write!(f, "{s:?}"), ConvexValue::Bytes(b) => write!(f, "{b}"), ConvexValue::Array(arr) => write!(f, "{arr}"), ConvexValue::Set(set) => write!(f, "{set}"), ConvexValue::Map(map) => write!(f, "{map}"), ConvexValue::Object(m) => write!(f, "{m}"), } } } impl Size for ConvexValue { fn size(&self) -> usize { match self { ConvexValue::Null => 1, ConvexValue::Int64(_) => 1 + 8, ConvexValue::Float64(_) => 1 + 8, ConvexValue::Boolean(_) => 1, ConvexValue::String(s) => s.size(), ConvexValue::Bytes(b) => b.size(), ConvexValue::Array(arr) => arr.size(), ConvexValue::Set(set) => set.size(), ConvexValue::Map(map) => map.size(), ConvexValue::Object(m) => m.size(), } } fn nesting(&self) -> usize { match self { ConvexValue::Null => 0, ConvexValue::Int64(_) => 0, ConvexValue::Float64(_) => 0, ConvexValue::Boolean(_) => 0, ConvexValue::String(_) => 0, ConvexValue::Bytes(_) => 0, ConvexValue::Array(arr) => arr.nesting(), ConvexValue::Set(set) => set.nesting(), ConvexValue::Map(map) => map.nesting(), ConvexValue::Object(m) => m.nesting(), } } } impl HeapSize for ConvexValue { fn heap_size(&self) -> usize { match self { ConvexValue::Null => 0, ConvexValue::Int64(_) => 0, ConvexValue::Float64(_) => 0, ConvexValue::Boolean(_) => 0, ConvexValue::String(s) => s.heap_size(), ConvexValue::Bytes(b) => b.heap_size(), ConvexValue::Array(arr) => arr.heap_size(), ConvexValue::Set(set) => set.heap_size(), ConvexValue::Map(map) => map.heap_size(), ConvexValue::Object(m) => m.heap_size(), } } } /// f64 doesn't implement `hash` so we need to manually implement `hash` for /// `ConvexValue`. Must be compatible with our manual implementation of `cmp`. impl Hash for ConvexValue { fn hash<H: Hasher>(&self, h: &mut H) { match self { ConvexValue::Null => { h.write_u8(2); }, ConvexValue::Int64(i) => { h.write_u8(3); i.hash(h); }, ConvexValue::Float64(f) => { h.write_u8(4); f.to_le_bytes().hash(h); }, ConvexValue::Boolean(b) => { h.write_u8(5); b.hash(h); }, ConvexValue::String(s) => { h.write_u8(6); s.hash(h); }, ConvexValue::Bytes(b) => { h.write_u8(7); b.hash(h); }, ConvexValue::Array(a) => { h.write_u8(8); a.hash(h); }, ConvexValue::Set(s) => { h.write_u8(9); s.hash(h); }, ConvexValue::Map(m) => { h.write_u8(10); m.hash(h); }, ConvexValue::Object(o) => { h.write_u8(11); o.hash(h); }, } } } #[cfg(feature = "testing")] impl std::ops::Index<&str> for ConvexValue { type Output = ConvexValue; #[track_caller] fn index(&self, field: &str) -> &Self::Output { let ConvexValue::Object(o) = self else { panic!("indexing {field} into non-object: {self}") }; let Some(v) = o.get(field) else { panic!("field {field} missing in object: {self}") }; v } } #[cfg(feature = "testing")] impl std::ops::Index<usize> for ConvexValue { type Output = ConvexValue; #[track_caller] fn index(&self, index: usize) -> &Self::Output { let ConvexValue::Array(a) = self else { panic!("indexing {index} into non-array: {self}") }; let Some(v) = a.get(index) else { panic!("index {index} out of bounds in array: {self}") }; v } } #[cfg(test)] mod hash_tests { use std::hash::{ Hash, Hasher, }; use cmd_util::env::env_config; use proptest::prelude::*; use crate::ConvexValue; struct SaveHasher(Vec<u8>); impl Hasher for SaveHasher { fn finish(&self) -> u64 { unimplemented!() } fn write(&mut self, bytes: &[u8]) { self.0.extend_from_slice(bytes); } } proptest! { #![proptest_config(ProptestConfig { cases: 1024 * env_config("CONVEX_PROPTEST_MULTIPLIER", 1), failure_persistence: None, .. ProptestConfig::default() })] #[test] fn proptest_value_encode_for_hash( v1 in any::<ConvexValue>(), v2 in any::<ConvexValue>(), ) { let mut v1_encoded = SaveHasher(vec![]); let mut v2_encoded = SaveHasher(vec![]); v1.hash(&mut v1_encoded); v2.hash(&mut v2_encoded); assert_eq!( v1 == v2, v1_encoded.0 == v2_encoded.0 ); } } } pub trait Namespace { fn is_system(&self) -> bool; } #[cfg(any(test, feature = "testing"))] pub mod proptest { use core::f64; use proptest::prelude::*; use super::{ bytes::ConvexBytes, string::ConvexString, ConvexValue, }; use crate::field_name::FieldName; impl Arbitrary for ConvexValue { type Parameters = ( <FieldName as Arbitrary>::Parameters, ValueBranching, ExcludeSetsAndMaps, RestrictNaNs, ); type Strategy = impl Strategy<Value = ConvexValue>; fn arbitrary_with( (field_params, branching, exclude_sets_and_maps, restrict_nans): Self::Parameters, ) -> Self::Strategy { resolved_value_strategy( move || any_with::<FieldName>(field_params), branching, exclude_sets_and_maps, restrict_nans, ) } } pub fn float64_strategy() -> impl Strategy<Value = f64> { prop::num::f64::ANY | prop::num::f64::SIGNALING_NAN } #[derive(Default)] // default to include sets and maps pub struct ExcludeSetsAndMaps(pub bool); // Whether to allow any `NaN` value (e.g. negative `NaN`s) or not. // Defaults to including any valid `NaN` value. #[derive(Default)] pub struct RestrictNaNs(pub bool); pub struct ValueBranching { pub depth: usize, pub node_target: usize, pub branching: usize, } impl ValueBranching { pub fn new(depth: usize, node_target: usize, branching: usize) -> Self { Self { depth, node_target, branching, } } pub fn small() -> Self { Self::new(4, 4, 4) } pub fn medium() -> Self { Self::new(4, 32, 8) } pub fn large() -> Self { Self::new(8, 64, 16) } } impl Default for ValueBranching { fn default() -> Self { Self::medium() } } pub fn resolved_value_strategy<F, S>( field_strategy: F, branching: ValueBranching, exclude_sets_and_maps: ExcludeSetsAndMaps, restrict_nans: RestrictNaNs, ) -> impl Strategy<Value = ConvexValue> where F: Fn() -> S + 'static, S: Strategy<Value = FieldName> + 'static, { use crate::{ id_v6::DeveloperDocumentId, resolved_object_strategy, ConvexArray, ConvexMap, ConvexSet, }; // https://altsysrq.github.io/proptest-book/proptest/tutorial/recursive.html let leaf = prop_oneof![ 1 => any::<DeveloperDocumentId>() .prop_map(|id| { let s = id.encode().try_into().expect("Could not create String value from ID"); ConvexValue::String(s) }), 1 => Just(ConvexValue::Null), 1 => any::<i64>().prop_map(ConvexValue::from), 1 => (prop::num::f64::ANY | prop::num::f64::SIGNALING_NAN) .prop_map(move |f| { if restrict_nans.0 && f.is_nan() { ConvexValue::Float64(f64::NAN) } else { ConvexValue::Float64(f) } }), 1 => any::<bool>().prop_map(ConvexValue::from), 1 => any::<ConvexString>().prop_filter_map("String ID", |s| match DeveloperDocumentId::decode(&s) { Ok(_) => None, Err(_) => Some(ConvexValue::String(s)) }), 1 => any::<ConvexBytes>().prop_map(ConvexValue::Bytes), ]; let map_set_weight = if exclude_sets_and_maps.0 { 0 } else { 1 }; let ValueBranching { depth, node_target, branching, } = branching; leaf.prop_recursive( depth as u32, node_target as u32, branching as u32, move |inner| { prop_oneof![ // Manually create the strategies here rather than using the `Arbitrary` // implementations on `Array`, etc. This lets us explicitly pass `inner` // through rather than starting the `Value` strategy from // scratch at each tree level. 1 => prop::collection::vec(inner.clone(), 0..branching) .prop_filter_map("Vec wasn't a valid Convex value", |v| { ConvexArray::try_from(v).ok() }) .prop_map(ConvexValue::Array), map_set_weight => prop::collection::btree_set(inner.clone(), 0..branching) .prop_filter_map("BTreeSet wasn't a valid Convex value", |v| { ConvexSet::try_from(v).ok() }) .prop_map(ConvexValue::Set), map_set_weight => prop::collection::btree_map( inner.clone(), inner.clone(), 0..branching, ) .prop_filter_map("BTreeMap wasn't a valid Convex value", |v| { ConvexMap::try_from(v).ok() }) .prop_map(ConvexValue::Map), 1 => resolved_object_strategy(field_strategy(), inner, 0..branching) .prop_map(ConvexValue::Object), ] }, ) } } #[cfg(any(test, feature = "testing"))] pub use self::{ object::resolved_object_strategy, proptest::resolved_value_strategy, proptest::ExcludeSetsAndMaps, };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get-convex/convex-backend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server