diff --git a/generators/rust/generator/src/lib.rs b/generators/rust/generator/src/lib.rs index 9226172..214818b 100644 --- a/generators/rust/generator/src/lib.rs +++ b/generators/rust/generator/src/lib.rs @@ -15,7 +15,7 @@ use syn::spanned::Spanned; use treeldr_layouts::{ distill::RdfContext, layout::{DataLayout, LayoutType, ListLayout, LiteralLayout}, - Layout, Layouts, Pattern, Ref, + Layout, Layouts, Literal, Pattern, Ref, Value, }; use utils::ident_from_iri; @@ -27,7 +27,7 @@ pub enum Error { MissingTypeIdentifier(R), #[error("invalid field identifier `{0}`")] - InvalidFieldIdent(String), + InvalidFieldIdent(Value), #[error("invalid variant identifier `{0}`")] InvalidVariantIdent(String), @@ -364,20 +364,23 @@ where let fields = layout .fields .iter() - .map(|(name, f)| { - let f_ident = syn::parse_str::(name.as_str()) - .map_err(|_| Error::InvalidFieldIdent(name.clone()))?; + .map(|(key, f)| match key { + Value::Literal(Literal::TextString(name)) => { + let f_ident = syn::parse_str::(name.as_str()) + .map_err(|_| Error::InvalidFieldIdent(key.clone()))?; - let intro = generate_intro_attribute(f.intro, layout.input + layout.intro); - let dataset = generate_dataset_attribute(rdf, &f.dataset)?; - let input = generate_value_input_attribute(rdf, &f.value.input)?; - let graph = generate_value_graph_attribute(rdf, &f.value.graph)?; - let layout = options.layout_ref(rdf, &f.value.layout)?; + let intro = generate_intro_attribute(f.intro, layout.input + layout.intro); + let dataset = generate_dataset_attribute(rdf, &f.dataset)?; + let input = generate_value_input_attribute(rdf, &f.value.input)?; + let graph = generate_value_graph_attribute(rdf, &f.value.graph)?; + let layout = options.layout_ref(rdf, &f.value.layout)?; - Ok(quote! { - #[tldr(#intro, #dataset, #input, #graph)] - #f_ident : #layout - }) + Ok(quote! { + #[tldr(#intro, #dataset, #input, #graph)] + #f_ident : #layout + }) + } + other => Err(Error::InvalidFieldIdent(other.clone())), }) .collect::, _>>()?; diff --git a/generators/rust/treeldr-rs-macros/src/generate/de.rs b/generators/rust/treeldr-rs-macros/src/generate/de.rs index 4df7936..5a7c6dc 100644 --- a/generators/rust/treeldr-rs-macros/src/generate/de.rs +++ b/generators/rust/treeldr-rs-macros/src/generate/de.rs @@ -4,7 +4,7 @@ use rdf_types::{dataset::TraversableDataset, Id, LiteralType, Term}; use syn::DeriveInput; use treeldr_layouts::{ layout::{DataLayout, ListLayout, LiteralLayout}, - Dataset, Layout, Pattern, + Dataset, Layout, Literal, Pattern, Value, }; use crate::parse::parse; @@ -17,6 +17,9 @@ pub enum Error { #[error(transparent)] Build(#[from] treeldr_layouts::abs::syntax::BuildError), + #[error("invalid field ident `{0}`")] + InvalidFieldIdent(Value), + #[error("invalid datatype `{0}`")] InvalidDatatype(String), } @@ -26,6 +29,7 @@ impl Error { match self { Self::Parse(e) => e.span(), Self::Build(_) => Span::call_site(), + Self::InvalidFieldIdent(_) => Span::call_site(), Self::InvalidDatatype(_) => Span::call_site(), } } @@ -157,7 +161,16 @@ pub fn generate(input: DeriveInput) -> Result { let intro = layout.intro; let dataset = dataset_to_array(&layout.dataset); - let opt_fields = layout.fields.iter().map(|(name, field)| { + let layout_fields: Vec<_> = layout + .fields + .iter() + .map(|(key, field)| match key { + Value::Literal(Literal::TextString(name)) => Ok((name, field)), + _ => Err(Error::InvalidFieldIdent(key.clone())), + }) + .collect::>()?; + + let opt_fields = layout_fields.iter().copied().map(|(name, field)| { let ident = syn::Ident::new(name, Span::call_site()); let ty = input .type_map @@ -166,7 +179,7 @@ pub fn generate(input: DeriveInput) -> Result { quote!(#ident : Option<#ty>) }); - let deserialize_fields = layout.fields.iter().map(|(name, field)| { + let deserialize_fields = layout_fields.iter().copied().map(|(name, field)| { let field_ident = syn::Ident::new(name, Span::call_site()); let field_ty = input .type_map @@ -214,7 +227,7 @@ pub fn generate(input: DeriveInput) -> Result { } }); - let unwrap_fields = layout.fields.iter().map(|(name, f)| { + let unwrap_fields = layout_fields.iter().copied().map(|(name, f)| { let ident = syn::Ident::new(name, Span::call_site()); if f.required { quote!(#ident: data.#ident.ok_or_else(|| ::treeldr::DeserializeError::MissingField(#name.to_owned()))?) diff --git a/generators/rust/treeldr-rs-macros/src/generate/ser.rs b/generators/rust/treeldr-rs-macros/src/generate/ser.rs index 5d6f858..f23fd05 100644 --- a/generators/rust/treeldr-rs-macros/src/generate/ser.rs +++ b/generators/rust/treeldr-rs-macros/src/generate/ser.rs @@ -4,7 +4,7 @@ use rdf_types::{dataset::TraversableDataset, Id, LiteralType, Term}; use syn::DeriveInput; use treeldr_layouts::{ layout::{DataLayout, ListLayout, LiteralLayout}, - Dataset, Layout, Pattern, + Dataset, Layout, Pattern, Value, }; use crate::parse::parse; @@ -17,6 +17,9 @@ pub enum Error { #[error(transparent)] Build(#[from] treeldr_layouts::abs::syntax::BuildError), + #[error("invalid field ident `{0}`")] + InvalidFieldIdent(Value), + #[error("invalid datatype `{0}`")] InvalidDatatype(String), } @@ -26,6 +29,7 @@ impl Error { match self { Self::Parse(e) => e.span(), Self::Build(_) => Span::call_site(), + Self::InvalidFieldIdent(_) => Span::call_site(), Self::InvalidDatatype(_) => Span::call_site(), } } @@ -159,56 +163,63 @@ pub fn generate(input: DeriveInput) -> Result { let intro = layout.intro; let dataset = dataset_to_array(&layout.dataset); - let fields = layout.fields.iter().map(|(name, field)| { - let field_ident = syn::Ident::new(name, Span::call_site()); - let field_intro = field.intro; - let field_dataset = dataset_to_array(&field.dataset); - let field_layout = input - .type_map - .get(field.value.layout.id().as_iri().unwrap()) - .unwrap(); - let field_inputs = inputs_to_array(&field.value.input); - let field_graph = match &field.value.graph { - Some(None) => quote!(None), - Some(Some(g)) => { - let g = generate_pattern(g); - quote!(Some(env.instantiate_pattern(#g))) - } - None => quote!(current_graph.cloned()), - }; - - let m = field.value.input.len(); - - if field.required { - quote! { - { - let env = env.intro(rdf, #field_intro); - env.instantiate_dataset(&#field_dataset, output); - <#field_layout as ::treeldr::SerializeLd<#m, V, I>>::serialize_ld_with( - &self.#field_ident, - rdf, - &env.instantiate_patterns(&#field_inputs), - #field_graph.as_ref(), - output - )?; - } - } - } else { - quote! { - if let Some(value) = &self.#field_ident { - let env = env.intro(rdf, #field_intro); - env.instantiate_dataset(&#field_dataset, output); - <#field_layout as ::treeldr::SerializeLd<#m, V, I>>::serialize_ld_with( - value, - rdf, - &env.instantiate_patterns(&#field_inputs), - #field_graph.as_ref(), - output - )?; + let fields: Vec<_> = layout + .fields + .iter() + .map(|(key, field)| { + let name = key + .as_str() + .ok_or_else(|| Error::InvalidFieldIdent(key.clone()))?; + let field_ident = syn::Ident::new(name, Span::call_site()); + let field_intro = field.intro; + let field_dataset = dataset_to_array(&field.dataset); + let field_layout = input + .type_map + .get(field.value.layout.id().as_iri().unwrap()) + .unwrap(); + let field_inputs = inputs_to_array(&field.value.input); + let field_graph = match &field.value.graph { + Some(None) => quote!(None), + Some(Some(g)) => { + let g = generate_pattern(g); + quote!(Some(env.instantiate_pattern(#g))) } + None => quote!(current_graph.cloned()), + }; + + let m = field.value.input.len(); + + if field.required { + Ok(quote! { + { + let env = env.intro(rdf, #field_intro); + env.instantiate_dataset(&#field_dataset, output); + <#field_layout as ::treeldr::SerializeLd<#m, V, I>>::serialize_ld_with( + &self.#field_ident, + rdf, + &env.instantiate_patterns(&#field_inputs), + #field_graph.as_ref(), + output + )?; + } + }) + } else { + Ok(quote! { + if let Some(value) = &self.#field_ident { + let env = env.intro(rdf, #field_intro); + env.instantiate_dataset(&#field_dataset, output); + <#field_layout as ::treeldr::SerializeLd<#m, V, I>>::serialize_ld_with( + value, + rdf, + &env.instantiate_patterns(&#field_inputs), + #field_graph.as_ref(), + output + )?; + } + }) } - } - }); + }) + .collect::>()?; quote! { let env = env.intro(rdf, #intro); diff --git a/generators/rust/treeldr-rs-macros/src/parse.rs b/generators/rust/treeldr-rs-macros/src/parse.rs index 29981bb..b225e2e 100644 --- a/generators/rust/treeldr-rs-macros/src/parse.rs +++ b/generators/rust/treeldr-rs-macros/src/parse.rs @@ -4,12 +4,15 @@ use iref::{Iri, IriBuf, IriRefBuf}; use proc_macro2::{Span, TokenStream, TokenTree}; use rdf_types::BlankIdBuf; use syn::spanned::Spanned; -use treeldr_layouts::abs::syntax::{ - BooleanLayout, ByteStringLayout, CompactIri, DataLayout, Dataset, ExtraProperties, Field, - IdLayout, Layout, LayoutHeader, ListItem, ListLayout, ListNode, ListNodeOrLayout, - LiteralLayout, NumberLayout, OrderedListLayout, Pattern, ProductLayout, Quad, SizedListLayout, - SumLayout, TextStringLayout, UnitLayout, UnorderedListLayout, ValueFormat, ValueFormatOrLayout, - VariableNameBuf, Variant, VariantFormat, VariantFormatOrLayout, +use treeldr_layouts::{ + abs::syntax::{ + BooleanLayout, ByteStringLayout, CompactIri, DataLayout, Dataset, ExtraProperties, Field, + IdLayout, Layout, LayoutHeader, ListItem, ListLayout, ListNode, ListNodeOrLayout, + LiteralLayout, NumberLayout, OrderedListLayout, Pattern, ProductLayout, Quad, + SizedListLayout, SumLayout, TextStringLayout, UnitLayout, UnorderedListLayout, ValueFormat, + ValueFormatOrLayout, VariableNameBuf, Variant, VariantFormat, VariantFormatOrLayout, + }, + Value, }; #[derive(Default)] @@ -205,7 +208,7 @@ pub fn parse(input: syn::DeriveInput) -> Result { }, ))), Kind::Record => { - let fields = match input.data { + let entries = match input.data { syn::Data::Struct(s) => match s.fields { syn::Fields::Named(fields) => fields .named @@ -227,7 +230,7 @@ pub fn parse(input: syn::DeriveInput) -> Result { required, }; - Ok((name, field)) + Ok((Value::string(name), field)) }) .collect::, _>>()?, f => return Err(Error::ExpectedNamedFields(f.span())), @@ -246,7 +249,7 @@ pub fn parse(input: syn::DeriveInput) -> Result { dataset: type_attrs.dataset.unwrap_or_default(), extra: type_attrs.extra.unwrap_or_default(), }, - fields, + fields: entries, }) } Kind::Sum => { diff --git a/layouts/src/abs/syntax/layout/product.rs b/layouts/src/abs/syntax/layout/product.rs index fa0ddc0..bcba6c2 100644 --- a/layouts/src/abs/syntax/layout/product.rs +++ b/layouts/src/abs/syntax/layout/product.rs @@ -3,12 +3,15 @@ use std::collections::BTreeMap; use json_syntax::{TryFromJson, TryFromJsonObject}; use serde::{Deserialize, Serialize}; -use crate::abs::{ - self, - syntax::{ - check_type, expect_object, get_entry, require_entry, Build, BuildError, Context, Dataset, - Error, ObjectUnusedEntries, Pattern, Scope, ValueFormatOrLayout, ValueIntro, +use crate::{ + abs::{ + self, + syntax::{ + check_type, expect_object, get_entry, require_entry, Build, BuildError, Context, + Dataset, Error, ObjectUnusedEntries, Pattern, Scope, ValueFormatOrLayout, ValueIntro, + }, }, + Value, }; use super::{LayoutHeader, ProductLayoutType}; @@ -23,7 +26,7 @@ pub struct ProductLayout { pub header: LayoutHeader, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub fields: BTreeMap, + pub fields: BTreeMap, } impl TryFromJson for ProductLayout { @@ -55,6 +58,8 @@ impl TryFromJsonObject for ProductLayout { code_map, offset, )?; + let fields: BTreeMap = + get_entry(object, "fields", &mut unused_entries, code_map, offset)?.unwrap_or_default(); let result = Self { type_: ProductLayoutType, header: LayoutHeader::try_from_json_object_at( @@ -63,8 +68,10 @@ impl TryFromJsonObject for ProductLayout { code_map, offset, )?, - fields: get_entry(object, "fields", &mut unused_entries, code_map, offset)? - .unwrap_or_default(), + fields: fields + .into_iter() + .map(|(k, v)| (Value::string(k), v)) + .collect(), }; unused_entries.check()?; Ok(result) diff --git a/layouts/src/distill/de/mod.rs b/layouts/src/distill/de/mod.rs index 1de3841..a4ad639 100644 --- a/layouts/src/distill/de/mod.rs +++ b/layouts/src/distill/de/mod.rs @@ -46,11 +46,11 @@ pub enum Error { #[error("layout `{0}` is undefined")] LayoutNotFound(Ref), - #[error("missing required field `{field_name}`")] - MissingRequiredField { + #[error("missing required key `{key}`")] + MissingRequiredKey { layout: Ref, - field_name: String, - value: BTreeMap, + key: Value, + value: BTreeMap, }, } @@ -65,13 +65,13 @@ impl Error { Self::DataAmbiguity => Error::DataAmbiguity, Self::TermAmbiguity(a) => Error::TermAmbiguity(a), Self::LayoutNotFound(layout_ref) => Error::LayoutNotFound(layout_ref.map(f)), - Self::MissingRequiredField { + Self::MissingRequiredKey { layout, - field_name, + key: field_name, value, - } => Error::MissingRequiredField { + } => Error::MissingRequiredKey { layout: layout.map(f), - field_name, + key: field_name, value, }, } @@ -704,7 +704,7 @@ where } } Layout::Product(layout) => match value { - Value::Record(value) => { + Value::Map(value) => { let env = env.intro(rdf, layout.intro); env.instantiate_dataset(&layout.dataset, output)?; @@ -727,11 +727,11 @@ where } } - for (name, field) in &layout.fields { - if field.required && !value.contains_key(name) { - return Err(Error::MissingRequiredField { + for (key, field) in &layout.fields { + if field.required && !value.contains_key(key) { + return Err(Error::MissingRequiredKey { layout: layout_ref.clone().cast(), - field_name: name.clone(), + key: key.clone(), value: value.clone(), }); } diff --git a/layouts/src/distill/hy/mod.rs b/layouts/src/distill/hy/mod.rs index 0344981..b5791cd 100644 --- a/layouts/src/distill/hy/mod.rs +++ b/layouts/src/distill/hy/mod.rs @@ -57,10 +57,10 @@ pub enum DataFragment { variant_name: String, }, - #[error("field `{field_name}`")] - Field { + #[error("key `{key}`")] + Key { layout: Ref, - field_name: String, + key: Value, }, #[error("list node")] @@ -330,7 +330,7 @@ where let mut record = BTreeMap::new(); - for (name, field) in &layout.fields { + for (key, field) in &layout.fields { let mut field_substitution = substitution.clone(); field_substitution.intro(field.intro); @@ -340,9 +340,9 @@ where field.dataset.quads().with_default_graph(current_graph), ) .into_unique() - .for_fragment(|| DataFragment::Field { + .for_fragment(|| DataFragment::Key { layout: layout_ref.clone().cast(), - field_name: name.clone(), + key: key.clone(), })?; match field_substitution { @@ -362,20 +362,20 @@ where &field_inputs, )?; - record.insert(name.clone(), value); + record.insert(key.clone(), value); } None => { if field.required { - return Err(Error::MissingData(Box::new(DataFragment::Field { + return Err(Error::MissingData(Box::new(DataFragment::Key { layout: layout_ref.clone().cast(), - field_name: name.clone(), + key: key.clone(), }))); } } } } - Ok(TypedValue::Record(record, layout_ref.casted())) + Ok(TypedValue::Map(record, layout_ref.casted())) } Layout::List(layout) => { match layout { diff --git a/layouts/src/layout/product.rs b/layouts/src/layout/product.rs index 865b8c3..7c1da8c 100644 --- a/layouts/src/layout/product.rs +++ b/layouts/src/layout/product.rs @@ -2,7 +2,7 @@ use educe::Educe; use std::collections::BTreeMap; use std::hash::Hash; -use crate::{Dataset, Ref, ValueFormat}; +use crate::{Dataset, Ref, Value, ValueFormat}; use super::LayoutType; @@ -24,7 +24,7 @@ pub struct ProductLayout { pub intro: u32, /// Fields. - pub fields: BTreeMap>, + pub fields: BTreeMap>, /// Dataset. pub dataset: Dataset, diff --git a/layouts/src/value/cbor/serde_cbor.rs b/layouts/src/value/cbor/serde_cbor.rs index 7c229ea..c8b9e2a 100644 --- a/layouts/src/value/cbor/serde_cbor.rs +++ b/layouts/src/value/cbor/serde_cbor.rs @@ -71,12 +71,12 @@ impl TypedValue { inner.try_into_tagged_serde_cbor_with_ref(vocabulary, interpretation, layouts)?, Some(ty.cast()), ), - Self::Record(map, ty) => ( + Self::Map(map, ty) => ( serde_cbor::Value::Map( map.into_iter() .map(|(key, value)| { Ok(( - serde_cbor::Value::Text(key), + key.into(), value.try_into_tagged_serde_cbor_with_ref( vocabulary, interpretation, @@ -126,9 +126,9 @@ impl From> for serde_cbor::Value { TypedValue::Literal(TypedLiteral::TextString(s, _)) => Self::Text(s), TypedValue::Literal(TypedLiteral::Id(s, _)) => Self::Text(s), TypedValue::Variant(inner, _, _) => (*inner).into(), - TypedValue::Record(map, _) => Self::Map( + TypedValue::Map(map, _) => Self::Map( map.into_iter() - .map(|(key, value)| (serde_cbor::Value::Text(key), value.into())) + .map(|(key, value)| (key.into(), value.into())) .collect(), ), TypedValue::List(items, _) => Self::Array(items.into_iter().map(Into::into).collect()), @@ -165,9 +165,9 @@ impl From for serde_cbor::Value { Value::Literal(Literal::Number(n)) => n.into(), Value::Literal(Literal::ByteString(bytes)) => serde_cbor::Value::Bytes(bytes), Value::Literal(Literal::TextString(string)) => serde_cbor::Value::Text(string), - Value::Record(map) => serde_cbor::Value::Map( + Value::Map(map) => serde_cbor::Value::Map( map.into_iter() - .map(|(key, value)| (serde_cbor::Value::Text(key), value.into())) + .map(|(key, value)| (key.into(), value.into())) .collect(), ), Value::List(items) => { diff --git a/layouts/src/value/de.rs b/layouts/src/value/de.rs index f6c4504..63a1318 100644 --- a/layouts/src/value/de.rs +++ b/layouts/src/value/de.rs @@ -113,7 +113,7 @@ impl<'de> serde::Deserialize<'de> for Value { map.insert(key, value); } - Ok(Value::Record(map)) + Ok(Value::Map(map)) } } diff --git a/layouts/src/value/mod.rs b/layouts/src/value/mod.rs index e27773d..ae8347a 100644 --- a/layouts/src/value/mod.rs +++ b/layouts/src/value/mod.rs @@ -270,11 +270,39 @@ pub enum Literal { TextString(String), } +impl Literal { + pub fn as_str(&self) -> Option<&str> { + match self { + Self::TextString(s) => Some(s), + _ => None, + } + } +} + +impl fmt::Display for Literal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Unit => f.write_str("null"), + Self::Boolean(true) => f.write_str("true"), + Self::Boolean(false) => f.write_str("false"), + Self::Number(n) => n.fmt(f), + Self::ByteString(bytes) => { + f.write_str("x")?; + for b in bytes { + write!(f, "{b:02x}")?; + } + Ok(()) + } + Self::TextString(s) => json_syntax::print::string_literal(s, f), + } + } +} + /// Untyped tree value. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Value { Literal(Literal), - Record(BTreeMap), + Map(BTreeMap), List(Vec), } @@ -288,6 +316,18 @@ impl Value { pub fn is_unit(&self) -> bool { matches!(self, Self::Literal(Literal::Unit)) } + + /// Creates a text string value. + pub fn string(value: String) -> Self { + Self::Literal(Literal::TextString(value)) + } + + pub fn as_str(&self) -> Option<&str> { + match self { + Self::Literal(l) => l.as_str(), + _ => None, + } + } } impl Default for Value { @@ -316,11 +356,11 @@ impl TryFromJson for Value { .map(|item| Self::try_from_json_at(item.value, code_map, item.offset).unwrap()) .collect(), )), - json_syntax::Value::Object(o) => Ok(Self::Record( + json_syntax::Value::Object(o) => Ok(Self::Map( o.iter_mapped(code_map, offset) .map(|entry| { ( - entry.value.key.value.to_string(), + Value::string(entry.value.key.value.to_string()), Self::try_from_json_at( entry.value.value.value, code_map, @@ -343,9 +383,9 @@ impl From for Value { json_syntax::Value::Number(n) => Self::Literal(Literal::Number(n.into())), json_syntax::Value::String(s) => Self::Literal(Literal::TextString(s.to_string())), json_syntax::Value::Array(a) => Self::List(a.into_iter().map(Into::into).collect()), - json_syntax::Value::Object(o) => Self::Record( + json_syntax::Value::Object(o) => Self::Map( o.into_iter() - .map(|entry| (entry.key.into_string(), entry.value.into())) + .map(|entry| (Value::string(entry.key.into_string()), entry.value.into())) .collect(), ), } @@ -362,16 +402,48 @@ impl From for Value { serde_json::Value::Array(items) => { Self::List(items.into_iter().map(Into::into).collect()) } - serde_json::Value::Object(entries) => Self::Record( + serde_json::Value::Object(entries) => Self::Map( entries .into_iter() - .map(|(key, value)| (key, value.into())) + .map(|(key, value)| (Value::string(key), value.into())) .collect(), ), } } } +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Literal(l) => l.fmt(f), + Self::List(l) => { + f.write_str("[")?; + for (i, v) in l.iter().enumerate() { + if i > 0 { + f.write_str(",")?; + } + + v.fmt(f)?; + } + f.write_str("]") + } + Self::Map(m) => { + f.write_str("{")?; + for (i, (k, v)) in m.iter().enumerate() { + if i > 0 { + f.write_str(",")?; + } + + k.fmt(f)?; + f.write_str(":")?; + v.fmt(f)?; + } + f.write_str("}") + } + } + } +} + /// Error raised when trying to convert a value to JSON that is not compatible /// with the JSON data model. #[derive(Debug, thiserror::Error)] @@ -383,6 +455,10 @@ pub enum NonJsonValue { /// Byte string value, not supported by JSON. #[error("byte string cannot be converted to JSON")] ByteString(Vec), + + /// Non-string key, not supported by JSON. + #[error("non-string key")] + NonStringKey(Value), } impl From for NonJsonValue { @@ -440,11 +516,16 @@ impl TryFrom for json_syntax::Value { fn try_from(value: Value) -> Result { match value { Value::Literal(l) => l.try_into(), - Value::Record(r) => { + Value::Map(r) => { let mut object = json_syntax::Object::new(); for (key, value) in r { - object.insert(key.into(), value.try_into()?); + match key { + Value::Literal(Literal::TextString(key)) => { + object.insert(key.into(), value.try_into()?); + } + other => return Err(NonJsonValue::NonStringKey(other)), + } } Ok(json_syntax::Value::Object(object)) @@ -464,14 +545,19 @@ impl TryFrom for serde_json::Value { fn try_from(value: Value) -> Result { match value { Value::Literal(l) => l.try_into(), - Value::Record(r) => { - let mut map = serde_json::Map::new(); + Value::Map(r) => { + let mut object = serde_json::Map::new(); for (key, value) in r { - map.insert(key, value.try_into()?); + match key { + Value::Literal(Literal::TextString(key)) => { + object.insert(key, value.try_into()?); + } + other => return Err(NonJsonValue::NonStringKey(other)), + } } - Ok(serde_json::Value::Object(map)) + Ok(serde_json::Value::Object(object)) } Value::List(list) => list .into_iter() @@ -542,8 +628,8 @@ pub enum TypedValue { /// The third parameter is the index of the variant in the sum layout. Variant(Box, Ref, u32), - /// Record. - Record(BTreeMap, Ref), + /// Map. + Map(BTreeMap, Ref), /// List. List(Vec, Ref), @@ -565,7 +651,7 @@ impl TypedValue { match self { Self::Literal(t) => Some(t.type_()), Self::Variant(_, ty, _) => Some(ty.as_casted()), - Self::Record(_, ty) => Some(ty.as_casted()), + Self::Map(_, ty) => Some(ty.as_casted()), Self::List(_, ty) => Some(ty.as_casted()), Self::Always(_) => None, } @@ -576,7 +662,7 @@ impl TypedValue { match self { Self::Literal(l) => l.into_untyped(), Self::Variant(value, _, _) => value.into_untyped(), - Self::Record(map, _) => Value::Record( + Self::Map(map, _) => Value::Map( map.into_iter() .map(|(k, v)| (k, v.into_untyped())) .collect(), diff --git a/layouts/src/value/ser.rs b/layouts/src/value/ser.rs index dc9b354..263f75f 100644 --- a/layouts/src/value/ser.rs +++ b/layouts/src/value/ser.rs @@ -7,7 +7,7 @@ impl serde::Serialize for Value { { match self { Self::Literal(literal) => literal.serialize(serializer), - Self::Record(record) => { + Self::Map(record) => { use serde::ser::SerializeMap; let mut map = serializer.serialize_map(Some(record.len()))?;