use std::{fmt, ops::Deref, str::FromStr};
use atelier_core::{
model::{
shapes::{AppliedTraits, HasTraits, MemberShape, Operation, ShapeKind, StructureOrUnion},
values::{Number, Value as NodeValue},
HasIdentity, Identifier, Model, NamespaceID, ShapeID,
},
prelude::prelude_namespace_id,
};
use lazy_static::lazy_static;
use serde::{de::DeserializeOwned, Deserialize};
use crate::{
error::{Error, Result},
JsonValue,
};
const WASMCLOUD_MODEL_NAMESPACE: &str = "org.wasmcloud.model";
const WASMCLOUD_CORE_NAMESPACE: &str = "org.wasmcloud.core";
const WASMCLOUD_ACTOR_NAMESPACE: &str = "org.wasmcloud.actor";
const TRAIT_CODEGEN_RUST: &str = "codegenRust";
const TRAIT_SERIALIZATION: &str = "serialization";
const TRAIT_WASMBUS: &str = "wasmbus";
const TRAIT_WASMBUS_DATA: &str = "wasmbusData";
const TRAIT_FIELD_NUM: &str = "n";
const TRAIT_RENAME: &str = "rename";
lazy_static! {
static ref WASMCLOUD_MODEL_NAMESPACE_ID: NamespaceID =
NamespaceID::new_unchecked(WASMCLOUD_MODEL_NAMESPACE);
static ref WASMCLOUD_CORE_NAMESPACE_ID: NamespaceID =
NamespaceID::new_unchecked(WASMCLOUD_CORE_NAMESPACE);
static ref WASMCLOUD_ACTOR_NAMESPACE_ID: NamespaceID =
NamespaceID::new_unchecked(WASMCLOUD_ACTOR_NAMESPACE);
static ref SERIALIZATION_TRAIT_ID: ShapeID = ShapeID::new(
NamespaceID::new_unchecked(WASMCLOUD_MODEL_NAMESPACE),
Identifier::from_str(TRAIT_SERIALIZATION).unwrap(),
None
);
static ref CODEGEN_RUST_TRAIT_ID: ShapeID = ShapeID::new(
NamespaceID::new_unchecked(WASMCLOUD_MODEL_NAMESPACE),
Identifier::from_str(TRAIT_CODEGEN_RUST).unwrap(),
None
);
static ref WASMBUS_TRAIT_ID: ShapeID = ShapeID::new(
NamespaceID::new_unchecked(WASMCLOUD_MODEL_NAMESPACE),
Identifier::from_str(TRAIT_WASMBUS).unwrap(),
None
);
static ref WASMBUS_DATA_TRAIT_ID: ShapeID = ShapeID::new(
NamespaceID::new_unchecked(WASMCLOUD_MODEL_NAMESPACE),
Identifier::from_str(TRAIT_WASMBUS_DATA).unwrap(),
None
);
static ref FIELD_NUM_TRAIT_ID: ShapeID = ShapeID::new(
NamespaceID::new_unchecked(WASMCLOUD_MODEL_NAMESPACE),
Identifier::from_str(TRAIT_FIELD_NUM).unwrap(),
None
);
static ref RENAME_TRAIT_ID: ShapeID = ShapeID::new(
NamespaceID::new_unchecked(WASMCLOUD_MODEL_NAMESPACE),
Identifier::from_str(TRAIT_RENAME).unwrap(),
None
);
static ref UNIT_ID: ShapeID = ShapeID::new_unchecked(WASMCLOUD_MODEL_NAMESPACE, "Unit", None);
}
pub fn wasmcloud_model_namespace() -> &'static NamespaceID {
&WASMCLOUD_MODEL_NAMESPACE_ID
}
pub fn wasmcloud_core_namespace() -> &'static NamespaceID {
&WASMCLOUD_CORE_NAMESPACE_ID
}
pub fn wasmcloud_actor_namespace() -> &'static NamespaceID {
&WASMCLOUD_ACTOR_NAMESPACE_ID
}
#[cfg(feature = "wasmbus")]
pub fn wasmbus_trait() -> &'static ShapeID {
&WASMBUS_TRAIT_ID
}
#[allow(dead_code)]
#[cfg(feature = "wasmbus")]
pub fn wasmbus_data_trait() -> &'static ShapeID {
&WASMBUS_DATA_TRAIT_ID
}
pub fn serialization_trait() -> &'static ShapeID {
&SERIALIZATION_TRAIT_ID
}
pub fn codegen_rust_trait() -> &'static ShapeID {
&CODEGEN_RUST_TRAIT_ID
}
pub fn field_num_trait() -> &'static ShapeID {
&FIELD_NUM_TRAIT_ID
}
pub fn rename_trait() -> &'static ShapeID {
&RENAME_TRAIT_ID
}
pub fn unit_shape() -> &'static ShapeID {
&UNIT_ID
}
#[allow(dead_code)]
pub enum CommentKind {
Inner,
Documentation,
InQuote,
}
#[derive(Default, Clone, PartialEq, Eq)]
pub struct WasmbusProtoVersion {
base: u8, }
impl TryFrom<&str> for WasmbusProtoVersion {
type Error = crate::error::Error;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
match value {
"0" => Ok(WasmbusProtoVersion { base: 0 }),
"2" => Ok(WasmbusProtoVersion { base: 2 }),
_ => Err(Error::Model(format!(
"Invalid wasmbus.protocol: '{value}'. The default value is \"0\"."
))),
}
}
}
impl fmt::Debug for WasmbusProtoVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.to_string())
}
}
impl ToString for WasmbusProtoVersion {
fn to_string(&self) -> String {
format!("{}", self.base)
}
}
impl WasmbusProtoVersion {
pub fn has_cbor(&self) -> bool {
self.base >= 2
}
}
pub(crate) enum Ty<'typ> {
Shape(&'typ ShapeID),
Opt(&'typ ShapeID),
Ref(&'typ ShapeID),
Ptr(&'typ ShapeID),
}
#[macro_export]
macro_rules! expect_empty {
($list:expr, $msg:expr) => {
if !$list.is_empty() {
return Err(Error::InvalidModel(format!(
"{}: {}",
$msg,
$list.keys().map(|k| k.to_string()).collect::<Vec<String>>().join(",")
)));
}
};
}
#[macro_export]
macro_rules! unsupported_shape {
($fn_name:ident, $shape_type:ty, $doc:expr) => {
#[allow(unused_variables)]
fn $fn_name(
&mut self,
id: &ShapeID,
traits: &AppliedTraits,
shape: &$shape_type,
) -> Result<()> {
return Err(weld_codegen::error::Error::UnsupportedShape(
id.to_string(),
$doc.to_string(),
));
}
};
}
pub fn is_opt_namespace(id: &ShapeID, ns: &Option<NamespaceID>) -> bool {
match ns {
Some(ns) => id.namespace() == ns,
None => true,
}
}
pub fn get_operation<'model>(
model: &'model Model,
operation_id: &'_ ShapeID,
service_id: &'_ Identifier,
) -> Result<(&'model Operation, &'model AppliedTraits)> {
let op = model
.shapes()
.filter(|t| t.id() == operation_id)
.find_map(|t| {
if let ShapeKind::Operation(op) = t.body() {
Some((op, t.traits()))
} else {
None
}
})
.ok_or_else(|| {
Error::Model(format!(
"missing operation {} for service {}",
&operation_id.to_string(),
&service_id.to_string()
))
})?;
Ok(op)
}
pub fn get_trait<T: DeserializeOwned>(traits: &AppliedTraits, id: &ShapeID) -> Result<Option<T>> {
match traits.get(id) {
Some(Some(val)) => match trait_value(val) {
Ok(obj) => Ok(Some(obj)),
Err(e) => Err(e),
},
Some(None) => Ok(None),
None => Ok(None),
}
}
pub fn wasmbus_proto(traits: &AppliedTraits) -> Result<Option<WasmbusProtoVersion>> {
match get_trait(traits, wasmbus_trait()) {
Ok(Some(Wasmbus { protocol: Some(version), .. })) => {
Ok(Some(WasmbusProtoVersion::try_from(version.as_str())?))
}
Ok(_) => Ok(Some(WasmbusProtoVersion::default())),
_ => Ok(None),
}
}
pub fn trait_value<T: DeserializeOwned>(value: &NodeValue) -> Result<T> {
let json = value_to_json(value);
let obj = serde_json::from_value(json)?;
Ok(obj)
}
pub fn value_to_json(value: &NodeValue) -> JsonValue {
match value {
NodeValue::None => JsonValue::Null,
NodeValue::Array(v) => JsonValue::Array(v.iter().map(value_to_json).collect()),
NodeValue::Object(v) => {
let mut object = crate::JsonMap::default();
for (k, v) in v {
let _ = object.insert(k.clone(), value_to_json(v));
}
JsonValue::Object(object)
}
NodeValue::Number(v) => match v {
Number::Integer(v) => JsonValue::Number((*v).into()),
Number::Float(v) => JsonValue::Number(serde_json::Number::from_f64(*v).unwrap()),
},
NodeValue::Boolean(v) => JsonValue::Bool(*v),
NodeValue::String(v) => JsonValue::String(v.clone()),
}
}
pub fn resolve<'model>(model: &'model Model, shape: &'model ShapeID) -> &'model ShapeID {
if let Some(resolved) = model.shape(shape) {
resolved.id()
} else {
shape
}
}
pub fn has_default(model: &'_ Model, member: &MemberShape) -> bool {
let id = resolve(model, member.target());
#[allow(unused_mut)]
let mut has = false;
let name = id.shape_name().to_string();
if id.namespace().eq(prelude_namespace_id()) {
cfg_if::cfg_if! {
if #[cfg(feature = "BigInteger")] {
has = has || &name == "bigInteger";
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "BigDecimal")] {
has = has || &name == "bigDecimal";
}
}
has || matches!(
name.as_str(),
"List" | "Set" | "Map"
| "Blob" | "Boolean" | "String" | "Byte" | "Short"
| "Integer" | "Long" | "Float" | "Double"
| "Timestamp"
)
} else if id.namespace() == wasmcloud_model_namespace() {
matches!(
name.as_str(),
"U64" | "U32" | "U16" | "U8" | "I64" | "I32" | "I16" | "I8" | "F64" | "F32"
)
} else {
false
}
}
pub struct NumberedMember {
field_num: Option<u16>,
shape: MemberShape,
}
impl NumberedMember {
pub(crate) fn new(member: &MemberShape) -> Result<Self> {
Ok(NumberedMember {
shape: member.to_owned(),
field_num: get_trait::<u16>(member.traits(), field_num_trait()).map_err(|e| {
Error::Model(format!(
"invalid field number @n() for field '{}': {}",
member.id(),
e
))
})?,
})
}
pub(crate) fn field_num(&self) -> &Option<u16> {
&self.field_num
}
}
impl Deref for NumberedMember {
type Target = MemberShape;
fn deref(&self) -> &Self::Target {
&self.shape
}
}
use std::iter::Iterator;
use crate::wasmbus_model::Wasmbus;
pub(crate) fn get_sorted_fields(
id: &Identifier,
strukt: &StructureOrUnion,
) -> Result<(Vec<NumberedMember>, bool)> {
let mut fields = strukt
.members()
.map(NumberedMember::new)
.collect::<Result<Vec<NumberedMember>>>()?;
let has_numbers = crate::model::has_field_numbers(&fields, &id.to_string())?;
if has_numbers {
fields.sort_by_key(|f| f.field_num().unwrap());
} else {
fields.sort_by_key(|f| f.id().to_owned());
}
Ok((fields, has_numbers))
}
pub(crate) fn has_field_numbers(fields: &[NumberedMember], name: &str) -> Result<bool> {
let mut numbered = std::collections::BTreeSet::default();
for f in fields.iter() {
if let Some(n) = f.field_num() {
numbered.insert(*n);
}
}
if numbered.is_empty() {
Ok(false)
} else if numbered.len() == fields.len() {
Ok(true)
} else {
Err(crate::Error::Model(format!(
"structure {name} has incomplete or invalid field numbers: either some fields are missing \
the '@n()' trait, or some fields have duplicate numbers."
)))
}
}
#[derive(Clone, Deserialize)]
pub struct PackageName {
pub namespace: String,
#[serde(rename = "crate")]
pub crate_name: Option<String>,
#[serde(rename = "py_module")]
pub py_module: Option<String>,
pub go_package: Option<String>,
pub doc: Option<String>,
}