mod builder;
mod field;
mod value;
pub use self::{builder::Builder, field::Field, value::Value};
use super::{schema::ValueType, Schema, TypeName};
use crate::{Address, Decimal, Error};
use eyre::{Result, WrapErr};
use prost_amino::encode_length_delimiter as encode_leb128; use std::collections::BTreeMap;
use subtle_encoding::hex;
pub type Tag = u64;
#[derive(Clone, Debug)]
pub struct Msg {
type_name: TypeName,
fields: Vec<Field>,
}
impl Msg {
pub fn from_json_value(schema: &Schema, json_value: serde_json::Value) -> Result<Self> {
let json_obj = match json_value.as_object() {
Some(obj) => obj,
None => return Err(Error::Type).wrap_err("expected JSON object"),
};
if json_obj.len() != 2 {
return Err(Error::Parse).wrap_err("unexpected keys in JSON object");
}
let type_name = match json_obj.get("type").and_then(|v| v.as_str()) {
Some(name) => name.parse::<TypeName>()?,
None => return Err(Error::Parse).wrap_err("no `type` key in JSON object"),
};
let type_def = match schema.get_definition(&type_name) {
Some(def) => def,
None => {
return Err(Error::FieldName)
.wrap_err_with(|| format!("no type definition for `{}`", type_name))
}
};
let value_obj = match json_obj.get("value").and_then(|v| v.as_object()) {
Some(obj) => obj,
None => {
return Err(Error::Parse).wrap_err("missing or invalid `value` key in JSON object")
}
};
let mut fields = vec![];
for (json_name, json_value) in value_obj {
let field_name = json_name.parse::<TypeName>()?;
let field_def = match type_def.get_field(&field_name) {
Some(def) => def,
None => {
return Err(Error::FieldName)
.wrap_err_with(|| format!("unknown field name: `{}`", field_name))
}
};
let value_str = match json_value.as_str() {
Some(s) => s,
None => {
return Err(Error::Parse)
.wrap_err_with(|| format!("couldn't parse JSON value: `{}`", field_name))
}
};
let value = match field_def.value_type() {
ValueType::Bytes => hex::decode(value_str)
.map(Value::Bytes)
.map_err(|_| Error::Parse)
.wrap_err_with(|| format!("invalid hex-encoded bytes: '{}'", value_str))?,
ValueType::SdkAccAddress => {
let (hrp, addr) = Address::from_bech32(value_str)?;
if schema.acc_prefix() != hrp {
return Err(Error::Parse)
.wrap_err_with(|| format!("invalid account prefix: {}", value_str));
}
Value::SdkAccAddress(addr)
}
ValueType::SdkValAddress => {
let (hrp, addr) = Address::from_bech32(value_str)?;
if schema.val_prefix() != hrp {
return Err(Error::Parse)
.wrap_err_with(|| format!("invalid validator prefix: {}", value_str));
}
Value::SdkValAddress(addr)
}
ValueType::SdkDecimal => Value::SdkDecimal(value_str.parse::<Decimal>()?),
ValueType::String => Value::String(value_str.to_owned()),
};
fields.push(Field::new(field_def.tag(), field_name, value));
}
fields.sort_by_key(|f1| f1.tag());
Ok(Self { type_name, fields })
}
pub fn type_name(&self) -> &TypeName {
&self.type_name
}
pub fn fields(&self) -> &[Field] {
&self.fields
}
pub fn to_json_value(&self, schema: &Schema) -> serde_json::Value {
let mut values = BTreeMap::new();
for field in &self.fields {
values.insert(
field.name().to_string(),
field.value().to_json_value(schema),
);
}
let mut json = serde_json::Map::new();
json.insert(
"type".to_owned(),
serde_json::Value::String(self.type_name.to_string()),
);
json.insert(
"value".to_owned(),
values.into_iter().collect::<serde_json::Map<_, _>>().into(),
);
serde_json::Value::Object(json)
}
pub fn to_amino_bytes(&self) -> Vec<u8> {
let mut result = self.type_name.amino_prefix();
for field in &self.fields {
let prefix = field.tag() << 3 | field.value().wire_type();
encode_leb128(prefix as usize, &mut result).expect("LEB128 encoding error");
let mut encoded_value = field.value().to_amino_bytes();
encode_leb128(encoded_value.len(), &mut result).expect("LEB128 encoding error");
result.append(&mut encoded_value);
}
result
}
}
#[cfg(test)]
mod tests {
use super::{Msg, Value};
use crate::{amino::Schema, Address};
use serde_json::json;
const EXAMPLE_SCHEMA: &str = "tests/support/example_schema.toml";
#[test]
fn from_json_value() {
let schema = Schema::load_toml(EXAMPLE_SCHEMA).unwrap();
let example_json = json!({
"type": "oracle/MsgExchangeRateVote",
"value": {
"denom": "umnt",
"exchange_rate": "-1.000000000000000000",
"feeder": "terra1t9et8wjeh8d0ewf4lldchterxsmhpcgg5auy47",
"salt": "4V7A",
"validator": "terravaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03x2mfyu7"
}
});
let msg = Msg::from_json_value(&schema, example_json.clone()).unwrap();
assert_eq!(msg.type_name().as_str(), "oracle/MsgExchangeRateVote");
assert_eq!(msg.fields().len(), 5);
let field1 = &msg.fields()[0];
assert_eq!(field1.tag(), 1);
assert_eq!(field1.value(), &Value::SdkDecimal("-1".parse().unwrap()));
let field2 = &msg.fields()[1];
assert_eq!(field2.tag(), 2);
assert_eq!(field2.value(), &Value::String("4V7A".to_owned()));
let field3 = &msg.fields()[2];
assert_eq!(field3.tag(), 3);
assert_eq!(field3.value(), &Value::String("umnt".to_owned()));
let field4 = &msg.fields()[3];
assert_eq!(field4.tag(), 4);
assert_eq!(
field4.value(),
&Value::SdkAccAddress(Address([
89, 114, 179, 186, 89, 185, 218, 252, 185, 53, 255, 219, 139, 175, 35, 52, 55, 112,
225, 8,
]))
);
let field5 = &msg.fields()[4];
assert_eq!(field5.tag(), 5);
assert_eq!(
field5.value(),
&Value::SdkValAddress(Address([
64, 209, 159, 146, 104, 83, 52, 200, 172, 226, 63, 157, 200, 192, 200, 89, 119, 72,
62, 38,
]))
);
assert_eq!(msg.to_json_value(&schema), example_json);
}
}