use anyhow::{Context, Result};
use bitcoincash::hashes::hex::FromHex;
use bitcoincash::hashes::Hash;
use serde_json::Value;
use crate::chaindef::TokenID;
use crate::errors::rpc_invalid_params;
pub fn bool_from_value(val: Option<&Value>, name: &str) -> Result<bool> {
let val = val.context(rpc_invalid_params(format!("missing {}", name)))?;
let val = val
.as_bool()
.context(rpc_invalid_params(format!("not a bool {}", name)))?;
Ok(val)
}
pub fn bool_from_value_or(val: Option<&Value>, name: &str, default: bool) -> Result<bool> {
if val.is_none() {
return Ok(default);
}
bool_from_value(val, name)
}
pub fn hash_from_value<T: Hash>(val: Option<&Value>) -> Result<T> {
let hash = val.context(rpc_invalid_params("missing hash".to_string()))?;
let hash = hash.as_str().context(rpc_invalid_params(
"expected hash argument to be a string".to_string(),
))?;
let hash = T::from_hex(hash).context(rpc_invalid_params(
"expected hash argument to be a hex string".to_string(),
))?;
Ok(hash)
}
pub fn str_from_value(val: Option<&Value>, name: &str) -> Result<String> {
let string = val.context(rpc_invalid_params(format!("missing {}", name)))?;
let string = string
.as_str()
.context(rpc_invalid_params(format!("{} is not a string", name)))?;
Ok(string.into())
}
pub fn str_from_value_or_none(val: Option<&Value>, name: &str) -> Result<Option<String>> {
let string = match val {
Some(v) => v,
None => return Ok(None),
};
let string = string
.as_str()
.context(rpc_invalid_params(format!("{} is not a string", name)))?;
Ok(Some(string.into()))
}
pub fn hex_from_value(val: Option<&Value>, name: &str) -> Result<Vec<u8>> {
let string = val.context(rpc_invalid_params(format!("missing {}", name)))?;
let string = string
.as_str()
.context(rpc_invalid_params(format!("{} is not a string", name)))?;
let hex = hex::decode(string).context(rpc_invalid_params(format!(
"{} is not a valid hex string",
name
)))?;
Ok(hex)
}
pub fn usize_from_value(val: Option<&Value>, name: &str) -> Result<usize> {
let val = val.context(rpc_invalid_params(format!("missing {}", name)))?;
let val = val
.as_u64()
.context(rpc_invalid_params(format!("non-integer {}", name)))?;
Ok(val as usize)
}
pub fn usize_from_value_or(val: Option<&Value>, name: &str, default: usize) -> Result<usize> {
if val.is_none() {
return Ok(default);
}
usize_from_value(val, name)
}
pub fn tokenid_from_value(val: Option<&Value>) -> Result<Option<TokenID>> {
if let Some(v) = val {
if v.is_null() {
return Ok(None);
}
let v = v.as_str().context("token_id is not a string")?;
if let Ok(id) = TokenID::from_hex(v) {
return Ok(Some(id));
};
#[cfg(nexa)]
{
if let Ok((payload, version_type, _)) = crate::nexa::cashaddr::decode(v) {
if version_type != crate::nexa::cashaddr::version_byte_flags::TYPE_GROUP {
bail!(rpc_invalid_params(
"The `token_id` parameter had invalid cashaddr version flag (not a token)"
.to_string()
));
}
return Ok(Some(TokenID::from_vec(payload)?));
}
}
bail!(rpc_invalid_params(
"The `token_id` parameter is not a valid identifier string".to_string()
));
} else {
Ok(None)
}
}
pub fn cursor_from_value(val: Option<&Value>) -> Result<Option<String>> {
match val {
Some(inner_val) => {
if inner_val.is_null() {
Ok(None) } else {
Err(rpc_invalid_params("Unknown 'cursor' provided".to_string()))
}
}
None => Ok(None), }
}
pub fn commitment_from_value(val: Option<&Value>) -> Result<Option<Vec<u8>>> {
if let Some(v) = val {
let v = v
.as_str()
.context(rpc_invalid_params("commitment is not a string".to_string()))?;
let decoded = hex::decode(v).context(rpc_invalid_params(
"commitment is not a valid hex string".to_string(),
))?;
if decoded.len() <= 40 {
Ok(Some(decoded))
} else {
Err(rpc_invalid_params(
"commitment is not a valid hex string or longer than 40 bytes".to_string(),
))
}
} else {
Ok(None)
}
}
#[cfg(nexa)]
pub fn param_to_outpointhash(value: Option<&Value>) -> Result<crate::chaindef::OutPointHash> {
use crate::chaindef::OutPointHash;
let value = match value {
Some(v) => v,
None => {
return Err(rpc_invalid_params(
"missing argument outpointhash".to_string(),
))
}
};
let mut hash = hex::decode(
value
.as_str()
.context(format!("non-string outpointhash value: {}", value))?,
)
.context(format!("non-hex outpointhash value: {}", value))?;
hash.reverse();
OutPointHash::from_slice(&hash).context(format!("invalid blockhash {}", value))
}
pub fn parse_version_str(version: &str) -> Option<String> {
version_compare::Version::from(version).map(|v| v.to_string())
}
#[cfg(test)]
mod tests {
#[cfg(bch)]
use bitcoin_hashes::hex::ToHex;
use super::parse_version_str;
use super::tokenid_from_value;
#[test]
fn test_tokenid_from_hex_value() {
let hex_encoded = json!("27c21ff3552e283180422900c9671e60e6f6922691750aa1b2ed3b3347b00000");
let hex_decoded = tokenid_from_value(Some(&hex_encoded)).unwrap().unwrap();
assert_eq!(
"27c21ff3552e283180422900c9671e60e6f6922691750aa1b2ed3b3347b00000",
hex_decoded.to_hex()
)
}
#[cfg(nexa)]
#[test]
fn test_tokenid_from_cashaddr_value() {
let cashaddr_encoded =
json!("nexareg:tqnuy8ln25hzsvvqgg5spjt8reswda5jy6gh2z4pktknkv68kqqqqvcwuh9hk");
let cashaddr_decoded = tokenid_from_value(Some(&cashaddr_encoded))
.unwrap()
.unwrap();
assert_eq!(
"27c21ff3552e283180422900c9671e60e6f6922691750aa1b2ed3b3347b00000",
cashaddr_decoded.to_hex()
);
}
#[test]
fn test_parse_version_str() {
assert!(parse_version_str("1.4").is_some());
assert!(parse_version_str("garbage").is_none());
}
}