jam-tooling 0.1.16

Various helpful utilities for JAM tooling developers
Documentation
use jam_std_common::hash_raw;
use jam_types::{Memo, ServiceId, MEMO_LEN};

pub type Blob = Vec<u8>;

#[derive(Clone, Debug)]
#[allow(clippy::len_without_is_empty)]
pub enum DataIdLen {
	HashLen([u8; 32], usize),
	Data(Vec<u8>),
}

impl DataIdLen {
	pub fn hash(&self) -> [u8; 32] {
		match self {
			DataIdLen::HashLen(hash, _) => *hash,
			DataIdLen::Data(data) => hash_raw(data),
		}
	}
	pub fn len(&self) -> usize {
		match self {
			DataIdLen::HashLen(_, l) => *l,
			DataIdLen::Data(data) => data.len(),
		}
	}
	pub fn data(&self) -> Option<&[u8]> {
		match self {
			DataIdLen::HashLen(..) => None,
			DataIdLen::Data(ref data) => Some(data),
		}
	}
}

pub fn data_id_len(s: &str) -> anyhow::Result<DataIdLen> {
	match s {
		_ if s.len() > 65 &&
			s.bytes().take(64).all(|x| x.is_ascii_hexdigit()) &&
			s.find(':') == Some(64) &&
			s[65..].parse::<usize>().is_ok() =>
		{
			let mut hash = [0u8; 32];
			for (i, c) in s.as_bytes()[..64].chunks(2).enumerate() {
				hash[i] = u8::from_str_radix(std::str::from_utf8(c)?, 16)?;
			}
			Ok(DataIdLen::HashLen(hash, s[65..].parse::<usize>().expect("see above")))
		},
		_ => Ok(DataIdLen::Data(blob(s)?)),
	}
}

#[allow(dead_code)]
#[derive(Clone, Debug)]
pub enum DataId {
	Hash([u8; 32]),
	Data(Vec<u8>),
}

#[allow(dead_code)]
#[allow(clippy::len_without_is_empty)]
impl DataId {
	pub fn hash(&self) -> [u8; 32] {
		match self {
			DataId::Hash(hash) => *hash,
			DataId::Data(data) => hash_raw(data),
		}
	}
	pub fn len(&self) -> Option<usize> {
		match self {
			DataId::Hash(_) => None,
			DataId::Data(data) => Some(data.len()),
		}
	}
	pub fn data(&self) -> Option<&[u8]> {
		match self {
			DataId::Hash(..) => None,
			DataId::Data(ref data) => Some(data),
		}
	}
}

#[allow(dead_code)]
pub fn data_id(s: &str) -> anyhow::Result<DataId> {
	match s {
		_ if s.len() == 64 && s.bytes().all(|x| x.is_ascii_hexdigit()) => {
			let mut hash = [0u8; 32];
			for (i, c) in s.as_bytes().chunks(2).enumerate() {
				hash[i] = u8::from_str_radix(std::str::from_utf8(c)?, 16)?;
			}
			Ok(DataId::Hash(hash))
		},
		_ => Ok(DataId::Data(blob(s)?)),
	}
}

pub fn blob(s: &str) -> anyhow::Result<Blob> {
	match s {
		_ if (s.starts_with("./") || s.starts_with("/")) && std::path::Path::new(s).exists() =>
			Ok(std::fs::read(s)?),
		_ if s.starts_with("0x") &&
			s.len() % 2 == 0 &&
			s.bytes().skip(2).all(|x| x.is_ascii_hexdigit()) =>
		{
			let mut inner = Vec::with_capacity((s.len() - 2) / 2);
			for c in s.as_bytes()[2..].chunks(2) {
				inner.push(u8::from_str_radix(std::str::from_utf8(c)?, 16)?);
			}
			Ok(inner)
		},
		_ => Ok(s.as_bytes().to_vec()),
	}
}

pub fn gas(s: &str) -> anyhow::Result<u64> {
	if s == "max" {
		Ok(u64::MAX)
	} else {
		Ok(s.parse::<u64>()?)
	}
}

pub fn memo(s: &str) -> anyhow::Result<Memo> {
	match s {
		_ if s.len() == MEMO_LEN * 2 && s.bytes().all(|x| x.is_ascii_hexdigit()) => {
			let mut inner = Memo::default();
			for (i, c) in s.as_bytes().chunks(2).enumerate() {
				inner[i] = u8::from_str_radix(std::str::from_utf8(c)?, 16)?;
			}
			Ok(inner)
		},
		_ if s.len() <= MEMO_LEN => {
			let mut inner = Memo::default();
			inner.0[..s.len()].copy_from_slice(s.as_bytes());
			Ok(inner)
		},
		_ => Err(anyhow::anyhow!("Memo too long")),
	}
}

pub fn service_id(s: &str) -> anyhow::Result<ServiceId> {
	Ok(ServiceId::from_str_radix(s, 16)?)
}