From a9afe767723506db40358da1d403f64ace5ab3dc Mon Sep 17 00:00:00 2001 From: stevelr Date: Tue, 22 Mar 2022 15:37:38 -0700 Subject: [PATCH] Feat/enum types (#97) * add Document to common Signed-off-by: stevelr * add utility for dumping smithy model in json Signed-off-by: stevelr * - adds common::Document value type to correspond with smithy "Document" - adds Unit type - generate union/enums with unit type and serialized with numeric value - initial support for borrowed struct definitions - new command line utility `dump-json-model` to dump smithy model - add fixed_array and fixed_map to simplify decoding - bump weld-codegen to 0.4.3 - bump wasmbus-rpc to 0.8.4 Signed-off-by: stevelr * source file for common::Document, Number, and DocumentRef Signed-off-by: stevelr --- codegen/Cargo.toml | 8 +- codegen/bin/dump-smithy-model.rs | 34 ++ codegen/codegen.toml | 2 +- codegen/src/codegen_py.rs | 2 +- codegen/src/codegen_rust.rs | 546 +++++++++++++++---- codegen/src/decode_rust.rs | 165 +++--- codegen/src/encode_rust.rs | 97 ++-- codegen/src/model.rs | 5 + codegen/src/wasmbus_model.rs | 7 +- codegen/src/writer.rs | 10 + rpc-rs/Cargo.toml | 4 +- rpc-rs/codegen.toml | 2 +- rpc-rs/src/cbor.rs | 118 ++--- rpc-rs/src/common.rs | 52 +- rpc-rs/src/document.rs | 875 +++++++++++++++++++++++++++++++ rpc-rs/src/lib.rs | 4 + rpc-rs/src/wasmbus_core.rs | 301 +++++------ rpc-rs/src/wasmbus_model.rs | 43 +- 18 files changed, 1768 insertions(+), 507 deletions(-) create mode 100644 codegen/bin/dump-smithy-model.rs create mode 100644 rpc-rs/src/document.rs diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 0c0f298..ecdbbea 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "weld-codegen" -version = "0.4.2" +version = "0.4.3" edition = "2021" authors = [ "wasmcloud Team" ] license = "Apache-2.0" @@ -18,12 +18,14 @@ BigInteger = [] BigDecimal = [] [dependencies] +anyhow = "1.0" atelier_assembler = "0.1" atelier_core = "0.2" atelier_json = "0.2" atelier_smithy = "0.2" bytes = "1.0" cfg-if = "1.0" +clap = { version = "3.1", features = [ "derive" ] } directories = "4.0" downloader = { version = "0.2", features = ["rustls-tls"], default-features = false } handlebars = "4.0" @@ -47,3 +49,7 @@ path = "src/lib.rs" [[bin]] name = "codegen" path = "bin/gen.rs" + +[[bin]] +name = "dump-smithy-model" +path = "bin/dump-smithy-model.rs" diff --git a/codegen/bin/dump-smithy-model.rs b/codegen/bin/dump-smithy-model.rs new file mode 100644 index 0000000..8b0383e --- /dev/null +++ b/codegen/bin/dump-smithy-model.rs @@ -0,0 +1,34 @@ +//! dump model in json + +use anyhow::{anyhow, Context, Result}; +use clap::{self, Parser}; +use std::path::PathBuf; +use weld_codegen::{config::CodegenConfig, sources_to_model}; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + /// codegen.toml file (default: "./codegen.toml") + #[clap(short, long)] + config: Option, +} + +fn main() -> Result<()> { + let args = Args::parse(); + let config_path = args + .config + .unwrap_or_else(|| PathBuf::from("./codegen.toml")); + if !config_path.is_file() { + return Err(anyhow!("missing config file {}", &config_path.display())); + } + let config = std::fs::read_to_string(&config_path) + .with_context(|| format!("error reading config file {}", config_path.display()))? + .parse::()?; + let base_dir = config_path.parent().unwrap().to_path_buf(); + let model = sources_to_model(&config.models, &base_dir, 0)?; + let json_model = atelier_json::model_to_json(&model); + + let out = std::io::stdout(); + serde_json::to_writer(&out, &json_model)?; + Ok(()) +} diff --git a/codegen/codegen.toml b/codegen/codegen.toml index 15e8a08..0c911c4 100644 --- a/codegen/codegen.toml +++ b/codegen/codegen.toml @@ -4,7 +4,7 @@ [[models]] url = "https://cdn.jsdelivr.net/gh/wasmcloud/interfaces@d6ae2dd196aae3c2486e747eb1b3cd188ea71132/core/wasmcloud-core.smithy" [[models]] -url = "https://cdn.jsdelivr.net/gh/wasmcloud/interfaces@cb504c8b7cfcee0a8c5ecdedf75757f4718ad4e6/core/wasmcloud-model.smithy" +url = "https://cdn.jsdelivr.net/gh/wasmcloud/interfaces@e0f205da8a0e1549497571c3e994a1851480621c/core/wasmcloud-model.smithy" [rust] output_dir = "." diff --git a/codegen/src/codegen_py.rs b/codegen/src/codegen_py.rs index 8d9a6d8..c830ba1 100644 --- a/codegen/src/codegen_py.rs +++ b/codegen/src/codegen_py.rs @@ -41,7 +41,7 @@ const WASMBUS_RPC_CRATE: &str = "wasmbus_rpc"; const DEFAULT_MAP_TYPE: &str = "dict"; // python 3.9+ const DEFAULT_LIST_TYPE: &str = "list"; // python 3.9+ const DEFAULT_SET_TYPE: &str = "set"; // python 3.9+ -const DEFAULT_DOCUMENT_TYPE: &str = "bytes"; +const DEFAULT_DOCUMENT_TYPE: &str = "Document"; /// declarations for sorting. First sort key is the type (simple, then map, then struct). /// In rust, sorting by BytesMut as the second key will result in sort by item name. diff --git a/codegen/src/codegen_rust.rs b/codegen/src/codegen_rust.rs index 5114bb5..3118af3 100644 --- a/codegen/src/codegen_rust.rs +++ b/codegen/src/codegen_rust.rs @@ -34,13 +34,17 @@ use atelier_core::{ TRAIT_DOCUMENTATION, TRAIT_TRAIT, TRAIT_UNSTABLE, }, }; -use std::{collections::HashMap, path::Path, str::FromStr, string::ToString}; +use std::{ + collections::{BTreeMap, HashMap}, + path::Path, + str::FromStr, + string::ToString, +}; const WASMBUS_RPC_CRATE: &str = "wasmbus_rpc"; const DEFAULT_MAP_TYPE: &str = "std::collections::HashMap"; const DEFAULT_LIST_TYPE: &str = "Vec"; const DEFAULT_SET_TYPE: &str = "std::collections::BTreeSet"; -const DEFAULT_DOCUMENT_TYPE: &str = "Vec"; /// declarations for sorting. First sort key is the type (simple, then map, then struct). /// In rust, sorting by BytesMut as the second key will result in sort by item name. @@ -55,6 +59,7 @@ pub struct RustCodeGen<'model> { pub(crate) packages: HashMap, pub(crate) import_core: String, pub(crate) model: Option<&'model Model>, + pub(crate) with_lifetime: BTreeMap, } impl<'model> RustCodeGen<'model> { @@ -64,6 +69,7 @@ impl<'model> RustCodeGen<'model> { namespace: None, packages: HashMap::default(), import_core: String::default(), + with_lifetime: BTreeMap::default(), } } } @@ -93,6 +99,39 @@ enum MethodArgFlags { ToString, } +/// Optional lifetime. +/// When used as a parameter to a codegen function, the function adds the lifetime +/// annotation to any identifiers generated _only if_ the identifier is required to have +/// a lifetime. (based on self.has_lifetime(shape_id)) +#[derive(Copy)] +pub(crate) enum Lifetime<'lt> { + None, // no lifetime + Any, // '_ + L(&'lt str), // 'x (str contains the symbol only) +} + +impl<'lt> Lifetime<'lt> { + /// returns one of `""`, `"<'_>"`, or `"<'x>"` where `x` is the L variant value + pub(crate) fn annotate(&self) -> String { + match self { + Lifetime::None => String::default(), + Lifetime::Any => "<'_>".to_string(), + Lifetime::L(s) => format!("<'{}>", s), + } + } + + /// returns true if + pub(crate) fn is_some(&self) -> bool { + !matches!(self, Lifetime::None) + } +} + +impl<'lt> Clone for Lifetime<'lt> { + fn clone(&self) -> Self { + *self + } +} + /// Returns true if the type is a rust primitive pub fn is_rust_primitive(id: &ShapeID) -> bool { (id.namespace() == prelude_namespace_id() @@ -176,6 +215,7 @@ impl<'model> CodeGen for RustCodeGen<'model> { self.packages.insert(p.namespace.to_string(), p.clone()); } } + self.map_lifetimes(model); } Ok(()) } @@ -269,7 +309,7 @@ impl<'model> CodeGen for RustCodeGen<'model> { } } w.write(&format!( - "\npub const SMITHY_VERSION : &str = \"{}\";\n\n", + "\n#[allow(dead_code)] pub const SMITHY_VERSION : &str = \"{}\";\n\n", model.smithy_version() )); Ok(()) @@ -287,12 +327,21 @@ impl<'model> CodeGen for RustCodeGen<'model> { shapes.sort_by_key(|v| v.0); for (id, traits, shape) in shapes.into_iter() { + if let Some(cg) = get_trait::(traits, codegen_rust_trait())? { + if cg.skip { + continue; + } + } + let lt = match self.has_lifetime(id) { + true => Lifetime::L("v"), + false => Lifetime::None, + }; match shape { ShapeKind::Simple(simple) => { - self.declare_simple_shape(w, id.shape_name(), traits, simple)?; + self.declare_simple_shape(w, id.shape_name(), traits, simple, lt)?; } ShapeKind::Map(map) => { - self.declare_map_shape(w, id.shape_name(), traits, map)?; + self.declare_map_shape(w, id.shape_name(), traits, map, lt)?; } ShapeKind::List(list) => { self.declare_list_or_set_shape( @@ -301,6 +350,7 @@ impl<'model> CodeGen for RustCodeGen<'model> { traits, list, DEFAULT_LIST_TYPE, + lt, )?; } ShapeKind::Set(set) => { @@ -310,13 +360,14 @@ impl<'model> CodeGen for RustCodeGen<'model> { traits, set, DEFAULT_SET_TYPE, + lt, )?; } ShapeKind::Structure(strukt) => { - self.declare_structure_shape(w, id.shape_name(), traits, strukt)?; + self.declare_structure_shape(w, id, traits, strukt, lt)?; } ShapeKind::Union(strukt) => { - self.declare_union_shape(w, id.shape_name(), traits, strukt)?; + self.declare_union_shape(w, id, traits, strukt, lt)?; } ShapeKind::Operation(_) | ShapeKind::Resource(_) @@ -391,6 +442,100 @@ pub(crate) fn is_rust_source(path: &Path) -> bool { } impl<'model> RustCodeGen<'model> { + /// populate `with_lifetime` with every shape that requires a lifetime declaration + /// Save both positive and negative results to minimize re-evaluating shapes. + /// at the moment, this only works for values that are input parameters, + /// because we can't return a ref to the vec created inside a receiver or sender-response + fn map_lifetime( + &mut self, + model: &Model, + id: &ShapeID, + _traits: &AppliedTraits, + shape: &ShapeKind, + ) -> bool { + if let Some(val) = self.with_lifetime.get(id) { + return *val; + } + // FUTURE: add codegenRust trait "borrowed" (or array of lifetimes?) + // and check that here. If we use array of Lifetimes, a struct + // could define more than one + // if has-trait-borrowed { self.with_lifetime.insert(); return true } + // then, get rid of special case code for DocumentRef, + // and add the borrowed/lifetime trait to wasmcloud-common.smithy + match shape { + ShapeKind::Simple(_) => {} + ShapeKind::Map(map) => { + let value = map.value().target(); + if let Some(target) = model.shape(value) { + if self.map_lifetime(model, value, target.traits(), target.body()) { + self.with_lifetime.insert(id.clone(), true); + return true; + } + } else if self.has_lifetime(value) { + self.with_lifetime.insert(id.clone(), true); + return true; + } + let key = map.key().target(); + if let Some(target) = model.shape(key) { + if self.map_lifetime(model, key, target.traits(), target.body()) { + self.with_lifetime.insert(id.clone(), true); + return true; + } + } else if self.has_lifetime(key) { + self.with_lifetime.insert(id.clone(), true); + return true; + } + } + ShapeKind::List(list_or_set) | ShapeKind::Set(list_or_set) => { + let member = list_or_set.member().target(); + if let Some(target) = model.shape(member) { + if self.map_lifetime(model, member, target.traits(), target.body()) { + self.with_lifetime.insert(id.clone(), true); + return true; + } + } else if self.has_lifetime(member) { + self.with_lifetime.insert(id.clone(), true); + return true; + } + } + ShapeKind::Structure(struct_or_union) | ShapeKind::Union(struct_or_union) => { + for member in struct_or_union.members() { + let field = member.target(); + if let Some(target) = model.shape(field) { + if self.map_lifetime(model, field, target.traits(), target.body()) { + self.with_lifetime.insert(id.clone(), true); + return true; + } + } else if self.has_lifetime(field) { + self.with_lifetime.insert(id.clone(), true); + return true; + } + } + } + ShapeKind::Operation(_) + | ShapeKind::Resource(_) + | ShapeKind::Service(_) + | ShapeKind::Unresolved => {} + } + self.with_lifetime.insert(id.clone(), false); + false + } + + pub(crate) fn map_lifetimes(&mut self, model: &Model) { + // Initialize with any types that require lifetimes in their declarations + // TODO: remove DocumentRef here and use trait in smithy file instead + let document_ref = ShapeID::new_unchecked("org.wasmcloud.common", "DocumentRef", None); + self.with_lifetime.insert(document_ref, true); + for (id, traits, shape) in model.shapes().map(|s| (s.id(), s.traits(), s.body())) { + self.map_lifetime(model, id, traits, shape); + } + } + + /// Checks whether the shape should have a lifetime annotation. + pub(crate) fn has_lifetime(&self, id: &ShapeID) -> bool { + *self.with_lifetime.get(id).unwrap_or(&false) + } + /// Apply documentation traits: (documentation, deprecated, unstable) fn apply_documentation_traits( &mut self, @@ -428,32 +573,40 @@ impl<'model> RustCodeGen<'model> { } /// field type, wrapped with Option if field is not required - pub(crate) fn field_type_string(&self, field: &MemberShape) -> Result { - self.type_string(if is_optional_type(field) { - Ty::Opt(field.target()) - } else { - Ty::Shape(field.target()) - }) + pub(crate) fn field_type_string( + &self, + field: &MemberShape, + lt: Lifetime<'_>, + ) -> Result { + let target = field.target(); + self.type_string( + if is_optional_type(field) { + Ty::Opt(target) + } else { + Ty::Shape(target) + }, + lt, + ) } /// Write a type name, a primitive or defined type, with or without deref('&') and with or without Option<> - pub(crate) fn type_string(&self, ty: Ty<'_>) -> Result { + /// The lifetime parameter will only be used if the type requires a lifetime + pub(crate) fn type_string(&self, ty: Ty<'_>, lt: Lifetime<'_>) -> Result { let mut s = String::new(); match ty { Ty::Opt(id) => { s.push_str("Option<"); - s.push_str(&self.type_string(Ty::Shape(id))?); + s.push_str(&self.type_string(Ty::Shape(id), lt)?); s.push('>'); } Ty::Ref(id) => { s.push('&'); - s.push_str(&self.type_string(Ty::Shape(id))?); + s.push_str(&self.type_string(Ty::Shape(id), lt)?); } Ty::Shape(id) => { let name = id.shape_name().to_string(); if id.namespace() == prelude_namespace_id() { let ty = match name.as_ref() { - // Document are Blob SHAPE_BLOB => "Vec", SHAPE_BOOLEAN | SHAPE_PRIMITIVEBOOLEAN => "bool", SHAPE_STRING => "String", @@ -463,10 +616,10 @@ impl<'model> RustCodeGen<'model> { SHAPE_LONG | SHAPE_PRIMITIVELONG => "i64", SHAPE_FLOAT | SHAPE_PRIMITIVEFLOAT => "f32", SHAPE_DOUBLE | SHAPE_PRIMITIVEDOUBLE => "f64", - // if declared as members (of a struct, list, or map), we don't have trait data here to write - // as anything other than a blob. Instead, a type should be created for the Document that can have traits, - // and that type used for the member. This should probably be a lint rule. - SHAPE_DOCUMENT => DEFAULT_DOCUMENT_TYPE, + SHAPE_DOCUMENT => { + s.push_str(&self.import_core); + "::common::Document" + } SHAPE_TIMESTAMP => "Timestamp", SHAPE_BIGINTEGER => { cfg_if::cfg_if! { @@ -508,27 +661,14 @@ impl<'model> RustCodeGen<'model> { { // we are in the same namespace so we don't need to specify namespace s.push_str(&self.to_type_name(&id.shape_name().to_string())); + if self.has_lifetime(id) { + s.push_str(<.annotate()); + } } else { - match self.packages.get(&id.namespace().to_string()) { - Some(PackageName { - crate_name: Some(crate_name), - .. - }) => { - // the crate name should be valid rust syntax. If not, they'll get an error with rustc - s.push_str(crate_name); - s.push_str("::"); - s.push_str(&self.to_type_name(&id.shape_name().to_string())); - } - _ => { - return Err(Error::Model(format!( - "undefined crate for namespace {} for symbol {}. Make sure \ - codegen.toml includes all dependent namespaces, and that the \ - dependent .smithy file contains package metadata with crate: \ - value", - &id.namespace(), - &id - ))); - } + s.push_str(&self.get_crate_path(id)?); + s.push_str(&self.to_type_name(&id.shape_name().to_string())); + if self.has_lifetime(id) { + s.push_str(<.annotate()); } } } @@ -537,8 +677,8 @@ impl<'model> RustCodeGen<'model> { } /// Write a type name, a primitive or defined type, with or without deref('&') and with or without Option<> - fn write_type(&mut self, w: &mut Writer, ty: Ty<'_>) -> Result<()> { - w.write(&self.type_string(ty)?); + fn write_type(&mut self, w: &mut Writer, ty: Ty<'_>, lt: Lifetime<'_>) -> Result<()> { + w.write(&self.type_string(ty, lt)?); Ok(()) } @@ -561,10 +701,14 @@ impl<'model> RustCodeGen<'model> { id: &Identifier, traits: &AppliedTraits, simple: &Simple, + lt: Lifetime, ) -> Result<()> { self.apply_documentation_traits(w, id, traits); w.write(b"pub type "); self.write_ident(w, id); + if lt.is_some() { + w.write(lt.annotate().as_bytes()); + } w.write(b" = "); let ty = match simple { Simple::Blob => "Vec", @@ -576,9 +720,10 @@ impl<'model> RustCodeGen<'model> { Simple::Long => "i64", Simple::Float => "f32", Simple::Double => "f64", - - // note: in the future, codegen traits may modify this - Simple::Document => DEFAULT_DOCUMENT_TYPE, + Simple::Document => { + w.write(&self.import_core); + "::common::Document" + } Simple::Timestamp => "Timestamp", Simple::BigInteger => { cfg_if::cfg_if! { @@ -592,6 +737,9 @@ impl<'model> RustCodeGen<'model> { } }; w.write(ty); + //let end_mark = w.pos(); + //self.type_aliases + // .insert(id.to_string(), w.get_slice(start_pos, end_pos).to_string()); w.write(b";\n\n"); Ok(()) } @@ -602,16 +750,20 @@ impl<'model> RustCodeGen<'model> { id: &Identifier, traits: &AppliedTraits, shape: &MapShape, + lt: Lifetime<'_>, ) -> Result<()> { self.apply_documentation_traits(w, id, traits); w.write(b"pub type "); self.write_ident(w, id); + if lt.is_some() { + w.write(lt.annotate().as_bytes()); + } w.write(b" = "); w.write(DEFAULT_MAP_TYPE); w.write(b"<"); - self.write_type(w, Ty::Shape(shape.key().target()))?; + self.write_type(w, Ty::Shape(shape.key().target()), lt)?; w.write(b","); - self.write_type(w, Ty::Shape(shape.value().target()))?; + self.write_type(w, Ty::Shape(shape.value().target()), lt)?; w.write(b">;\n\n"); Ok(()) } @@ -623,14 +775,18 @@ impl<'model> RustCodeGen<'model> { traits: &AppliedTraits, shape: &ListOrSet, typ: &str, + lt: Lifetime<'_>, ) -> Result<()> { self.apply_documentation_traits(w, id, traits); w.write(b"pub type "); self.write_ident(w, id); + if lt.is_some() { + w.write(lt.annotate().as_bytes()); + } w.write(b" = "); w.write(typ); w.write(b"<"); - self.write_type(w, Ty::Shape(shape.member().target()))?; + self.write_type(w, Ty::Shape(shape.member().target()), lt)?; w.write(b">;\n\n"); Ok(()) } @@ -638,40 +794,24 @@ impl<'model> RustCodeGen<'model> { fn declare_structure_shape( &mut self, w: &mut Writer, - id: &Identifier, + id: &ShapeID, traits: &AppliedTraits, strukt: &StructureOrUnion, + lt: Lifetime<'_>, ) -> Result<()> { let is_trait_struct = traits.contains_key(&prelude_shape_named(TRAIT_TRAIT).unwrap()); - self.apply_documentation_traits(w, id, traits); - let mut derive_list = vec!["Clone", "Debug", "PartialEq", "Serialize", "Deserialize"]; - // derive(Default) is disabled for traits and enabled for all other structs - let mut derive_default = !is_trait_struct; - // derive(Eq) is enabled, unless specifically disabled in codegenRust - let mut derive_eq = true; - let mut non_exhaustive = false; - - if let Some(cg) = get_trait::(traits, codegen_rust_trait())? { - derive_default = !cg.no_derive_default; - derive_eq = !cg.no_derive_eq; - non_exhaustive = cg.non_exhaustive; - } - if derive_default { - derive_list.push("Default"); - } - if derive_eq { - derive_list.push("Eq"); - } - derive_list.sort_unstable(); - let derive_decl = format!("#[derive({})]\n", derive_list.join(",")); - w.write(&derive_decl); - if non_exhaustive { + let ident = id.shape_name(); + self.apply_documentation_traits(w, ident, traits); + let preface = self.build_preface(id, traits); + w.write(&preface.derives()); + if preface.non_exhaustive { w.write(b"#[non_exhaustive]\n"); } w.write(b"pub struct "); - self.write_ident(w, id); + self.write_ident(w, ident); + w.write(<.annotate()); w.write(b" {\n"); - let (fields, _is_numbered) = get_sorted_fields(id, strukt)?; + let (fields, _is_numbered) = get_sorted_fields(ident, strukt)?; for member in fields.iter() { self.apply_documentation_traits(w, member.id(), member.traits()); // use the declared name for serialization, unless an override is declared @@ -688,6 +828,11 @@ impl<'model> RustCodeGen<'model> { if ser_name != rust_field_name { w.write(&format!(" #[serde(rename=\"{}\")] ", ser_name)); } + let lt = if self.has_lifetime(member.target()) { + lt + } else { + Lifetime::None + }; // for rmp-msgpack - need to use serde_bytes serializer for Blob (and Option) // otherwise Vec is written as an array of individual bytes, not a memory slice. @@ -719,6 +864,8 @@ impl<'model> RustCodeGen<'model> { w.write(r#" #[serde(default, skip_serializing_if = "Option::is_none")] "#); } else if (is_trait_struct && !member.is_required()) || has_default(self.model.unwrap(), member) + || (member.target() + == &ShapeID::new_unchecked(PRELUDE_NAMESPACE, SHAPE_DOCUMENT, None)) { // trait structs are deserialized only and need default values // on deserialization, so always add [serde(default)] for trait structs. @@ -738,7 +885,7 @@ impl<'model> RustCodeGen<'model> { format!( " pub {}: {},\n", &rust_field_name, - self.field_type_string(member)? + self.field_type_string(member, lt)? ) .as_bytes(), ); @@ -750,32 +897,46 @@ impl<'model> RustCodeGen<'model> { fn declare_union_shape( &mut self, w: &mut Writer, - id: &Identifier, + id: &ShapeID, traits: &AppliedTraits, strukt: &StructureOrUnion, + lt: Lifetime<'_>, ) -> Result<()> { - let (fields, is_numbered) = get_sorted_fields(id, strukt)?; + let ident = id.shape_name(); + let (fields, is_numbered) = get_sorted_fields(ident, strukt)?; if !is_numbered { return Err(Error::Model(format!( "union {} must have numbered fields", - id + ident ))); } - self.apply_documentation_traits(w, id, traits); - w.write(b"#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]\n"); - println!("Union: {}:\n:{:#?}", id, strukt); - + self.apply_documentation_traits(w, ident, traits); + let mut preface = self.build_preface(id, traits); + preface.default = false; + w.write(&preface.derives()); + if preface.non_exhaustive { + w.write(b"#[non_exhaustive]\n"); + } w.write(b"pub enum "); - self.write_ident(w, id); + self.write_ident(w, ident); + if lt.is_some() { + w.write(lt.annotate().as_bytes()); + } w.write(b" {\n"); for member in fields.iter() { self.apply_documentation_traits(w, member.id(), member.traits()); + let field_num = member.field_num().unwrap(); + w.write(&format!("/// n({})\n", field_num)); let variant_name = self.to_type_name(&member.id().to_string()); - w.write(&format!( - "{}({}),\n", - variant_name, - self.type_string(Ty::Shape(member.target()))? - )); // TODO: Ty::Ref ? + if member.target() == crate::model::unit_shape() { + w.write(&format!("{},\n", variant_name,)); + } else { + w.write(&format!( + "{}({}),\n", + variant_name, + self.type_string(Ty::Shape(member.target()), lt)? + )); + } } w.write(b"}\n\n"); Ok(()) @@ -865,12 +1026,30 @@ impl<'model> RustCodeGen<'model> { let method_name = self.to_method_name(method_id, method_traits); let mut arg_flags = MethodArgFlags::Normal; self.apply_documentation_traits(w, method_id, method_traits); + let input_lt = if let Some(input_type) = op.input() { + self.has_lifetime(input_type) + } else { + false + }; + let output_lt = if let Some(output_type) = op.output() { + self.has_lifetime(output_type) + } else { + false + }; + let func_lt = if input_lt { "'vin," } else { "" }; w.write(b"async fn "); w.write(&method_name); if let Some(input_type) = op.input() { if input_type == &ShapeID::new_unchecked(PRELUDE_NAMESPACE, SHAPE_STRING, None) { arg_flags = MethodArgFlags::ToString; - w.write(""); + w.write(&format!( + "<{}TS:ToString + ?Sized + std::marker::Sync>", + func_lt + )); + } else if input_lt { + w.write(b"<"); + w.write(func_lt.as_bytes()); + w.write(b">"); } } w.write(b"(&self, ctx: &Context"); @@ -879,12 +1058,28 @@ impl<'model> RustCodeGen<'model> { if matches!(arg_flags, MethodArgFlags::ToString) { w.write(b"&TS"); } else { - self.write_type(w, Ty::Ref(input_type))?; + self.write_type( + w, + Ty::Ref(input_type), + if input_lt { + Lifetime::L("vin") + } else { + Lifetime::None + }, + )?; } } w.write(b") -> RpcResult<"); if let Some(output_type) = op.output() { - self.write_type(w, Ty::Shape(output_type))?; + self.write_type( + w, + Ty::Shape(output_type), + if output_lt { + Lifetime::L("static") + } else { + Lifetime::None + }, + )?; } else { w.write(b"()"); } @@ -934,16 +1129,23 @@ impl<'model> RustCodeGen<'model> { w.write(&self.op_dispatch_name(method_ident)); w.write(b"\" => {\n"); if let Some(op_input) = op.input() { + let borrowed = self.has_lifetime(op_input); let symbol = op_input.shape_name().to_string(); - // let value : InputType = deserialize(...)?; if has_cbor { + let crate_prefix = self.get_crate_path(op_input)?; w.write(&format!( r#" - let value : {} = {}::common::decode(&message.arg, &decode_{}) + let value : {} = {}::common::{}(&message.arg, &{}decode_{}) .map_err(|e| RpcError::Deser(format!("'{}': {{}}", e)))?; "#, - self.type_string(Ty::Shape(op_input))?, + self.type_string(Ty::Shape(op_input), Lifetime::Any)?, self.import_core, + if borrowed { + "decode_borrowed" + } else { + "decode" + }, + &crate_prefix, crate::strings::to_snake_case(&symbol), &symbol, )); @@ -953,7 +1155,7 @@ impl<'model> RustCodeGen<'model> { let value: {} = {}::common::deserialize(&message.arg) .map_err(|e| RpcError::Deser(format!("'{}': {{}}", e)))?; "#, - self.type_string(Ty::Shape(op_input))?, + self.type_string(Ty::Shape(op_input), Lifetime::Any)?, self.import_core, &symbol, )) @@ -1116,14 +1318,21 @@ impl<'model> RustCodeGen<'model> { if let Some(op_output) = op.output() { let symbol = op_output.shape_name().to_string(); if has_cbor { + let crate_prefix = self.get_crate_path(op_output)?; w.write(&format!( r#" - let value : {} = {}::common::decode(&resp, &decode_{}) + let value : {} = {}::common::{}(&resp, &{}decode_{}) .map_err(|e| RpcError::Deser(format!("'{{}}': {}", e)))?; Ok(value) "#, - self.type_string(Ty::Shape(op_output))?, + self.type_string(Ty::Shape(op_output), Lifetime::L("static"))?, self.import_core, + if self.has_lifetime(op_output) { + "decode_owned" + } else { + "decode" + }, + &crate_prefix, crate::strings::to_snake_case(&symbol), &symbol, )); @@ -1134,7 +1343,7 @@ impl<'model> RustCodeGen<'model> { .map_err(|e| RpcError::Deser(format!("'{{}}': {}", e)))?; Ok(value) "#, - self.type_string(Ty::Shape(op_output))?, + self.type_string(Ty::Shape(op_output), Lifetime::Any)?, self.import_core, &symbol, )); @@ -1253,8 +1462,147 @@ impl<'model> RustCodeGen<'model> { }; Ok(ctors) } + + /// returns the Rust package prefix for the symbol, using metadata crate declarations + pub(crate) fn get_crate_path(&self, id: &ShapeID) -> Result { + let namespace = id.namespace(); + // special case for Document + if id == &ShapeID::new_unchecked(PRELUDE_NAMESPACE, "Document", None) { + return Ok("wasmbus_rpc::common::".to_string()); + } + + // no prefix required for prelude (smithy.api) or wasmbus_model, + // because they are always imported + if namespace == prelude_namespace_id() + || namespace == wasmcloud_model_namespace() + // no prefix required if namespace is the namespace of + // the file we are generating + || (self.namespace.is_some() + && namespace == self.namespace.as_ref().unwrap()) + { + return Ok(String::new()); + } + + // look up the crate name + // the crate name should be valid rust syntax. If not, they'll get an error with rustc + match self.packages.get(&namespace.to_string()) { + Some(crate::model::PackageName { + crate_name: Some(crate_name), + .. + }) => Ok(format!("{}::", crate_name)), + _ => Err(Error::Model(format!( + "undefined crate for namespace '{}' symbol '{}'. Make sure codegen.toml includes \ + all dependent namespaces, and that the dependent .smithy file contains package \ + metadata with crate: value", + namespace, + id.shape_name(), + ))), + } + } + + /// returns crate prefix for model namespace + pub(crate) fn get_model_crate(&self) -> String { + if self.namespace.is_none() + || self.namespace.as_ref().unwrap() != wasmcloud_model_namespace() + { + format!("{}::model::", &self.import_core) + } else { + String::new() + } + } + + fn build_preface(&self, id: &ShapeID, traits: &AppliedTraits) -> StructPreface { + let mut preface = StructPreface::default(); + if self.has_lifetime(id) { + preface.deserialize = false; + } + // derive(Default) is disabled for traits and enabled for all other structs + let is_trait_struct = traits.contains_key(&prelude_shape_named(TRAIT_TRAIT).unwrap()); + if is_trait_struct { + preface.default = false + } + + if let Ok(Some(cg)) = get_trait::(traits, codegen_rust_trait()) { + preface.default = !cg.no_derive_default; + preface.eq = !cg.no_derive_eq; + preface.non_exhaustive = cg.non_exhaustive; + } + preface + } } // impl CodeGenRust +/// flags used to build `#[derive(...)]` and `#[non_exhaustive]` annotations for struct and enum +struct StructPreface { + clone: bool, + copy: bool, + debug: bool, + default: bool, + eq: bool, + partial_eq: bool, + serialize: bool, + deserialize: bool, + non_exhaustive: bool, +} +impl Default for StructPreface { + fn default() -> Self { + StructPreface { + clone: true, + copy: false, + debug: true, + default: true, + eq: true, + partial_eq: true, + serialize: true, + deserialize: true, + non_exhaustive: false, + } + } +} +impl StructPreface { + /// Return `#[derive(...)]` attribute for struct or enum + fn derives(&self) -> String { + if self.clone + || self.copy + || self.debug + || self.default + || self.eq + || self.partial_eq + || self.serialize + || self.deserialize + { + let mut s = "#[derive(".to_string(); + if self.clone { + s.push_str("Clone,"); + } + if self.copy { + s.push_str("Copy,"); + } + if self.debug { + s.push_str("Debug,"); + } + if self.default { + s.push_str("Default,"); + } + if self.deserialize { + s.push_str("Deserialize,"); + } + if self.eq { + s.push_str("Eq,"); + } + if self.partial_eq { + s.push_str("PartialEq,"); + } + if self.serialize { + s.push_str("Serialize,"); + } + s.push_str(")]\n"); + s + } else { + String::new() + } + } +} + /// is_optional_type determines whether the field should be wrapped in Option<> /// the value is true if it has an explicit `box` trait, or if it's /// un-annotated and not one of (boolean, byte, short, integer, long, float, double) diff --git a/codegen/src/decode_rust.rs b/codegen/src/decode_rust.rs index c21a6cb..c428c54 100644 --- a/codegen/src/decode_rust.rs +++ b/codegen/src/decode_rust.rs @@ -1,7 +1,7 @@ //! CBOR Decode functions use crate::{ - codegen_rust::{is_optional_type, is_rust_primitive, RustCodeGen}, + codegen_rust::{is_optional_type, is_rust_primitive, Lifetime, RustCodeGen}, error::{Error, Result}, gen::CodeGen, model::{wasmcloud_model_namespace, Ty}, @@ -73,7 +73,10 @@ fn decode_big_decimal() -> &'static str { todo!() // tag big decimal } fn decode_document() -> &'static str { - "d.bytes()?" + "wasmbus_rpc::common::decode_document(d)?" +} +fn decode_unit() -> &'static str { + "d.null()?" } impl<'model> RustCodeGen<'model> { @@ -112,52 +115,19 @@ impl<'model> RustCodeGen<'model> { b"I8" => decode_byte().to_string(), b"F64" => decode_double().to_string(), b"F32" => decode_float().to_string(), - _ => { - let mut s = String::new(); - if self.namespace.is_none() - || self.namespace.as_ref().unwrap() != wasmcloud_model_namespace() - { - s.push_str(&self.import_core); - s.push_str("::model::"); - } - s.push_str(&format!( - "decode_{}(d)?", - crate::strings::to_snake_case(&id.shape_name().to_string()), - )); - s - } + _ => format!( + "{}decode_{}(d)?", + self.get_model_crate(), + crate::strings::to_snake_case(&id.shape_name().to_string()), + ), } - } else if self.namespace.is_some() && id.namespace() == self.namespace.as_ref().unwrap() { + } else { format!( - "decode_{}(d).map_err(|e| format!(\"decoding '{}': {{}}\", e))?", + "{}decode_{}(d).map_err(|e| format!(\"decoding '{}': {{}}\", e))?", + self.get_crate_path(id)?, crate::strings::to_snake_case(&id.shape_name().to_string()), - &id.shape_name().to_string() + &id.to_string() ) - } else { - match self.packages.get(&id.namespace().to_string()) { - Some(crate::model::PackageName { - crate_name: Some(crate_name), - .. - }) => { - // the crate name should be valid rust syntax. If not, they'll get an error with rustc - format!( - "{}::decode_{}(d).map_err(|e| format!(\"decoding '{}::{}': {{}}\", e))?", - &crate_name, - crate::strings::to_snake_case(&id.shape_name().to_string()), - &crate_name, - &id.shape_name().to_string() - ) - } - _ => { - return Err(Error::Model(format!( - "undefined crate for namespace {} for symbol {}. Make sure codegen.toml \ - includes all dependent namespaces, and that the dependent .smithy file \ - contains package metadata with crate: value", - &id.namespace(), - &id - ))); - } - } }; Ok(stmt) } @@ -177,35 +147,33 @@ impl<'model> RustCodeGen<'model> { Simple::Timestamp => decode_timestamp(), Simple::BigInteger => decode_big_integer(), Simple::BigDecimal => decode_big_decimal(), - Simple::Document => decode_blob(), + Simple::Document => decode_document(), } .to_string(), ShapeKind::Map(map) => { format!( r#" {{ - let mut m: std::collections::HashMap<{},{}> = std::collections::HashMap::default(); - if let Some(n) = d.map()? {{ - for _ in 0..(n as usize) {{ - let k = {}; - let v = {}; - m.insert(k,v); - }} - }} else {{ - return Err(RpcError::Deser("indefinite maps not supported".to_string())); + let map_len = d.fixed_map()? as usize; + let mut m: std::collections::HashMap<{},{}> = std::collections::HashMap::with_capacity(map_len); + for _ in 0..map_len {{ + let k = {}; + let v = {}; + m.insert(k,v); }} m }} "#, - &self.type_string(Ty::Shape(map.key().target()))?, - &self.type_string(Ty::Shape(map.value().target()))?, + &self.type_string(Ty::Shape(map.key().target()), Lifetime::Any)?, + &self.type_string(Ty::Shape(map.value().target()), Lifetime::Any)?, &self.decode_shape_id(map.key().target())?, &self.decode_shape_id(map.value().target())?, ) } ShapeKind::List(list) | ShapeKind::Set(list) => { let member_decoder = self.decode_shape_id(list.member().target())?; - let member_type = self.type_string(Ty::Shape(list.member().target()))?; + let member_type = + self.type_string(Ty::Shape(list.member().target()), Lifetime::Any)?; format!( r#" if let Some(n) = d.array()? {{ @@ -230,7 +198,13 @@ impl<'model> RustCodeGen<'model> { &member_type, &member_decoder, &member_type, self.import_core, &member_decoder, ) } - ShapeKind::Structure(strukt) => self.decode_struct(id, strukt)?, + ShapeKind::Structure(strukt) => { + if id == crate::model::unit_shape() { + decode_unit().to_string() + } else { + self.decode_struct(id, strukt)? + } + } ShapeKind::Union(union_) => self.decode_union(id, union_)?, ShapeKind::Operation(_) | ShapeKind::Resource(_) @@ -246,25 +220,41 @@ impl<'model> RustCodeGen<'model> { let mut s = format!( r#" // decoding union {} - let len = d.array()?.ok_or_else(||RpcError::Deser("decoding union '{}': indefinite array not supported".to_string()))?; + let len = d.fixed_array()?; if len != 2 {{ return Err(RpcError::Deser("decoding union '{}': expected 2-array".to_string())); }} match d.u16()? {{ "#, - enum_name, enum_name, enum_name + enum_name, enum_name, ); for field in fields.iter() { let field_num = field.field_num().unwrap(); + let target = field.target(); let field_name = self.to_type_name(&field.id().to_string()); - let field_decoder = self.decode_shape_id(field.target())?; - s.push_str(&format!( - r#" - {} => {{ - let val = {}; - {}::{}(val) - }}, - "#, - &field_num, field_decoder, enum_name, field_name - )); + if target == crate::model::unit_shape() { + s.push_str(&format!( + r#" + {} => {{ + {}; + {}::{} + }}, + "#, + &field_num, + &decode_unit(), + enum_name, + field_name + )); + } else { + let field_decoder = self.decode_shape_id(target)?; + s.push_str(&format!( + r#" + {} => {{ + let val = {}; + {}::{}(val) + }}, + "#, + &field_num, field_decoder, enum_name, field_name + )); + } } s.push_str(&format!(r#" n => {{ return Err(RpcError::Deser(format!("invalid field number for union '{}':{{}}", n))); }}, @@ -278,9 +268,13 @@ impl<'model> RustCodeGen<'model> { fn decode_struct(&self, id: &ShapeID, strukt: &StructureOrUnion) -> Result { let (fields, _is_numbered) = crate::model::get_sorted_fields(id.shape_name(), strukt)?; let mut s = String::new(); + let lt = match self.has_lifetime(id) { + true => Lifetime::L("v"), + false => Lifetime::None, + }; for field in fields.iter() { let field_name = self.to_field_name(field.id(), field.traits())?; - let field_type = self.field_type_string(field)?; + let field_type = self.field_type_string(field, lt)?; if is_optional_type(field) { // allows adding Optional fields at end of struct // and maintaining backwards compatibility to read structs @@ -303,9 +297,9 @@ impl<'model> RustCodeGen<'model> { _ => return Err(RpcError::Deser("decoding struct {}, expected array or map".to_string())) }}; if is_array {{ - let len = d.array()?.ok_or_else(||RpcError::Deser("decoding struct {}: indefinite array not supported".to_string()))?; + let len = d.fixed_array()?; for __i in 0..(len as usize) {{ - "#, self.import_core, self.import_core, id.shape_name(), id.shape_name())); + "#, self.import_core, self.import_core, id.shape_name() )); if fields.is_empty() { s.push_str( r#" @@ -352,14 +346,13 @@ impl<'model> RustCodeGen<'model> { "#, ); } - s.push_str(&format!( + s.push_str( r#" - }} - }} else {{ - let len = d.map()?.ok_or_else(||RpcError::Deser("decoding struct {}: indefinite map not supported".to_string()))?; - for __i in 0..(len as usize) {{ + } + } else { + let len = d.fixed_map()?; + for __i in 0..(len as usize) { "#, - id.shape_name()) ); if fields.is_empty() { // we think struct is empty (as of current definition), @@ -446,7 +439,7 @@ impl<'model> RustCodeGen<'model> { } /// generate decode_* function for every declared type - /// name of the function is encode_ where is the camel_case type name + /// name of the function is decode_ where is the camel_case type name /// It is generated in the module that declared type S, so it can always /// be found by prefixing the function name with the module path. pub(crate) fn declare_shape_decoder( @@ -455,6 +448,11 @@ impl<'model> RustCodeGen<'model> { id: &ShapeID, kind: &ShapeKind, ) -> Result<()> { + let has_lifetime = self.has_lifetime(id); + // since we don't have borrowing decoders yet, skip it if there are lifetimes + if has_lifetime { + return Ok(()); + } match kind { ShapeKind::Simple(_) | ShapeKind::Structure(_) @@ -468,14 +466,17 @@ impl<'model> RustCodeGen<'model> { r#" // Decode {} from cbor input stream #[doc(hidden)] {} - pub fn decode_{}(d: &mut {}::cbor::Decoder<'_>) -> Result<{},RpcError> + pub fn decode_{}{}(d: &mut {}::cbor::Decoder{}) -> Result<{}{},RpcError> {{ let __result = {{ "#, &name, if is_rust_copy { "#[inline]" } else { "" }, crate::strings::to_snake_case(&name.to_string()), + if has_lifetime { "<'v>" } else { "" }, self.import_core, - &id.shape_name() + if has_lifetime { "<'v>" } else { "<'_>" }, + self.to_type_name(&id.shape_name().to_string()), + if has_lifetime { "<'v>" } else { "" }, ); let body = self.decode_shape_kind(id, kind)?; s.push_str(&body); diff --git a/codegen/src/encode_rust.rs b/codegen/src/encode_rust.rs index 2342a9e..4a47f1f 100644 --- a/codegen/src/encode_rust.rs +++ b/codegen/src/encode_rust.rs @@ -106,7 +106,13 @@ fn encode_double(val: ValExpr) -> String { format!("e.f64({})?;\n", val.as_copy()) } fn encode_document(val: ValExpr) -> String { - format!("e.bytes({})?;\n", val.as_ref()) + format!( + "wasmbus_rpc::common::encode_document(&mut e, {})?;\n", + val.as_ref() + ) +} +fn encode_unit() -> String { + "e.null()?;\n".to_string() } fn encode_timestamp(val: ValExpr) -> String { format!( @@ -162,54 +168,21 @@ impl<'model> RustCodeGen<'model> { b"I8" => encode_byte(val), b"F64" => encode_double(val), b"F32" => encode_float(val), - _ => { - let mut s = String::new(); - if self.namespace.is_none() - || self.namespace.as_ref().unwrap() != wasmcloud_model_namespace() - { - s.push_str(&self.import_core); - s.push_str("::model::"); - } - s.push_str(&format!( - "encode_{}( e, {})?;\n", - crate::strings::to_snake_case(&id.shape_name().to_string()), - val.as_ref() - )); - s - } + _ => format!( + "{}encode_{}( e, {})?;\n", + &self.get_model_crate(), + crate::strings::to_snake_case(&id.shape_name().to_string()), + val.as_ref() + ), } - } else if self.namespace.is_some() && id.namespace() == self.namespace.as_ref().unwrap() { + } else { format!( - "encode_{}({} e, {})?;\n", + "{}encode_{}({} e, {})?;\n", + self.get_crate_path(id)?, crate::strings::to_snake_case(&id.shape_name().to_string()), if enc_owned { "&mut " } else { "" }, - val.as_ref() + val.as_ref(), ) - } else { - match self.packages.get(&id.namespace().to_string()) { - Some(crate::model::PackageName { - crate_name: Some(crate_name), - .. - }) => { - // the crate name should be valid rust syntax. If not, they'll get an error with rustc - format!( - "{}::encode_{}( {} e, {})?;\n", - &crate_name, - crate::strings::to_snake_case(&id.shape_name().to_string()), - if enc_owned { "&mut " } else { "" }, - val.as_ref(), - ) - } - _ => { - return Err(Error::Model(format!( - "undefined crate for namespace {} for symbol {}. Make sure codegen.toml \ - includes all dependent namespaces, and that the dependent .smithy file \ - contains package metadata with crate: value", - &id.namespace(), - &id - ))); - } - } }; Ok(stmt) } @@ -301,9 +274,13 @@ impl<'model> RustCodeGen<'model> { s } ShapeKind::Structure(struct_) => { - let (s, is_empty_struct) = self.encode_struct(id, struct_, val)?; - empty_struct = is_empty_struct; - s + if id != crate::model::unit_shape() { + let (s, is_empty_struct) = self.encode_struct(id, struct_, val)?; + empty_struct = is_empty_struct; + s + } else { + encode_unit() + } } ShapeKind::Union(union_) => { let (s, _) = self.encode_union(id, union_, val)?; @@ -325,15 +302,30 @@ impl<'model> RustCodeGen<'model> { val: ValExpr, ) -> Result<(String, IsEmptyStruct)> { let (fields, _) = crate::model::get_sorted_fields(id.shape_name(), strukt)?; + // FUTURE: if all variants are unit, this can be encoded as an int, not array + // .. but decoder would have to peek to distinguish array from int + //let is_all_unit = fields + // .iter() + // .all(|f| f.target() == crate::model::unit_shape()); + let is_all_unit = false; // for now, stick with array let mut s = String::new(); s.push_str(&format!("// encoding union {}\n", id.shape_name())); s.push_str("e.array(2)?;\n"); s.push_str(&format!("match {} {{\n", val.as_str())); for field in fields.iter() { + let target = field.target(); let field_name = self.to_type_name(&field.id().to_string()); - s.push_str(&format!("{}::{}(v) => {{", id.shape_name(), &field_name)); - s.push_str(&format!("e.u16({})?;\n", &field.field_num().unwrap())); - s.push_str(&self.encode_shape_id(field.target(), ValExpr::Ref("v"), false)?); + if target == crate::model::unit_shape() { + s.push_str(&format!("{}::{} => {{", id.shape_name(), &field_name)); + s.push_str(&format!("e.u16({})?;\n", &field.field_num().unwrap())); + if !is_all_unit { + s.push_str(&encode_unit()); + } + } else { + s.push_str(&format!("{}::{}(v) => {{", id.shape_name(), &field_name)); + s.push_str(&format!("e.u16({})?;\n", &field.field_num().unwrap())); + s.push_str(&self.encode_shape_id(target, ValExpr::Ref("v"), false)?); + } s.push_str("},\n"); } s.push_str("}\n"); @@ -427,8 +419,9 @@ impl<'model> RustCodeGen<'model> { let mut s = format!( r#" // Encode {} as CBOR and append to output stream - #[doc(hidden)] {} - pub fn encode_{}(e: &mut {}::cbor::Encoder, {}: &{}) -> RpcResult<()> + #[doc(hidden)] #[allow(unused_mut)] {} + pub fn encode_{}( + mut e: &mut {}::cbor::Encoder, {}: &{}) -> RpcResult<()> {{ "#, &name, diff --git a/codegen/src/model.rs b/codegen/src/model.rs index 6eed523..5dd1b93 100644 --- a/codegen/src/model.rs +++ b/codegen/src/model.rs @@ -64,6 +64,7 @@ lazy_static! { Identifier::from_str(TRAIT_RENAME).unwrap(), None ); + static ref UNIT_ID: ShapeID = ShapeID::new_unchecked(WASMCLOUD_MODEL_NAMESPACE, "Unit", None); } /// namespace for org.wasmcloud.model @@ -104,6 +105,10 @@ pub fn rename_trait() -> &'static ShapeID { &RENAME_TRAIT_ID } +pub fn unit_shape() -> &'static ShapeID { + &UNIT_ID +} + #[allow(dead_code)] pub enum CommentKind { Inner, diff --git a/codegen/src/wasmbus_model.rs b/codegen/src/wasmbus_model.rs index e1ffcf5..7d35f1a 100644 --- a/codegen/src/wasmbus_model.rs +++ b/codegen/src/wasmbus_model.rs @@ -1,9 +1,10 @@ -// This file is generated automatically using wasmcloud/weld-codegen 0.4.0 +// This file is generated automatically using wasmcloud/weld-codegen 0.4.3 #[allow(unused_imports)] use minicbor::{encode::Write, Encode}; #[allow(unused_imports)] use serde::{Deserialize, Serialize}; +#[allow(dead_code)] pub const SMITHY_VERSION: &str = "1.0"; /// Capability contract id, e.g. 'wasmcloud:httpserver' @@ -57,6 +58,10 @@ pub struct CodegenRust { #[serde(rename = "nonExhaustive")] #[serde(default)] pub non_exhaustive: bool, + /// if true, do not generate code for this item. + /// This trait can be used if an item needs to be hand-generated + #[serde(default)] + pub skip: bool, } /// indicates that a trait or class extends one or more bases diff --git a/codegen/src/writer.rs b/codegen/src/writer.rs index e09cff2..fee2108 100644 --- a/codegen/src/writer.rs +++ b/codegen/src/writer.rs @@ -15,6 +15,16 @@ impl Writer { pub fn take(&mut self) -> BytesMut { self.writer.split_to(self.writer.len()) } + + /// Returns current position + pub fn pos(&self) -> usize { + self.writer.len() + } + + /// Returns slice from writer + pub fn get_slice(&self, start_pos: usize, end_pos: usize) -> &[u8] { + &self.writer[start_pos..end_pos] + } } pub trait ToBytes { diff --git a/rpc-rs/Cargo.toml b/rpc-rs/Cargo.toml index 80d21eb..4221aa9 100644 --- a/rpc-rs/Cargo.toml +++ b/rpc-rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasmbus-rpc" -version = "0.8.3" +version = "0.8.4" authors = [ "wasmcloud Team" ] license = "Apache-2.0" description = "Runtime library for actors and capability providers" @@ -63,4 +63,4 @@ regex = "1" env_logger = "0.9.0" [build-dependencies] -weld-codegen = { version = "0.4.1", path = "../codegen" } +weld-codegen = { version = "0.4.3", path = "../codegen" } diff --git a/rpc-rs/codegen.toml b/rpc-rs/codegen.toml index 6326159..21cf467 100644 --- a/rpc-rs/codegen.toml +++ b/rpc-rs/codegen.toml @@ -6,7 +6,7 @@ url = "https://cdn.jsdelivr.net/gh/wasmcloud/interfaces@d6ae2dd196aae3c2486e747e [[models]] # wasmbus-model -url = "https://cdn.jsdelivr.net/gh/wasmcloud/interfaces@cb504c8b7cfcee0a8c5ecdedf75757f4718ad4e6/core/wasmcloud-model.smithy" +url = "https://cdn.jsdelivr.net/gh/wasmcloud/interfaces@e0f205da8a0e1549497571c3e994a1851480621c/core/wasmcloud-model.smithy" [rust] output_dir = "." diff --git a/rpc-rs/src/cbor.rs b/rpc-rs/src/cbor.rs index 1e344a9..979f968 100644 --- a/rpc-rs/src/cbor.rs +++ b/rpc-rs/src/cbor.rs @@ -104,12 +104,26 @@ impl<'b> Decoder<'b> { Ok(self.inner.array()?) } + /// Begin decoding an array of known length + pub fn fixed_array(&mut self) -> RpcResult { + self.inner + .array()? + .ok_or_else(|| RpcError::Deser("indefinite array not expected".to_string())) + } + /// Begin decoding a map. If the size is known, /// it is returned as `Some`. For indefinite maps, `None` is returned. pub fn map(&mut self) -> RpcResult> { Ok(self.inner.map()?) } + /// Begin decoding a map of known size + pub fn fixed_map(&mut self) -> RpcResult { + self.inner + .map()? + .ok_or_else(|| RpcError::Deser("indefinite map not expected".to_string())) + } + /// Inspect the CBOR type at the current position pub fn datatype(&mut self) -> RpcResult { Ok(self.inner.datatype()?.into()) @@ -126,7 +140,7 @@ impl<'b> Decoder<'b> { // around a cbor implementation. This function breaks that abstraction, // and any use of it outside the wasmbus-rpc crate risks breaking // if there is a change to the underlying implementation. - //#[hidden] + //#[doc(hidden)] //pub(crate) fn inner(&mut self) -> &mut minicbor::Decoder { // &self.inner //} @@ -138,72 +152,40 @@ impl<'b> Debug for Decoder<'b> { } } -// A type that accepts byte slices for writing -//pub trait Write { -// type Error: std::fmt::Display; -// fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error>; -//} - -/* -impl Write for Vec { - type Error = std::convert::Infallible; - - fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { - self.extend_from_slice(buf); - Ok(()) - } -} - */ - -//struct WriteX { -// writer: Box>, -//} - -//impl WriteX { -// pub fn new(writer: Box>) -> Self { -// Self { writer } -// } -//} - -//impl Write for WriteX { -// type Error = RpcError; -// -// fn write_all(&mut self, buf: &[u8]) -> Result<(), RpcError> { -// self.writer -// .write_all(buf) -// .map_err(|e| RpcError::Ser(format!("encoder write: {}", &e.to_string()))) -// } -//} -// -//impl minicbor::encode::write::Write for WriteX { -// type Error = RpcError; -// -// fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { -// self.writer -// .write_all(buf) -// .map_err(|e| RpcError::Ser(format!("encoding: {}", e))) -// } -//} - -/* -impl Write for Vec { - type Error = std::convert::Infallible; - - fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { - self.extend_from_slice(buf); - Ok(()) +/// A type that can be decoded from CBOR. +pub trait Decode<'b>: Sized { + /// Decode a value using the given `Decoder`. + fn decode(d: &mut Decoder<'b>) -> Result; + + /// If possible, return a nil value of `Self`. + /// + /// This method is primarily used by `minicbor-derive` and allows + /// creating a special value denoting the absence of a "real" value if + /// no CBOR value is present. The canonical example of a type where + /// this is sensible is the `Option` type, whose `Decode::nil` method + /// would return `Some(None)`. + /// + /// With the exception of `Option<_>` all types `T` are considered + /// mandatory by default, i.e. `T::nil()` returns `None`. Missing values + /// of `T` therefore cause decoding errors in derived `Decode` + /// implementations. + /// + /// NB: A type implementing `Decode` with an overriden `Decode::nil` + /// method should also override `Encode::is_nil` if it implements `Encode` + /// at all. + fn nil() -> Option { + None } } - */ +pub trait MDecodeOwned: for<'de> crate::minicbor::Decode<'de> {} +impl MDecodeOwned for T where T: for<'de> crate::minicbor::Decode<'de> {} + +pub trait DecodeOwned: for<'de> crate::cbor::Decode<'de> {} +impl DecodeOwned for T where T: for<'de> crate::cbor::Decode<'de> {} -//impl Write for W { -// type Error = std::io::Error; -// -// fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { -// std::io::Write::write_all(self, buf) -// } -//} +//pub trait DecodeOwned: for<'de> Decode<'de> {} +//impl DecodeOwned for T where T: for<'de> Decode<'de> {} pub use minicbor::encode::Write; @@ -377,13 +359,19 @@ where self.inner.into_inner() } + /// Write a tag + pub fn tag(&mut self, tag: u64) -> RpcResult<&mut Self> { + self.inner.tag(minicbor::data::Tag::Unassigned(tag))?; + Ok(self) + } + // Pierce the veil. // This module exposes public functions to support code generated // by `weld-codegen`. Its purpose is to create an abstraction layer // around a cbor implementation. This function breaks that abstraction, // and any use of it outside the wasmbus-rpc crate risks breaking // if there is a change to the underlying implementation. - //#[hidden] + //#[doc(hidden)] //pub(crate) fn inner(&mut self) -> &mut minicbor::Encoder { // &self.inner //} @@ -415,8 +403,8 @@ pub enum Type { ArrayIndef, Map, MapIndef, - Tag, Break, + Tag, Unknown(u8), } diff --git a/rpc-rs/src/common.rs b/rpc-rs/src/common.rs index 89005ae..c75e750 100644 --- a/rpc-rs/src/common.rs +++ b/rpc-rs/src/common.rs @@ -3,6 +3,11 @@ use async_trait::async_trait; use serde::{Deserialize, Serialize}; use std::{borrow::Cow, fmt}; +pub use crate::document::{ + decode_document, decode_number, encode_document, encode_document_ref, encode_number, Document, + DocumentRef, Number, +}; + /// A wasmcloud message #[derive(Debug)] pub struct Message<'m> { @@ -154,6 +159,8 @@ pub fn message_format(data: &[u8]) -> (MessageFormat, usize) { pub type CborDecodeFn = dyn Fn(&mut crate::cbor::Decoder<'_>) -> RpcResult; +pub type CborDecodeFnLt<'de, T> = dyn Fn(&mut crate::cbor::Decoder<'de>) -> RpcResult; + pub fn decode( buf: &[u8], cbor_dec: &CborDecodeFn, @@ -165,13 +172,46 @@ pub fn decode( } (MessageFormat::Msgpack, offset) => deserialize(&buf[offset..]) .map_err(|e| RpcError::Deser(format!("decoding '{}': {{}}", e)))?, - _ => return Err(RpcError::Deser("invalid encoding for '{}'".to_string())), + _ => return Err(RpcError::Deser("invalid encoding".to_string())), }; Ok(value) } -pub trait DecodeOwned: for<'de> crate::minicbor::Decode<'de> {} -impl DecodeOwned for T where T: for<'de> crate::minicbor::Decode<'de> {} +pub fn decode_borrowed<'de, T>( + buf: &'de [u8], + cbor_dec: &'de CborDecodeFnLt<'de, T>, +) -> RpcResult { + match message_format(buf) { + (MessageFormat::Cbor, offset) => { + let d = &mut crate::cbor::Decoder::new(&buf[offset..]); + Ok(cbor_dec(d)?) + } + _ => Err(RpcError::Deser("invalid encoding (borrowed)".to_string())), + } +} + +pub fn decode_owned<'vin, T: Clone>( + buf: &'vin [u8], + cbor_dec: &CborDecodeFnLt<'vin, T>, +) -> RpcResult { + match message_format(buf) { + (MessageFormat::Cbor, offset) => { + let d = &mut crate::cbor::Decoder::new(&buf[offset..]); + let out: T = cbor_dec(d)?; + Ok(out) + } + _ => Err(RpcError::Deser("invalid encoding (borrowed)".to_string())), + } +} + +/* +pub fn decode_owned(d: &mut crate::cbor::Decoder) -> RpcResult +where + T: crate::cbor::DecodeOwned + Sized, +{ + T::decode(d) // .map_err(|e| RpcError::Deser(e.to_string())) +} + */ /// Wasmbus rpc sender that can send any message and cbor-serializable payload /// requires Protocol="2" @@ -186,7 +226,7 @@ impl AnySender { } impl AnySender { - /// Send enoded payload + /// Send encoded payload #[inline] async fn send_raw<'s, 'ctx, 'msg>( &'s self, @@ -221,7 +261,7 @@ impl AnySender { } /// Send rpc with serializable payload using cbor encode/decode - pub async fn send_cbor<'de, In: crate::minicbor::Encode, Out: DecodeOwned>( + pub async fn send_cbor<'de, In: crate::minicbor::Encode, Out: crate::cbor::MDecodeOwned>( &self, ctx: &Context, method: &str, @@ -244,3 +284,5 @@ impl AnySender { Ok(result) } } + +pub type Unit = (); diff --git a/rpc-rs/src/document.rs b/rpc-rs/src/document.rs new file mode 100644 index 0000000..e182d9e --- /dev/null +++ b/rpc-rs/src/document.rs @@ -0,0 +1,875 @@ +use crate::error::{RpcError, RpcResult}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Document Type +/// +/// Document types represents protocol-agnostic open content that is accessed like JSON data. +/// Open content is useful for modeling unstructured data that has no schema, data that can't be +/// modeled using rigid types, or data that has a schema that evolves outside of the purview of a model. +/// The serialization format of a document is an implementation detail of a protocol. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Document { + /// object + // n(0) + Object(HashMap), + /// Blob + // n(1) + Blob(Vec), + /// array + // n(2) + Array(Vec), + /// number + // n(3) + Number(Number), + /// UTF8 String + // n(4) + String(String), + /// boolean + // n(5) + Bool(bool), + /// null + // n(6) + Null, +} + +impl Default for Document { + fn default() -> Self { + Document::Null + } +} + +/// Borrowed Document Type +/// +/// Document types represents protocol-agnostic open content that is accessed like JSON data. +/// Open content is useful for modeling unstructured data that has no schema, data that can't be +/// modeled using rigid types, or data that has a schema that evolves outside of the purview of a model. +/// The serialization format of a document is an implementation detail of a protocol. +#[derive(Clone, Debug, PartialEq, Serialize)] +pub enum DocumentRef<'v> { + /// object + // n(0) + Object(HashMap>), + /// Blob + // n(1) + Blob(&'v [u8]), + /// array + // n(2) + Array(Vec>), + /// number + // n(3) + Number(Number), + /// UTF8 String + // n(4) + String(&'v str), + /// boolean + // n(5) + Bool(bool), + /// null + // n(6) + Null, +} + +impl Default for DocumentRef<'_> { + fn default() -> Self { + DocumentRef::Null + } +} + +impl<'v> DocumentRef<'v> { + pub fn to_owned(&self) -> Document { + match self { + DocumentRef::Object(o) => Document::Object( + o.iter() + .map(|(k, v)| (k.to_owned(), v.to_owned())) + .collect(), + ), + DocumentRef::Blob(b) => Document::Blob(b.to_vec()), + DocumentRef::Array(a) => Document::Array(a.iter().map(|v| v.to_owned()).collect()), + DocumentRef::Number(n) => Document::Number(*n), + DocumentRef::String(s) => Document::String((*s).into()), + DocumentRef::Bool(b) => Document::Bool(*b), + DocumentRef::Null => Document::Null, + } + } +} + +impl Document { + pub fn as_ref(&self) -> DocumentRef<'_> { + match self { + Document::Object(o) => { + DocumentRef::Object(o.iter().map(|(k, v)| (k.to_string(), v.as_ref())).collect()) + } + Document::Blob(b) => DocumentRef::Blob(b.as_ref()), + Document::Array(a) => DocumentRef::Array(a.iter().map(|v| v.as_ref()).collect()), + Document::Number(n) => DocumentRef::Number(*n), + Document::String(s) => DocumentRef::String(s.as_str()), + Document::Bool(b) => DocumentRef::Bool(*b), + Document::Null => DocumentRef::Null, + } + } +} + +impl Document { + /// Returns the map, if Document is an Object, otherwise None + pub fn to_object(self) -> Option> { + if let Document::Object(val) = self { + Some(val) + } else { + None + } + } + + /// Returns reference to the map, if Document is an Object, otherwise None + pub fn as_object(&self) -> Option<&HashMap> { + if let Document::Object(val) = self { + Some(val) + } else { + None + } + } + + /// returns true if the Document is an object + pub fn is_object(&self) -> bool { + self.as_object().is_some() + } + + /// Returns the blob, if Document is an Blob, otherwise None + pub fn as_blob(&self) -> Option<&[u8]> { + if let Document::Blob(val) = self { + Some(val) + } else { + None + } + } + + /// Returns the blob, if Document is an Blob, otherwise None + pub fn to_blob(self) -> Option> { + if let Document::Blob(val) = self { + Some(val) + } else { + None + } + } + + /// returns true if the Document is a blob (byte array) + pub fn is_blob(&self) -> bool { + self.as_blob().is_some() + } + + /// Returns the array, if Document is an Array, otherwise None + pub fn as_array(&self) -> Option<&Vec> { + if let Document::Array(val) = self { + Some(val) + } else { + None + } + } + + /// Returns the array, if Document is an Array, otherwise None + pub fn to_array(self) -> Option> { + if let Document::Array(val) = self { + Some(val) + } else { + None + } + } + + /// returns true if the Document is an array + pub fn is_array(&self) -> bool { + self.as_array().is_some() + } + + /// Returns the Number, if Document is a Number, otherwise None + pub fn as_number(&self) -> Option { + if let Document::Number(val) = self { + Some(*val) + } else { + None + } + } + + pub fn to_number(self) -> Option { + self.as_number() + } + + /// returns true if the Document is a number (signed/unsigned int or float) + pub fn is_number(&self) -> bool { + self.as_number().is_some() + } + + /// Returns the positive int, if Document is a positive int, otherwise None + pub fn as_pos_int(&self) -> Option { + if let Document::Number(Number::PosInt(val)) = self { + Some(*val) + } else { + None + } + } + + pub fn to_pos_int(self) -> Option { + self.as_pos_int() + } + + /// returns true if the Document is a positive integer + pub fn is_pos_int(&self) -> bool { + self.as_pos_int().is_some() + } + + /// Returns the signed int, if Document is a signed int, otherwise None + pub fn as_int(&self) -> Option { + if let Document::Number(Number::NegInt(val)) = self { + Some(*val) + } else { + None + } + } + + pub fn to_int(self) -> Option { + self.as_int() + } + + /// returns true if the Document is a signed integer + pub fn is_int(&self) -> bool { + self.as_int().is_some() + } + + /// Returns the float value, if Document is a float value, otherwise None + pub fn as_float(&self) -> Option { + if let Document::Number(Number::Float(val)) = self { + Some(*val) + } else { + None + } + } + + pub fn to_float(self) -> Option { + (&self).as_float() + } + + /// returns true if the Document is a float + pub fn is_float(&self) -> bool { + self.as_float().is_some() + } + + /// Returns the bool value, if Document is a bool value, otherwise None + pub fn as_bool(&self) -> Option { + if let Document::Bool(val) = self { + Some(*val) + } else { + None + } + } + + pub fn to_bool(self) -> Option { + self.as_bool() + } + + /// returns true if the Document is a boolean + pub fn is_bool(&self) -> bool { + self.as_bool().is_some() + } + + /// Returns borrowed str, if Document is a string value, otherwise None + pub fn as_str(&self) -> Option<&str> { + if let Document::String(val) = self { + Some(val.as_ref()) + } else { + None + } + } + + /// returns owned String, if Document is a String value + pub fn to_string(self) -> Option { + if let Document::String(val) = self { + Some(val) + } else { + None + } + } + + /// returns true if the Document is a string type + pub fn is_str(&self) -> bool { + self.as_str().is_some() + } + + /// returns true if the Document is null + pub fn is_null(&self) -> bool { + matches!(self, Document::Null) + } + + pub fn from_null() -> Self { + Document::Null + } +} + +impl<'v> DocumentRef<'v> { + /// Returns the map, if Document is an Object, otherwise None + pub fn to_object(self) -> Option>> { + if let DocumentRef::Object(val) = self { + Some(val) + } else { + None + } + } + + /// Returns reference to the map, if Document is an Object, otherwise None + pub fn as_object(&self) -> Option<&HashMap>> { + if let DocumentRef::Object(val) = self { + Some(val) + } else { + None + } + } + + /// returns true if the Document is an object + pub fn is_object(&self) -> bool { + self.as_object().is_some() + } + + /// Returns the blob, if Document is an Blob, otherwise None + pub fn as_blob(&self) -> Option<&'v [u8]> { + if let DocumentRef::Blob(val) = self { + Some(val) + } else { + None + } + } + + /// returns true if the Document is a blob (byte array) + pub fn is_blob(&self) -> bool { + self.as_blob().is_some() + } + + /// Returns the array, if Document is an Array, otherwise None + pub fn as_array(&self) -> Option<&Vec>> { + if let DocumentRef::Array(val) = self { + Some(val) + } else { + None + } + } + + /// Returns the array, if Document is an Array, otherwise None + pub fn to_array(self) -> Option>> { + if let DocumentRef::Array(val) = self { + Some(val) + } else { + None + } + } + + /// returns true if the Document is an array + pub fn is_array(&self) -> bool { + self.as_array().is_some() + } + + /// Returns the Number, if Document is a Number, otherwise None + pub fn as_number(&self) -> Option { + if let DocumentRef::Number(val) = self { + Some(*val) + } else { + None + } + } + + pub fn to_number(self) -> Option { + self.as_number() + } + + /// returns true if the Document is a number (signed/unsigned int or float) + pub fn is_number(&self) -> bool { + self.as_number().is_some() + } + + /// Returns the positive int, if Document is a positive int, otherwise None + pub fn as_pos_int(&self) -> Option { + if let DocumentRef::Number(Number::PosInt(val)) = self { + Some(*val) + } else { + None + } + } + + pub fn to_pos_int(self) -> Option { + self.as_pos_int() + } + + /// returns true if the Document is a positive integer + pub fn is_pos_int(&self) -> bool { + self.as_pos_int().is_some() + } + + /// Returns the signed int, if Document is a signed int, otherwise None + pub fn as_int(&self) -> Option { + if let DocumentRef::Number(Number::NegInt(val)) = self { + Some(*val) + } else { + None + } + } + + pub fn to_int(self) -> Option { + self.as_int() + } + + /// returns true if the Document is a signed integer + pub fn is_int(&self) -> bool { + self.as_int().is_some() + } + + /// Returns the float value, if Document is a float value, otherwise None + pub fn as_float(&self) -> Option { + if let DocumentRef::Number(Number::Float(val)) = self { + Some(*val) + } else { + None + } + } + + pub fn to_float(self) -> Option { + (&self).as_float() + } + + /// returns true if the Document is a float + pub fn is_float(&self) -> bool { + self.as_float().is_some() + } + + /// Returns the bool value, if Document is a bool value, otherwise None + pub fn as_bool(&self) -> Option { + if let DocumentRef::Bool(val) = self { + Some(*val) + } else { + None + } + } + + pub fn to_bool(self) -> Option { + self.as_bool() + } + + /// returns true if the Document is a boolean + pub fn is_bool(&self) -> bool { + self.as_bool().is_some() + } + + /// Returns borrowed str, if Document is a string value, otherwise None + pub fn as_str(&self) -> Option<&str> { + if let DocumentRef::String(val) = self { + Some(val) + } else { + None + } + } + + /// returns owned String, if Document is a String value + pub fn to_string(self) -> Option { + if let DocumentRef::String(val) = self { + Some(val.to_string()) + } else { + None + } + } + + /// returns true if the Document is a string type + pub fn is_str(&self) -> bool { + self.as_str().is_some() + } + + /// returns true if the Document is null + pub fn is_null(&self) -> bool { + matches!(self, DocumentRef::Null) + } + + pub fn from_null() -> Self { + DocumentRef::Null + } +} + +/// A number type that implements Javascript / JSON semantics, modeled on serde_json: +/// +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub enum Number { + /// Unsigned 64-bit integer value + // n(0) + PosInt(u64), + /// Signed 64-bit integer value + // n(1) + NegInt(i64), + /// 64-bit floating-point value + // n(2) + Float(f64), +} + +macro_rules! to_num_fn { + ($name:ident, $typ:ident) => { + /// Converts to a `$typ`. This conversion may be lossy. + impl Number { + pub fn $name(self) -> $typ { + match self { + Number::PosInt(val) => val as $typ, + Number::NegInt(val) => val as $typ, + Number::Float(val) => val as $typ, + } + } + } + }; +} + +macro_rules! from_num_fn { + ($typ:ident, $nt:ident) => { + impl From<$typ> for Document { + fn from(val: $typ) -> Self { + Document::Number(Number::$nt(val.into())) + } + } + impl From<$typ> for DocumentRef<'_> { + fn from(val: $typ) -> Self { + DocumentRef::Number(Number::$nt(val.into())) + } + } + }; +} + +macro_rules! impl_try_from { + ($t:ty, $p: ident) => { + impl TryFrom for $t { + type Error = Document; + + fn try_from(val: Document) -> Result<$t, Self::Error> { + match val { + Document::$p(v) => Ok(v), + v => Err(v), + } + } + } + }; +} + +macro_rules! impl_try_from_num { + ($t:ty, $n:ident) => { + impl TryFrom for $t { + type Error = Document; + + fn try_from(val: Document) -> Result<$t, Self::Error> { + match val { + Document::Number(Number::$n(v)) => Ok(v), + v => Err(v), + } + } + } + }; +} + +macro_rules! impl_try_from_ref { + ($t:ty, $p: ident) => { + impl<'v> TryFrom> for $t { + type Error = DocumentRef<'v>; + + fn try_from(val: DocumentRef<'v>) -> Result<$t, Self::Error> { + match val { + DocumentRef::$p(v) => Ok(v), + v => Err(v), + } + } + } + }; +} + +macro_rules! impl_try_from_ref_num { + ($t:ty, $n:ident) => { + impl<'v> TryFrom> for $t { + type Error = DocumentRef<'v>; + + fn try_from(val: DocumentRef<'v>) -> Result<$t, Self::Error> { + match val { + DocumentRef::Number(Number::$n(v)) => Ok(v), + v => Err(v), + } + } + } + }; +} + +macro_rules! impl_from { + ($t:ty, $p:ident) => { + impl From<$t> for Document { + fn from(val: $t) -> Self { + Document::$p(val) + } + } + }; +} +macro_rules! impl_from_ref { + ($t:ty, $p:ident) => { + impl<'v> From<$t> for DocumentRef<'v> { + fn from(val: $t) -> Self { + DocumentRef::$p(val) + } + } + }; +} + +to_num_fn!(to_f32, f32); +to_num_fn!(to_f64, f64); +to_num_fn!(to_i8, i8); +to_num_fn!(to_i16, i16); +to_num_fn!(to_i32, i32); +to_num_fn!(to_i64, i64); +to_num_fn!(to_u8, u8); +to_num_fn!(to_u16, u16); +to_num_fn!(to_u32, u32); +to_num_fn!(to_u64, u64); + +from_num_fn!(u64, PosInt); +from_num_fn!(u32, PosInt); +from_num_fn!(u16, PosInt); +from_num_fn!(u8, PosInt); +from_num_fn!(i64, NegInt); +from_num_fn!(i32, NegInt); +from_num_fn!(i16, NegInt); +from_num_fn!(i8, NegInt); +from_num_fn!(f64, Float); +from_num_fn!(f32, Float); + +impl_try_from!(HashMap, Object); +impl_try_from!(Vec, Blob); +impl_try_from!(Vec, Array); +impl_try_from!(String, String); +impl_try_from!(bool, Bool); + +impl_try_from_num!(u64, PosInt); +impl_try_from_num!(i64, NegInt); +impl_try_from_num!(f64, Float); + +impl_try_from_ref!(HashMap>, Object); +impl_try_from_ref!(&'v [u8], Blob); +impl_try_from_ref!(Vec>, Array); +impl_try_from_ref!(&'v str, String); +impl_try_from_ref!(bool, Bool); + +impl_try_from_ref_num!(u64, PosInt); +impl_try_from_ref_num!(i64, NegInt); +impl_try_from_ref_num!(f64, Float); + +impl_from!(HashMap, Object); +impl_from!(Vec, Blob); +impl_from!(Vec, Array); +impl_from!(String, String); +impl_from!(bool, Bool); + +impl_from_ref!(HashMap>, Object); +impl_from_ref!(&'v [u8], Blob); +impl_from_ref!(Vec>, Array); +impl_from_ref!(&'v str, String); +impl_from_ref!(bool, Bool); + +impl FromIterator<(String, Document)> for Document { + fn from_iter>(iter: I) -> Self { + let o: HashMap = iter.into_iter().collect(); + Document::Object(o) + } +} + +impl FromIterator for Document { + fn from_iter>(iter: I) -> Self { + let a: Vec = iter.into_iter().collect(); + Document::Array(a) + } +} + +/// Encode Document as cbor +#[doc(hidden)] +pub fn encode_document( + e: &mut crate::cbor::Encoder, + val: &Document, +) -> RpcResult<()> { + e.array(2)?; + match val { + Document::Object(map) => { + e.u8(0)?; + e.map(map.len() as u64)?; + for (k, v) in map.iter() { + e.str(k.as_str())?; + encode_document(e, v)?; + } + } + Document::Blob(blob) => { + e.u8(1)?; + e.bytes(blob)?; + } + Document::Array(vec) => { + e.u8(2)?; + e.array(vec.len() as u64)?; + for v in vec.iter() { + encode_document(e, v)?; + } + } + Document::Number(n) => { + e.u8(3)?; + encode_number(e, n)?; + } + Document::String(s) => { + e.u8(4)?; + e.str(s)?; + } + Document::Bool(b) => { + e.u8(5)?; + e.bool(*b)?; + } + Document::Null => { + e.u8(6)?; + e.null()?; + } + } + Ok(()) +} +/// Encode DocumentRef as cbor +#[doc(hidden)] +pub fn encode_document_ref<'v, W: crate::cbor::Write>( + e: &mut crate::cbor::Encoder, + val: &DocumentRef<'v>, +) -> RpcResult<()> { + e.array(2)?; + match val { + DocumentRef::Object(map) => { + e.u8(0)?; + e.map(map.len() as u64)?; + for (k, v) in map.iter() { + e.str(k.as_str())?; + encode_document_ref(e, v)?; + } + } + DocumentRef::Blob(blob) => { + e.u8(1)?; + e.bytes(blob)?; + } + DocumentRef::Array(vec) => { + e.u8(2)?; + e.array(vec.len() as u64)?; + for v in vec.iter() { + encode_document_ref(e, v)?; + } + } + DocumentRef::Number(n) => { + e.u8(3)?; + encode_number(e, n)?; + } + DocumentRef::String(s) => { + e.u8(4)?; + e.str(s)?; + } + DocumentRef::Bool(b) => { + e.u8(5)?; + e.bool(*b)?; + } + DocumentRef::Null => { + e.u8(6)?; + e.null()?; + } + } + Ok(()) +} + +/// Encode Number as cbor +#[doc(hidden)] +pub fn encode_number( + e: &mut crate::cbor::Encoder, + val: &Number, +) -> RpcResult<()> { + e.array(2)?; + match val { + Number::PosInt(val) => { + e.u8(0)?; + e.u64(*val)?; + } + Number::NegInt(val) => { + e.u8(1)?; + e.i64(*val)?; + } + Number::Float(val) => { + e.u8(2)?; + e.f64(*val)?; + } + } + Ok(()) +} + +#[doc(hidden)] +pub fn decode_document(d: &mut crate::cbor::Decoder<'_>) -> RpcResult { + let len = d.fixed_array()?; + if len != 2 { + return Err(RpcError::Deser("invalid Document".to_string())); + } + match d.u8()? { + 0 => { + // Object + let map_len = d.fixed_map()? as usize; + let mut map = std::collections::HashMap::with_capacity(map_len); + for _ in 0..map_len { + let k = d.str()?.to_string(); + let v = decode_document(d)?; + map.insert(k, v); + } + Ok(Document::Object(map)) + } + 1 => { + // Blob + Ok(Document::Blob(d.bytes()?.to_vec())) + } + 2 => { + // Array + let arr_len = d.fixed_array()? as usize; + let mut arr = Vec::with_capacity(arr_len); + for _ in 0..arr_len { + arr.push(decode_document(d)?); + } + Ok(Document::Array(arr)) + } + 3 => { + // Number + Ok(Document::Number(decode_number(d)?)) + } + 4 => { + // String + Ok(Document::String(d.str()?.into())) + } + 5 => { + // Bool + Ok(Document::Bool(d.bool()?)) + } + 6 => { + // Null + d.null()?; + Ok(Document::Null) + } + _ => Err(RpcError::Deser("invalid Document field".to_string())), + } +} + +impl<'b> crate::cbor::Decode<'b> for Document { + fn decode(d: &mut crate::cbor::Decoder<'b>) -> Result { + decode_document(d) + } +} + +#[doc(hidden)] +pub fn decode_number(d: &mut crate::cbor::Decoder) -> RpcResult { + let len = d.fixed_array()?; + if len != 2 { + return Err(RpcError::Deser("invalid Number".into())); + } + match d.u8()? { + 0 => Ok(Number::PosInt(d.u64()?)), + 1 => Ok(Number::NegInt(d.i64()?)), + 2 => Ok(Number::Float(d.f64()?)), + _ => Err(RpcError::Deser("invalid Number field".to_string())), + } +} + +impl<'b> crate::cbor::Decode<'b> for Number { + fn decode(d: &mut crate::cbor::Decoder<'b>) -> Result { + decode_number(d) + } +} diff --git a/rpc-rs/src/lib.rs b/rpc-rs/src/lib.rs index f2e5346..a85fe81 100644 --- a/rpc-rs/src/lib.rs +++ b/rpc-rs/src/lib.rs @@ -16,8 +16,12 @@ mod wasmbus_model; pub mod model { // re-export model lib as "model" pub use crate::wasmbus_model::*; + + // declare unit type + pub type Unit = (); } pub mod cbor; +pub(crate) mod document; pub mod error; // re-export nats-aflowt diff --git a/rpc-rs/src/wasmbus_core.rs b/rpc-rs/src/wasmbus_core.rs index 45ff40b..6b2f4b0 100644 --- a/rpc-rs/src/wasmbus_core.rs +++ b/rpc-rs/src/wasmbus_core.rs @@ -1,4 +1,4 @@ -// This file is generated automatically using wasmcloud/weld-codegen 0.4.2 +// This file is generated automatically using wasmcloud/weld-codegen 0.4.3 #[allow(unused_imports)] use crate::{ @@ -17,6 +17,7 @@ use serde::{Deserialize, Serialize}; #[allow(unused_imports)] use std::{borrow::Borrow, borrow::Cow, io::Write, string::ToString}; +#[allow(dead_code)] pub const SMITHY_VERSION: &str = "1.0"; /// List of linked actors for a provider @@ -24,8 +25,9 @@ pub type ActorLinks = Vec; // Encode ActorLinks as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] pub fn encode_actor_links( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &ActorLinks, ) -> RpcResult<()> { e.array(val.len() as u64)?; @@ -38,40 +40,40 @@ pub fn encode_actor_links( // Decode ActorLinks from cbor input stream #[doc(hidden)] pub fn decode_actor_links(d: &mut crate::cbor::Decoder<'_>) -> Result { - let __result = { - if let Some(n) = d.array()? { - let mut arr: Vec = Vec::with_capacity(n as usize); - for _ in 0..(n as usize) { - arr.push( - decode_link_definition(d) - .map_err(|e| format!("decoding 'LinkDefinition': {}", e))?, - ) - } - arr - } else { - // indefinite array - let mut arr: Vec = Vec::new(); - loop { - match d.datatype() { - Err(_) => break, - Ok(crate::cbor::Type::Break) => break, - Ok(_) => arr.push( - decode_link_definition(d) - .map_err(|e| format!("decoding 'LinkDefinition': {}", e))?, - ), + let __result = + { + if let Some(n) = d.array()? { + let mut arr: Vec = Vec::with_capacity(n as usize); + for _ in 0..(n as usize) { + arr.push(decode_link_definition(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#LinkDefinition': {}", e) + })?) } + arr + } else { + // indefinite array + let mut arr: Vec = Vec::new(); + loop { + match d.datatype() { + Err(_) => break, + Ok(crate::cbor::Type::Break) => break, + Ok(_) => arr.push(decode_link_definition(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#LinkDefinition': {}", e) + })?), + } + } + arr } - arr - } - }; + }; Ok(__result) } pub type ClusterIssuerKey = String; // Encode ClusterIssuerKey as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] pub fn encode_cluster_issuer_key( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &ClusterIssuerKey, ) -> RpcResult<()> { e.str(val)?; @@ -90,8 +92,9 @@ pub type ClusterIssuers = Vec; // Encode ClusterIssuers as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] pub fn encode_cluster_issuers( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &ClusterIssuers, ) -> RpcResult<()> { e.array(val.len() as u64)?; @@ -110,10 +113,9 @@ pub fn decode_cluster_issuers( if let Some(n) = d.array()? { let mut arr: Vec = Vec::with_capacity(n as usize); for _ in 0..(n as usize) { - arr.push( - decode_cluster_issuer_key(d) - .map_err(|e| format!("decoding 'ClusterIssuerKey': {}", e))?, - ) + arr.push(decode_cluster_issuer_key(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#ClusterIssuerKey': {}", e) + })?) } arr } else { @@ -123,10 +125,9 @@ pub fn decode_cluster_issuers( match d.datatype() { Err(_) => break, Ok(crate::cbor::Type::Break) => break, - Ok(_) => arr.push( - decode_cluster_issuer_key(d) - .map_err(|e| format!("decoding 'ClusterIssuerKey': {}", e))?, - ), + Ok(_) => arr.push(decode_cluster_issuer_key(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#ClusterIssuerKey': {}", e) + })?), } } arr @@ -140,8 +141,9 @@ pub struct HealthCheckRequest {} // Encode HealthCheckRequest as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] pub fn encode_health_check_request( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, _val: &HealthCheckRequest, ) -> RpcResult<()> { e.map(0)?; @@ -164,21 +166,12 @@ pub fn decode_health_check_request( } }; if is_array { - let len = d.array()?.ok_or_else(|| { - RpcError::Deser( - "decoding struct HealthCheckRequest: indefinite array not supported" - .to_string(), - ) - })?; + let len = d.fixed_array()?; for __i in 0..(len as usize) { d.skip()?; } } else { - let len = d.map()?.ok_or_else(|| { - RpcError::Deser( - "decoding struct HealthCheckRequest: indefinite map not supported".to_string(), - ) - })?; + let len = d.fixed_map()?; for __i in 0..(len as usize) { d.str()?; d.skip()?; @@ -201,8 +194,9 @@ pub struct HealthCheckResponse { // Encode HealthCheckResponse as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] pub fn encode_health_check_response( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &HealthCheckResponse, ) -> RpcResult<()> { e.array(2)?; @@ -234,12 +228,7 @@ pub fn decode_health_check_response( } }; if is_array { - let len = d.array()?.ok_or_else(|| { - RpcError::Deser( - "decoding struct HealthCheckResponse: indefinite array not supported" - .to_string(), - ) - })?; + let len = d.fixed_array()?; for __i in 0..(len as usize) { match __i { 0 => healthy = Some(d.bool()?), @@ -256,11 +245,7 @@ pub fn decode_health_check_response( } } } else { - let len = d.map()?.ok_or_else(|| { - RpcError::Deser( - "decoding struct HealthCheckResponse: indefinite map not supported".to_string(), - ) - })?; + let len = d.fixed_map()?; for __i in 0..(len as usize) { match d.str()? { "healthy" => healthy = Some(d.bool()?), @@ -327,8 +312,9 @@ pub struct HostData { // Encode HostData as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] pub fn encode_host_data( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &HostData, ) -> RpcResult<()> { e.array(14)?; @@ -386,11 +372,7 @@ pub fn decode_host_data(d: &mut crate::cbor::Decoder<'_>) -> Result host_id = Some(d.str()?.to_string()), @@ -402,23 +384,20 @@ pub fn decode_host_data(d: &mut crate::cbor::Decoder<'_>) -> Result provider_key = Some(d.str()?.to_string()), 7 => invocation_seed = Some(d.str()?.to_string()), 8 => { - env_values = Some( - decode_host_env_values(d) - .map_err(|e| format!("decoding 'HostEnvValues': {}", e))?, - ) + env_values = Some(decode_host_env_values(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#HostEnvValues': {}", e) + })?) } 9 => instance_id = Some(d.str()?.to_string()), 10 => { - link_definitions = Some( - decode_actor_links(d) - .map_err(|e| format!("decoding 'ActorLinks': {}", e))?, - ) + link_definitions = Some(decode_actor_links(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#ActorLinks': {}", e) + })?) } 11 => { - cluster_issuers = Some( - decode_cluster_issuers(d) - .map_err(|e| format!("decoding 'ClusterIssuers': {}", e))?, - ) + cluster_issuers = Some(decode_cluster_issuers(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#ClusterIssuers': {}", e) + })?) } 12 => { config_json = if crate::cbor::Type::Null == d.datatype()? { @@ -441,11 +420,7 @@ pub fn decode_host_data(d: &mut crate::cbor::Decoder<'_>) -> Result host_id = Some(d.str()?.to_string()), @@ -457,23 +432,20 @@ pub fn decode_host_data(d: &mut crate::cbor::Decoder<'_>) -> Result provider_key = Some(d.str()?.to_string()), "invocationSeed" => invocation_seed = Some(d.str()?.to_string()), "envValues" => { - env_values = Some( - decode_host_env_values(d) - .map_err(|e| format!("decoding 'HostEnvValues': {}", e))?, - ) + env_values = Some(decode_host_env_values(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#HostEnvValues': {}", e) + })?) } "instanceId" => instance_id = Some(d.str()?.to_string()), "linkDefinitions" => { - link_definitions = Some( - decode_actor_links(d) - .map_err(|e| format!("decoding 'ActorLinks': {}", e))?, - ) + link_definitions = Some(decode_actor_links(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#ActorLinks': {}", e) + })?) } "clusterIssuers" => { - cluster_issuers = Some( - decode_cluster_issuers(d) - .map_err(|e| format!("decoding 'ClusterIssuers': {}", e))?, - ) + cluster_issuers = Some(decode_cluster_issuers(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#ClusterIssuers': {}", e) + })?) } "configJson" => { config_json = if crate::cbor::Type::Null == d.datatype()? { @@ -602,8 +574,9 @@ pub type HostEnvValues = std::collections::HashMap; // Encode HostEnvValues as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] pub fn encode_host_env_values( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &HostEnvValues, ) -> RpcResult<()> { e.map(val.len() as u64)?; @@ -619,16 +592,13 @@ pub fn encode_host_env_values( pub fn decode_host_env_values(d: &mut crate::cbor::Decoder<'_>) -> Result { let __result = { { + let map_len = d.fixed_map()? as usize; let mut m: std::collections::HashMap = - std::collections::HashMap::default(); - if let Some(n) = d.map()? { - for _ in 0..(n as usize) { - let k = d.str()?.to_string(); - let v = d.str()?.to_string(); - m.insert(k, v); - } - } else { - return Err(RpcError::Deser("indefinite maps not supported".to_string())); + std::collections::HashMap::with_capacity(map_len); + for _ in 0..map_len { + let k = d.str()?.to_string(); + let v = d.str()?.to_string(); + m.insert(k, v); } m } @@ -659,8 +629,9 @@ pub struct Invocation { // Encode Invocation as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] pub fn encode_invocation( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &Invocation, ) -> RpcResult<()> { e.array(8)?; @@ -702,24 +673,18 @@ pub fn decode_invocation(d: &mut crate::cbor::Decoder<'_>) -> Result { - origin = Some( - decode_wasm_cloud_entity(d) - .map_err(|e| format!("decoding 'WasmCloudEntity': {}", e))?, - ) + origin = Some(decode_wasm_cloud_entity(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#WasmCloudEntity': {}", e) + })?) } 1 => { - target = Some( - decode_wasm_cloud_entity(d) - .map_err(|e| format!("decoding 'WasmCloudEntity': {}", e))?, - ) + target = Some(decode_wasm_cloud_entity(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#WasmCloudEntity': {}", e) + })?) } 2 => operation = Some(d.str()?.to_string()), 3 => msg = Some(d.bytes()?.to_vec()), @@ -739,24 +704,18 @@ pub fn decode_invocation(d: &mut crate::cbor::Decoder<'_>) -> Result { - origin = Some( - decode_wasm_cloud_entity(d) - .map_err(|e| format!("decoding 'WasmCloudEntity': {}", e))?, - ) + origin = Some(decode_wasm_cloud_entity(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#WasmCloudEntity': {}", e) + })?) } "target" => { - target = Some( - decode_wasm_cloud_entity(d) - .map_err(|e| format!("decoding 'WasmCloudEntity': {}", e))?, - ) + target = Some(decode_wasm_cloud_entity(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#WasmCloudEntity': {}", e) + })?) } "operation" => operation = Some(d.str()?.to_string()), "msg" => msg = Some(d.bytes()?.to_vec()), @@ -857,8 +816,9 @@ pub struct InvocationResponse { // Encode InvocationResponse as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] pub fn encode_invocation_response( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &InvocationResponse, ) -> RpcResult<()> { e.array(4)?; @@ -898,12 +858,7 @@ pub fn decode_invocation_response( } }; if is_array { - let len = d.array()?.ok_or_else(|| { - RpcError::Deser( - "decoding struct InvocationResponse: indefinite array not supported" - .to_string(), - ) - })?; + let len = d.fixed_array()?; for __i in 0..(len as usize) { match __i { 0 => msg = Some(d.bytes()?.to_vec()), @@ -929,11 +884,7 @@ pub fn decode_invocation_response( } } } else { - let len = d.map()?.ok_or_else(|| { - RpcError::Deser( - "decoding struct InvocationResponse: indefinite map not supported".to_string(), - ) - })?; + let len = d.fixed_map()?; for __i in 0..(len as usize) { match d.str()? { "msg" => msg = Some(d.bytes()?.to_vec()), @@ -1000,8 +951,9 @@ pub struct LinkDefinition { // Encode LinkDefinition as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] pub fn encode_link_definition( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &LinkDefinition, ) -> RpcResult<()> { e.array(5)?; @@ -1035,11 +987,7 @@ pub fn decode_link_definition( } }; if is_array { - let len = d.array()?.ok_or_else(|| { - RpcError::Deser( - "decoding struct LinkDefinition: indefinite array not supported".to_string(), - ) - })?; + let len = d.fixed_array()?; for __i in 0..(len as usize) { match __i { 0 => actor_id = Some(d.str()?.to_string()), @@ -1047,20 +995,15 @@ pub fn decode_link_definition( 2 => link_name = Some(d.str()?.to_string()), 3 => contract_id = Some(d.str()?.to_string()), 4 => { - values = Some( - decode_link_settings(d) - .map_err(|e| format!("decoding 'LinkSettings': {}", e))?, - ) + values = Some(decode_link_settings(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#LinkSettings': {}", e) + })?) } _ => d.skip()?, } } } else { - let len = d.map()?.ok_or_else(|| { - RpcError::Deser( - "decoding struct LinkDefinition: indefinite map not supported".to_string(), - ) - })?; + let len = d.fixed_map()?; for __i in 0..(len as usize) { match d.str()? { "actorId" => actor_id = Some(d.str()?.to_string()), @@ -1068,10 +1011,9 @@ pub fn decode_link_definition( "linkName" => link_name = Some(d.str()?.to_string()), "contractId" => contract_id = Some(d.str()?.to_string()), "values" => { - values = Some( - decode_link_settings(d) - .map_err(|e| format!("decoding 'LinkSettings': {}", e))?, - ) + values = Some(decode_link_settings(d).map_err(|e| { + format!("decoding 'org.wasmcloud.core#LinkSettings': {}", e) + })?) } _ => d.skip()?, } @@ -1126,8 +1068,9 @@ pub type LinkSettings = std::collections::HashMap; // Encode LinkSettings as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] pub fn encode_link_settings( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &LinkSettings, ) -> RpcResult<()> { e.map(val.len() as u64)?; @@ -1143,16 +1086,13 @@ pub fn encode_link_settings( pub fn decode_link_settings(d: &mut crate::cbor::Decoder<'_>) -> Result { let __result = { { + let map_len = d.fixed_map()? as usize; let mut m: std::collections::HashMap = - std::collections::HashMap::default(); - if let Some(n) = d.map()? { - for _ in 0..(n as usize) { - let k = d.str()?.to_string(); - let v = d.str()?.to_string(); - m.insert(k, v); - } - } else { - return Err(RpcError::Deser("indefinite maps not supported".to_string())); + std::collections::HashMap::with_capacity(map_len); + for _ in 0..map_len { + let k = d.str()?.to_string(); + let v = d.str()?.to_string(); + m.insert(k, v); } m } @@ -1170,8 +1110,9 @@ pub struct WasmCloudEntity { // Encode WasmCloudEntity as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] pub fn encode_wasm_cloud_entity( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &WasmCloudEntity, ) -> RpcResult<()> { e.array(3)?; @@ -1201,11 +1142,7 @@ pub fn decode_wasm_cloud_entity( } }; if is_array { - let len = d.array()?.ok_or_else(|| { - RpcError::Deser( - "decoding struct WasmCloudEntity: indefinite array not supported".to_string(), - ) - })?; + let len = d.fixed_array()?; for __i in 0..(len as usize) { match __i { 0 => public_key = Some(d.str()?.to_string()), @@ -1215,11 +1152,7 @@ pub fn decode_wasm_cloud_entity( } } } else { - let len = d.map()?.ok_or_else(|| { - RpcError::Deser( - "decoding struct WasmCloudEntity: indefinite map not supported".to_string(), - ) - })?; + let len = d.fixed_map()?; for __i in 0..(len as usize) { match d.str()? { "publicKey" => public_key = Some(d.str()?.to_string()), diff --git a/rpc-rs/src/wasmbus_model.rs b/rpc-rs/src/wasmbus_model.rs index fe15dd6..0395d6f 100644 --- a/rpc-rs/src/wasmbus_model.rs +++ b/rpc-rs/src/wasmbus_model.rs @@ -1,4 +1,4 @@ -// This file is generated automatically using wasmcloud/weld-codegen 0.4.2 +// This file is generated automatically using wasmcloud/weld-codegen 0.4.3 #[allow(unused_imports)] use crate::error::{RpcError, RpcResult}; #[allow(unused_imports)] @@ -6,6 +6,7 @@ use minicbor::{encode::Write, Encode}; #[allow(unused_imports)] use serde::{Deserialize, Serialize}; +#[allow(dead_code)] pub const SMITHY_VERSION: &str = "1.0"; /// Capability contract id, e.g. 'wasmcloud:httpserver' @@ -13,8 +14,9 @@ pub type CapabilityContractId = String; // Encode CapabilityContractId as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] pub fn encode_capability_contract_id( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &CapabilityContractId, ) -> RpcResult<()> { e.str(val)?; @@ -34,9 +36,10 @@ pub type F32 = f32; // Encode F32 as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] #[inline] pub fn encode_f32( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &F32, ) -> RpcResult<()> { e.f32(*val)?; @@ -55,9 +58,10 @@ pub type F64 = f64; // Encode F64 as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] #[inline] pub fn encode_f64( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &F64, ) -> RpcResult<()> { e.f64(*val)?; @@ -76,9 +80,10 @@ pub type I16 = i16; // Encode I16 as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] #[inline] pub fn encode_i16( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &I16, ) -> RpcResult<()> { e.i16(*val)?; @@ -97,9 +102,10 @@ pub type I32 = i32; // Encode I32 as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] #[inline] pub fn encode_i32( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &I32, ) -> RpcResult<()> { e.i32(*val)?; @@ -118,9 +124,10 @@ pub type I64 = i64; // Encode I64 as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] #[inline] pub fn encode_i64( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &I64, ) -> RpcResult<()> { e.i64(*val)?; @@ -139,9 +146,10 @@ pub type I8 = i8; // Encode I8 as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] #[inline] pub fn encode_i8( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &I8, ) -> RpcResult<()> { e.i8(*val)?; @@ -160,8 +168,9 @@ pub type IdentifierList = Vec; // Encode IdentifierList as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] pub fn encode_identifier_list( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &IdentifierList, ) -> RpcResult<()> { e.array(val.len() as u64)?; @@ -203,9 +212,10 @@ pub type U16 = i16; // Encode U16 as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] #[inline] pub fn encode_u16( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &U16, ) -> RpcResult<()> { e.i16(*val)?; @@ -224,9 +234,10 @@ pub type U32 = i32; // Encode U32 as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] #[inline] pub fn encode_u32( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &U32, ) -> RpcResult<()> { e.i32(*val)?; @@ -245,9 +256,10 @@ pub type U64 = i64; // Encode U64 as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] #[inline] pub fn encode_u64( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &U64, ) -> RpcResult<()> { e.i64(*val)?; @@ -266,9 +278,10 @@ pub type U8 = i8; // Encode U8 as CBOR and append to output stream #[doc(hidden)] +#[allow(unused_mut)] #[inline] pub fn encode_u8( - e: &mut crate::cbor::Encoder, + mut e: &mut crate::cbor::Encoder, val: &U8, ) -> RpcResult<()> { e.i8(*val)?; @@ -297,6 +310,10 @@ pub struct CodegenRust { #[serde(rename = "nonExhaustive")] #[serde(default)] pub non_exhaustive: bool, + /// if true, do not generate code for this item. + /// This trait can be used if an item needs to be hand-generated + #[serde(default)] + pub skip: bool, } /// indicates that a trait or class extends one or more bases