use alloy_primitives::Address;
use alloy_sol_types::SolValue;
use blueprint_core::{
__composite_rejection as composite_rejection, __define_rejection as define_rejection,
extract::OptionalFromJobCallParts,
};
use blueprint_core::{FromJobCall, FromJobCallParts, JobCall, job::call::Parts as JobCallParts};
use blueprint_std::string::{String, ToString};
use bytes::Bytes;
fn decode_compact_length(data: &[u8]) -> Option<(usize, usize)> {
if data.is_empty() {
return None;
}
let first = data[0];
if first < 0x80 {
Some((first as usize, 1))
} else if first < 0xC0 {
if data.len() < 2 {
return None;
}
let len = ((first as usize & 0x3F) << 8) | (data[1] as usize);
Some((len, 2))
} else if first < 0xE0 {
if data.len() < 3 {
return None;
}
let len = ((first as usize & 0x1F) << 16) | ((data[1] as usize) << 8) | (data[2] as usize);
Some((len, 3))
} else if first < 0xF0 {
if data.len() < 4 {
return None;
}
let len = ((first as usize & 0x0F) << 24)
| ((data[1] as usize) << 16)
| ((data[2] as usize) << 8)
| (data[3] as usize);
Some((len, 4))
} else {
if data.len() < 5 {
return None;
}
let len = ((data[1] as usize) << 24)
| ((data[2] as usize) << 16)
| ((data[3] as usize) << 8)
| (data[4] as usize);
Some((len, 5))
}
}
fn try_decode_compact_string(data: &[u8]) -> Option<String> {
let (len, header_size) = decode_compact_length(data)?;
let string_start = header_size;
let string_end = string_start + len;
if string_end > data.len() {
return None;
}
let string_bytes = &data[string_start..string_end];
core::str::from_utf8(string_bytes).ok().map(String::from)
}
fn try_decode_compact_single_string_struct(data: &[u8]) -> Option<String> {
let (field_count, header_size) = decode_compact_length(data)?;
if field_count != 1 {
return None;
}
try_decode_compact_string(&data[header_size..])
}
fn looks_like_abi_encoded(data: &[u8]) -> bool {
if data.len() < 64 {
return false;
}
let first_31_zeros = data[..31].iter().all(|&b| b == 0);
let offset_is_32 = data[31] == 32;
first_31_zeros && offset_is_32
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CallId(pub u64);
impl CallId {
pub const METADATA_KEY: &'static str = "X-TANGLE-CALL-ID";
}
blueprint_core::__impl_deref!(CallId: u64);
blueprint_core::__impl_from!(u64, CallId);
define_rejection! {
#[body = "No CallId found in the metadata"]
pub struct MissingCallId;
}
define_rejection! {
#[body = "The call id in the metadata is not a valid integer"]
pub struct InvalidCallId;
}
composite_rejection! {
pub enum CallIdRejection {
MissingCallId,
InvalidCallId,
}
}
impl TryFrom<&mut JobCallParts> for CallId {
type Error = CallIdRejection;
fn try_from(parts: &mut JobCallParts) -> Result<Self, Self::Error> {
let call_id_raw = parts
.metadata
.get(Self::METADATA_KEY)
.ok_or(MissingCallId)?;
let call_id = call_id_raw.try_into().map_err(|_| InvalidCallId)?;
Ok(CallId(call_id))
}
}
impl<Ctx> FromJobCallParts<Ctx> for CallId
where
Ctx: Send + Sync,
{
type Rejection = CallIdRejection;
async fn from_job_call_parts(
parts: &mut JobCallParts,
_: &Ctx,
) -> Result<Self, Self::Rejection> {
CallId::try_from(parts)
}
}
impl<Ctx> OptionalFromJobCallParts<Ctx> for CallId
where
Ctx: Send + Sync,
{
type Rejection = CallIdRejection;
async fn from_job_call_parts(
parts: &mut JobCallParts,
_: &Ctx,
) -> Result<Option<Self>, Self::Rejection> {
match Self::try_from(parts) {
Ok(value) => Ok(Some(value)),
Err(CallIdRejection::MissingCallId(_)) => Ok(None),
Err(err) => Err(err),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ServiceId(pub u64);
impl ServiceId {
pub const METADATA_KEY: &'static str = "X-TANGLE-SERVICE-ID";
}
blueprint_core::__impl_deref!(ServiceId: u64);
blueprint_core::__impl_from!(u64, ServiceId);
define_rejection! {
#[body = "No ServiceId found in the metadata"]
pub struct MissingServiceId;
}
define_rejection! {
#[body = "The service id in the metadata is not a valid integer"]
pub struct InvalidServiceId;
}
composite_rejection! {
pub enum ServiceIdRejection {
MissingServiceId,
InvalidServiceId,
}
}
impl TryFrom<&mut JobCallParts> for ServiceId {
type Error = ServiceIdRejection;
fn try_from(parts: &mut JobCallParts) -> Result<Self, Self::Error> {
let service_id_raw = parts
.metadata
.get(Self::METADATA_KEY)
.ok_or(MissingServiceId)?;
let service_id = service_id_raw.try_into().map_err(|_| InvalidServiceId)?;
Ok(ServiceId(service_id))
}
}
impl<Ctx> FromJobCallParts<Ctx> for ServiceId
where
Ctx: Send + Sync,
{
type Rejection = ServiceIdRejection;
async fn from_job_call_parts(
parts: &mut JobCallParts,
_: &Ctx,
) -> Result<Self, Self::Rejection> {
ServiceId::try_from(parts)
}
}
impl<Ctx> OptionalFromJobCallParts<Ctx> for ServiceId
where
Ctx: Send + Sync,
{
type Rejection = ServiceIdRejection;
async fn from_job_call_parts(
parts: &mut JobCallParts,
_: &Ctx,
) -> Result<Option<Self>, Self::Rejection> {
match Self::try_from(parts) {
Ok(value) => Ok(Some(value)),
Err(ServiceIdRejection::MissingServiceId(_)) => Ok(None),
Err(err) => Err(err),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct JobIndex(pub u8);
impl JobIndex {
pub const METADATA_KEY: &'static str = "X-TANGLE-JOB-INDEX";
}
blueprint_core::__impl_deref!(JobIndex: u8);
blueprint_core::__impl_from!(u8, JobIndex);
define_rejection! {
#[body = "No JobIndex found in the metadata"]
pub struct MissingJobIndex;
}
define_rejection! {
#[body = "The job index in the metadata is not a valid u8"]
pub struct InvalidJobIndex;
}
composite_rejection! {
pub enum JobIndexRejection {
MissingJobIndex,
InvalidJobIndex,
}
}
impl TryFrom<&mut JobCallParts> for JobIndex {
type Error = JobIndexRejection;
fn try_from(parts: &mut JobCallParts) -> Result<Self, Self::Error> {
let job_index_raw = parts
.metadata
.get(Self::METADATA_KEY)
.ok_or(MissingJobIndex)?;
let bytes = job_index_raw.as_bytes();
let job_index = *bytes.first().ok_or(InvalidJobIndex)?;
Ok(JobIndex(job_index))
}
}
impl<Ctx> FromJobCallParts<Ctx> for JobIndex
where
Ctx: Send + Sync,
{
type Rejection = JobIndexRejection;
async fn from_job_call_parts(
parts: &mut JobCallParts,
_: &Ctx,
) -> Result<Self, Self::Rejection> {
JobIndex::try_from(parts)
}
}
impl<Ctx> OptionalFromJobCallParts<Ctx> for JobIndex
where
Ctx: Send + Sync,
{
type Rejection = JobIndexRejection;
async fn from_job_call_parts(
parts: &mut JobCallParts,
_: &Ctx,
) -> Result<Option<Self>, Self::Rejection> {
match Self::try_from(parts) {
Ok(value) => Ok(Some(value)),
Err(JobIndexRejection::MissingJobIndex(_)) => Ok(None),
Err(err) => Err(err),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BlockNumber(pub u64);
impl BlockNumber {
pub const METADATA_KEY: &'static str = "X-TANGLE-BLOCK-NUMBER";
}
blueprint_core::__impl_deref!(BlockNumber: u64);
blueprint_core::__impl_from!(u64, BlockNumber);
define_rejection! {
#[body = "No BlockNumber found in the metadata"]
pub struct MissingBlockNumber;
}
define_rejection! {
#[body = "The block number in the metadata is not a valid integer"]
pub struct InvalidBlockNumber;
}
composite_rejection! {
pub enum BlockNumberRejection {
MissingBlockNumber,
InvalidBlockNumber,
}
}
impl TryFrom<&mut JobCallParts> for BlockNumber {
type Error = BlockNumberRejection;
fn try_from(parts: &mut JobCallParts) -> Result<Self, Self::Error> {
let block_number_raw = parts
.metadata
.get(Self::METADATA_KEY)
.ok_or(MissingBlockNumber)?;
let block_number = block_number_raw
.try_into()
.map_err(|_| InvalidBlockNumber)?;
Ok(BlockNumber(block_number))
}
}
impl<Ctx> FromJobCallParts<Ctx> for BlockNumber
where
Ctx: Send + Sync,
{
type Rejection = BlockNumberRejection;
async fn from_job_call_parts(
parts: &mut JobCallParts,
_: &Ctx,
) -> Result<Self, Self::Rejection> {
BlockNumber::try_from(parts)
}
}
impl<Ctx> OptionalFromJobCallParts<Ctx> for BlockNumber
where
Ctx: Send + Sync,
{
type Rejection = BlockNumberRejection;
async fn from_job_call_parts(
parts: &mut JobCallParts,
_: &Ctx,
) -> Result<Option<Self>, Self::Rejection> {
match Self::try_from(parts) {
Ok(value) => Ok(Some(value)),
Err(BlockNumberRejection::MissingBlockNumber(_)) => Ok(None),
Err(err) => Err(err),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BlockHash(pub [u8; 32]);
impl BlockHash {
pub const METADATA_KEY: &'static str = "X-TANGLE-BLOCK-HASH";
}
impl core::ops::Deref for BlockHash {
type Target = [u8; 32];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<[u8; 32]> for BlockHash {
fn from(hash: [u8; 32]) -> Self {
BlockHash(hash)
}
}
define_rejection! {
#[body = "No BlockHash found in the metadata"]
pub struct MissingBlockHash;
}
define_rejection! {
#[body = "The block hash in the metadata is not valid"]
pub struct InvalidBlockHash;
}
composite_rejection! {
pub enum BlockHashRejection {
MissingBlockHash,
InvalidBlockHash,
}
}
impl TryFrom<&mut JobCallParts> for BlockHash {
type Error = BlockHashRejection;
fn try_from(parts: &mut JobCallParts) -> Result<Self, Self::Error> {
let block_hash_raw = parts
.metadata
.get(Self::METADATA_KEY)
.ok_or(MissingBlockHash)?;
let bytes = block_hash_raw.as_bytes();
let hash: [u8; 32] = (&bytes[..]).try_into().map_err(|_| InvalidBlockHash)?;
Ok(BlockHash(hash))
}
}
impl<Ctx> FromJobCallParts<Ctx> for BlockHash
where
Ctx: Send + Sync,
{
type Rejection = BlockHashRejection;
async fn from_job_call_parts(
parts: &mut JobCallParts,
_: &Ctx,
) -> Result<Self, Self::Rejection> {
BlockHash::try_from(parts)
}
}
impl<Ctx> OptionalFromJobCallParts<Ctx> for BlockHash
where
Ctx: Send + Sync,
{
type Rejection = BlockHashRejection;
async fn from_job_call_parts(
parts: &mut JobCallParts,
_: &Ctx,
) -> Result<Option<Self>, Self::Rejection> {
match Self::try_from(parts) {
Ok(value) => Ok(Some(value)),
Err(BlockHashRejection::MissingBlockHash(_)) => Ok(None),
Err(err) => Err(err),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Timestamp(pub u64);
impl Timestamp {
pub const METADATA_KEY: &'static str = "X-TANGLE-TIMESTAMP";
}
blueprint_core::__impl_deref!(Timestamp: u64);
blueprint_core::__impl_from!(u64, Timestamp);
define_rejection! {
#[body = "No Timestamp found in the metadata"]
pub struct MissingTimestamp;
}
define_rejection! {
#[body = "The timestamp in the metadata is not a valid integer"]
pub struct InvalidTimestamp;
}
composite_rejection! {
pub enum TimestampRejection {
MissingTimestamp,
InvalidTimestamp,
}
}
impl TryFrom<&mut JobCallParts> for Timestamp {
type Error = TimestampRejection;
fn try_from(parts: &mut JobCallParts) -> Result<Self, Self::Error> {
let timestamp_raw = parts
.metadata
.get(Self::METADATA_KEY)
.ok_or(MissingTimestamp)?;
let timestamp = timestamp_raw.try_into().map_err(|_| InvalidTimestamp)?;
Ok(Timestamp(timestamp))
}
}
impl<Ctx> FromJobCallParts<Ctx> for Timestamp
where
Ctx: Send + Sync,
{
type Rejection = TimestampRejection;
async fn from_job_call_parts(
parts: &mut JobCallParts,
_: &Ctx,
) -> Result<Self, Self::Rejection> {
Timestamp::try_from(parts)
}
}
impl<Ctx> OptionalFromJobCallParts<Ctx> for Timestamp
where
Ctx: Send + Sync,
{
type Rejection = TimestampRejection;
async fn from_job_call_parts(
parts: &mut JobCallParts,
_: &Ctx,
) -> Result<Option<Self>, Self::Rejection> {
match Self::try_from(parts) {
Ok(value) => Ok(Some(value)),
Err(TimestampRejection::MissingTimestamp(_)) => Ok(None),
Err(err) => Err(err),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Caller(pub [u8; 20]);
impl Caller {
pub const METADATA_KEY: &'static str = "X-TANGLE-CALLER";
#[must_use]
pub fn as_address(&self) -> Address {
Address::from_slice(&self.0)
}
}
impl core::ops::Deref for Caller {
type Target = [u8; 20];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<[u8; 20]> for Caller {
fn from(addr: [u8; 20]) -> Self {
Caller(addr)
}
}
impl From<Address> for Caller {
fn from(addr: Address) -> Self {
Caller(addr.0.0)
}
}
define_rejection! {
#[body = "No Caller found in the metadata"]
pub struct MissingCaller;
}
define_rejection! {
#[body = "The caller in the metadata is not a valid address"]
pub struct InvalidCaller;
}
composite_rejection! {
pub enum CallerRejection {
MissingCaller,
InvalidCaller,
}
}
impl TryFrom<&mut JobCallParts> for Caller {
type Error = CallerRejection;
fn try_from(parts: &mut JobCallParts) -> Result<Self, Self::Error> {
let caller_raw = parts
.metadata
.get(Self::METADATA_KEY)
.ok_or(MissingCaller)?;
let bytes = caller_raw.as_bytes();
let addr: [u8; 20] = (&bytes[..]).try_into().map_err(|_| InvalidCaller)?;
Ok(Caller(addr))
}
}
impl<Ctx> FromJobCallParts<Ctx> for Caller
where
Ctx: Send + Sync,
{
type Rejection = CallerRejection;
async fn from_job_call_parts(
parts: &mut JobCallParts,
_: &Ctx,
) -> Result<Self, Self::Rejection> {
Caller::try_from(parts)
}
}
impl<Ctx> OptionalFromJobCallParts<Ctx> for Caller
where
Ctx: Send + Sync,
{
type Rejection = CallerRejection;
async fn from_job_call_parts(
parts: &mut JobCallParts,
_: &Ctx,
) -> Result<Option<Self>, Self::Rejection> {
match Self::try_from(parts) {
Ok(value) => Ok(Some(value)),
Err(CallerRejection::MissingCaller(_)) => Ok(None),
Err(err) => Err(err),
}
}
}
define_rejection! {
#[body = "Failed to decode the job input (tried both compact binary and ABI formats)"]
pub struct AbiDecodeError;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TangleArg<T>(pub T);
impl<T> core::ops::Deref for TangleArg<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> From<T> for TangleArg<T> {
fn from(value: T) -> Self {
TangleArg(value)
}
}
impl<T, Ctx> FromJobCall<Ctx> for TangleArg<T>
where
T: SolValue + Send + From<<T::SolType as alloy_sol_types::SolType>::RustType>,
for<'a> <<T as SolValue>::SolType as alloy_sol_types::SolType>::Token<'a>:
alloy_sol_types::abi::TokenSeq<'a>,
Ctx: Send + Sync,
{
type Rejection = AbiDecodeError;
async fn from_job_call(call: JobCall, _ctx: &Ctx) -> Result<Self, Self::Rejection> {
let (_, body) = call.into_parts();
if looks_like_abi_encoded(&body) {
if let Ok(value) = T::abi_decode(&body) {
return Ok(TangleArg(value));
}
if let Ok(value) = T::abi_decode_sequence(&body) {
return Ok(TangleArg(value));
}
if let Ok(value) = try_decode_compact::<T>(&body) {
return Ok(TangleArg(value));
}
} else {
if let Ok(value) = try_decode_compact::<T>(&body) {
return Ok(TangleArg(value));
}
if let Ok(value) = T::abi_decode(&body) {
return Ok(TangleArg(value));
}
if let Ok(value) = T::abi_decode_sequence(&body) {
return Ok(TangleArg(value));
}
}
Err(AbiDecodeError)
}
}
fn try_decode_compact<T>(data: &[u8]) -> Result<T, ()>
where
T: SolValue + From<<T::SolType as alloy_sol_types::SolType>::RustType>,
{
use alloy_sol_types::SolType;
let type_name_str = <T::SolType>::SOL_NAME;
if type_name_str.starts_with('(') || !type_name_str.contains('(') {
if let Some(decoded_string) = try_decode_compact_single_string_struct(data) {
let abi_encoded = alloy_sol_types::SolValue::abi_encode(&(decoded_string,));
if let Ok(value) = T::abi_decode(&abi_encoded) {
return Ok(value);
}
}
if type_name_str == "string" {
if let Some(decoded_string) = try_decode_compact_string(data) {
let abi_encoded = alloy_sol_types::SolValue::abi_encode(&decoded_string);
if let Ok(value) = T::abi_decode(&abi_encoded) {
return Ok(value);
}
}
}
}
Err(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TangleResult<T>(pub T);
impl<T> core::ops::Deref for TangleResult<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> From<T> for TangleResult<T> {
fn from(value: T) -> Self {
TangleResult(value)
}
}
impl<T: SolValue> blueprint_core::IntoJobResult for TangleResult<T> {
fn into_job_result(self) -> Option<blueprint_core::JobResult> {
let encoded = self.0.abi_encode();
Some(blueprint_core::JobResult::Ok {
head: blueprint_core::job::result::Parts::new(),
body: Bytes::from(encoded),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use blueprint_core::FromJobCallParts;
#[test]
fn test_job_index_extraction() {
let mut parts = JobCallParts::new(0);
parts.metadata.insert(JobIndex::METADATA_KEY, [5u8]);
let job_index = JobIndex::try_from(&mut parts).expect("should extract job index");
assert_eq!(*job_index, 5);
}
#[test]
fn test_job_index_different_values() {
let mut parts0 = JobCallParts::new(0);
parts0.metadata.insert(JobIndex::METADATA_KEY, [0u8]);
let idx0 = JobIndex::try_from(&mut parts0).unwrap();
assert_eq!(*idx0, 0);
let mut parts1 = JobCallParts::new(0);
parts1.metadata.insert(JobIndex::METADATA_KEY, [1u8]);
let idx1 = JobIndex::try_from(&mut parts1).unwrap();
assert_eq!(*idx1, 1);
let mut parts255 = JobCallParts::new(0);
parts255.metadata.insert(JobIndex::METADATA_KEY, [255u8]);
let idx255 = JobIndex::try_from(&mut parts255).unwrap();
assert_eq!(*idx255, 255);
assert_ne!(idx0, idx1);
assert_ne!(idx1, idx255);
}
#[test]
fn test_job_index_missing() {
let mut parts = JobCallParts::new(0);
let result = JobIndex::try_from(&mut parts);
assert!(result.is_err());
}
#[test]
fn test_job_index_into_u8() {
let job_index = JobIndex(42);
let val: u8 = *job_index;
assert_eq!(val, 42);
}
#[test]
fn test_job_index_metadata_key() {
assert_eq!(JobIndex::METADATA_KEY, "X-TANGLE-JOB-INDEX");
}
#[test]
fn test_per_job_routing_simulation() {
let mut parts_job0 = JobCallParts::new(0);
parts_job0.metadata.insert(JobIndex::METADATA_KEY, [0u8]);
parts_job0
.metadata
.insert(ServiceId::METADATA_KEY, 1u64.to_be_bytes());
let mut parts_job1 = JobCallParts::new(0);
parts_job1.metadata.insert(JobIndex::METADATA_KEY, [1u8]);
parts_job1
.metadata
.insert(ServiceId::METADATA_KEY, 1u64.to_be_bytes());
let job_idx0 = JobIndex::try_from(&mut parts_job0).unwrap();
let job_idx1 = JobIndex::try_from(&mut parts_job1).unwrap();
assert_eq!(*job_idx0, 0);
assert_eq!(*job_idx1, 1);
assert_ne!(job_idx0, job_idx1);
}
#[test]
fn test_decode_compact_length_single_byte() {
assert_eq!(decode_compact_length(&[0x00]), Some((0, 1)));
assert_eq!(decode_compact_length(&[0x05]), Some((5, 1)));
assert_eq!(decode_compact_length(&[0x7F]), Some((127, 1)));
}
#[test]
fn test_decode_compact_length_two_bytes() {
assert_eq!(decode_compact_length(&[0x80, 0x80]), Some((128, 2)));
assert_eq!(decode_compact_length(&[0x80, 0xFF]), Some((255, 2)));
}
#[test]
fn test_decode_compact_length_empty() {
assert_eq!(decode_compact_length(&[]), None);
}
#[test]
fn test_try_decode_compact_string() {
let data = b"\x08TestUser";
assert_eq!(
try_decode_compact_string(data),
Some("TestUser".to_string())
);
}
#[test]
fn test_try_decode_compact_string_empty() {
let data = b"\x00";
assert_eq!(try_decode_compact_string(data), Some(String::new()));
}
#[test]
fn test_try_decode_compact_string_hello() {
let data = b"\x05Hello";
assert_eq!(try_decode_compact_string(data), Some("Hello".to_string()));
}
#[test]
fn test_try_decode_compact_single_string_struct() {
let data = b"\x01\x08TestUser";
assert_eq!(
try_decode_compact_single_string_struct(data),
Some("TestUser".to_string())
);
}
#[test]
fn test_looks_like_abi_encoded() {
let mut abi_data = vec![0u8; 96];
abi_data[31] = 32; abi_data[63] = 5; abi_data[64..69].copy_from_slice(b"Hello");
assert!(looks_like_abi_encoded(&abi_data));
}
#[test]
fn test_looks_like_abi_encoded_false_for_short_data() {
let short_data = b"\x08TestUser";
assert!(!looks_like_abi_encoded(short_data));
}
#[test]
fn test_looks_like_abi_encoded_false_for_compact() {
let compact_data = b"\x01\x08TestUser";
assert!(!looks_like_abi_encoded(compact_data));
}
#[tokio::test]
async fn optional_call_id_missing_returns_none() {
let mut parts = JobCallParts::new(0);
let extracted = Option::<CallId>::from_job_call_parts(&mut parts, &())
.await
.unwrap();
assert_eq!(extracted, None);
}
#[tokio::test]
async fn optional_call_id_invalid_returns_err() {
let mut parts = JobCallParts::new(0);
parts.metadata.insert(CallId::METADATA_KEY, [1u8]);
let extracted = Option::<CallId>::from_job_call_parts(&mut parts, &()).await;
assert!(matches!(extracted, Err(CallIdRejection::InvalidCallId(_))));
}
#[tokio::test]
async fn optional_service_id_missing_returns_none() {
let mut parts = JobCallParts::new(0);
let extracted = Option::<ServiceId>::from_job_call_parts(&mut parts, &())
.await
.unwrap();
assert_eq!(extracted, None);
}
#[tokio::test]
async fn optional_service_id_invalid_returns_err() {
let mut parts = JobCallParts::new(0);
parts.metadata.insert(ServiceId::METADATA_KEY, [1u8]);
let extracted = Option::<ServiceId>::from_job_call_parts(&mut parts, &()).await;
assert!(matches!(
extracted,
Err(ServiceIdRejection::InvalidServiceId(_))
));
}
#[tokio::test]
async fn optional_job_index_missing_returns_none() {
let mut parts = JobCallParts::new(0);
let extracted = Option::<JobIndex>::from_job_call_parts(&mut parts, &())
.await
.unwrap();
assert_eq!(extracted, None);
}
#[tokio::test]
async fn optional_job_index_invalid_returns_err() {
let mut parts = JobCallParts::new(0);
parts
.metadata
.insert(JobIndex::METADATA_KEY, Vec::<u8>::new());
let extracted = Option::<JobIndex>::from_job_call_parts(&mut parts, &()).await;
assert!(matches!(
extracted,
Err(JobIndexRejection::InvalidJobIndex(_))
));
}
#[tokio::test]
async fn optional_block_number_missing_returns_none() {
let mut parts = JobCallParts::new(0);
let extracted = Option::<BlockNumber>::from_job_call_parts(&mut parts, &())
.await
.unwrap();
assert_eq!(extracted, None);
}
#[tokio::test]
async fn optional_block_number_invalid_returns_err() {
let mut parts = JobCallParts::new(0);
parts.metadata.insert(BlockNumber::METADATA_KEY, [1u8]);
let extracted = Option::<BlockNumber>::from_job_call_parts(&mut parts, &()).await;
assert!(matches!(
extracted,
Err(BlockNumberRejection::InvalidBlockNumber(_))
));
}
#[tokio::test]
async fn optional_block_hash_missing_returns_none() {
let mut parts = JobCallParts::new(0);
let extracted = Option::<BlockHash>::from_job_call_parts(&mut parts, &())
.await
.unwrap();
assert_eq!(extracted, None);
}
#[tokio::test]
async fn optional_block_hash_invalid_returns_err() {
let mut parts = JobCallParts::new(0);
parts.metadata.insert(BlockHash::METADATA_KEY, [0u8; 31]);
let extracted = Option::<BlockHash>::from_job_call_parts(&mut parts, &()).await;
assert!(matches!(
extracted,
Err(BlockHashRejection::InvalidBlockHash(_))
));
}
#[tokio::test]
async fn optional_timestamp_missing_returns_none() {
let mut parts = JobCallParts::new(0);
let extracted = Option::<Timestamp>::from_job_call_parts(&mut parts, &())
.await
.unwrap();
assert_eq!(extracted, None);
}
#[tokio::test]
async fn optional_timestamp_invalid_returns_err() {
let mut parts = JobCallParts::new(0);
parts.metadata.insert(Timestamp::METADATA_KEY, [1u8]);
let extracted = Option::<Timestamp>::from_job_call_parts(&mut parts, &()).await;
assert!(matches!(
extracted,
Err(TimestampRejection::InvalidTimestamp(_))
));
}
#[tokio::test]
async fn optional_caller_missing_returns_none() {
let mut parts = JobCallParts::new(0);
let extracted = Option::<Caller>::from_job_call_parts(&mut parts, &())
.await
.unwrap();
assert_eq!(extracted, None);
}
#[tokio::test]
async fn optional_caller_invalid_returns_err() {
let mut parts = JobCallParts::new(0);
parts.metadata.insert(Caller::METADATA_KEY, [0u8; 19]);
let extracted = Option::<Caller>::from_job_call_parts(&mut parts, &()).await;
assert!(matches!(extracted, Err(CallerRejection::InvalidCaller(_))));
}
}