diff --git a/codec/src/address.rs b/codec/src/address.rs
new file mode 100644
index 00000000..bf871e0b
--- /dev/null
+++ b/codec/src/address.rs
@@ -0,0 +1,93 @@
+use acropolis_common::{
+ Address, ByronAddress, NetworkId, ShelleyAddress, ShelleyAddressDelegationPart,
+ ShelleyAddressPaymentPart, ShelleyAddressPointer, StakeAddress, StakeCredential,
+};
+use anyhow::Result;
+use pallas::ledger::{
+ addresses as pallas_addresses, primitives::StakeCredential as PallasStakeCredential,
+};
+
+use crate::{tx::map_network, utils::to_hash};
+
+/// Derive our Address from a Pallas address
+// This is essentially a 1:1 mapping but makes the Message definitions independent
+// of Pallas
+pub fn map_address(address: &pallas_addresses::Address) -> Result
{
+ match address {
+ pallas_addresses::Address::Byron(byron_address) => Ok(Address::Byron(ByronAddress {
+ payload: byron_address.payload.to_vec(),
+ })),
+
+ pallas_addresses::Address::Shelley(shelley_address) => {
+ Ok(Address::Shelley(ShelleyAddress {
+ network: map_network(shelley_address.network())?,
+
+ payment: match shelley_address.payment() {
+ pallas_addresses::ShelleyPaymentPart::Key(hash) => {
+ ShelleyAddressPaymentPart::PaymentKeyHash(to_hash(hash))
+ }
+ pallas_addresses::ShelleyPaymentPart::Script(hash) => {
+ ShelleyAddressPaymentPart::ScriptHash(to_hash(hash))
+ }
+ },
+
+ delegation: match shelley_address.delegation() {
+ pallas_addresses::ShelleyDelegationPart::Null => {
+ ShelleyAddressDelegationPart::None
+ }
+ pallas_addresses::ShelleyDelegationPart::Key(hash) => {
+ ShelleyAddressDelegationPart::StakeKeyHash(to_hash(hash))
+ }
+ pallas_addresses::ShelleyDelegationPart::Script(hash) => {
+ ShelleyAddressDelegationPart::ScriptHash(to_hash(hash))
+ }
+ pallas_addresses::ShelleyDelegationPart::Pointer(pointer) => {
+ ShelleyAddressDelegationPart::Pointer(ShelleyAddressPointer {
+ slot: pointer.slot(),
+ tx_index: pointer.tx_idx(),
+ cert_index: pointer.cert_idx(),
+ })
+ }
+ },
+ }))
+ }
+
+ pallas_addresses::Address::Stake(stake_address) => Ok(Address::Stake(StakeAddress {
+ network: map_network(stake_address.network())?,
+ credential: match stake_address.payload() {
+ pallas_addresses::StakePayload::Stake(hash) => {
+ StakeCredential::AddrKeyHash(to_hash(hash))
+ }
+ pallas_addresses::StakePayload::Script(hash) => {
+ StakeCredential::ScriptHash(to_hash(hash))
+ }
+ },
+ })),
+ }
+}
+
+/// Map a Pallas StakeCredential to ours
+pub fn map_stake_credential(cred: &PallasStakeCredential) -> StakeCredential {
+ match cred {
+ PallasStakeCredential::AddrKeyhash(key_hash) => {
+ StakeCredential::AddrKeyHash(to_hash(key_hash))
+ }
+ PallasStakeCredential::ScriptHash(script_hash) => {
+ StakeCredential::ScriptHash(to_hash(script_hash))
+ }
+ }
+}
+
+/// Map a PallasStakeCredential to our StakeAddress
+pub fn map_stake_address(cred: &PallasStakeCredential, network_id: NetworkId) -> StakeAddress {
+ let payload = match cred {
+ PallasStakeCredential::AddrKeyhash(key_hash) => {
+ StakeCredential::AddrKeyHash(to_hash(key_hash))
+ }
+ PallasStakeCredential::ScriptHash(script_hash) => {
+ StakeCredential::ScriptHash(to_hash(script_hash))
+ }
+ };
+
+ StakeAddress::new(payload, network_id)
+}
diff --git a/codec/src/certs.rs b/codec/src/certs.rs
new file mode 100644
index 00000000..c0b8770c
--- /dev/null
+++ b/codec/src/certs.rs
@@ -0,0 +1,368 @@
+use crate::{
+ address::{map_stake_address, map_stake_credential},
+ utils::*,
+};
+use acropolis_common::*;
+use anyhow::{Result, anyhow};
+use pallas_primitives::{Nullable, alonzo, conway};
+use pallas_traverse::MultiEraCert;
+
+#[allow(clippy::too_many_arguments)]
+pub fn to_pool_reg(
+ operator: &pallas_primitives::PoolKeyhash,
+ vrf_keyhash: &pallas_primitives::VrfKeyhash,
+ pledge: &pallas_primitives::Coin,
+ cost: &pallas_primitives::Coin,
+ margin: &pallas_primitives::UnitInterval,
+ reward_account: &pallas_primitives::RewardAccount,
+ pool_owners: &[pallas_primitives::AddrKeyhash],
+ relays: &[pallas_primitives::Relay],
+ pool_metadata: &Nullable,
+ network_id: NetworkId,
+ force_reward_network_id: bool,
+) -> Result {
+ Ok(PoolRegistration {
+ operator: to_pool_id(operator),
+ vrf_key_hash: to_vrf_key(vrf_keyhash),
+ pledge: *pledge,
+ cost: *cost,
+ margin: Ratio {
+ numerator: margin.numerator,
+ denominator: margin.denominator,
+ },
+ reward_account: if force_reward_network_id {
+ StakeAddress::new(
+ StakeAddress::from_binary(reward_account)?.credential,
+ network_id.clone(),
+ )
+ } else {
+ StakeAddress::from_binary(reward_account)?
+ },
+ pool_owners: pool_owners
+ .iter()
+ .map(|v| {
+ StakeAddress::new(StakeCredential::AddrKeyHash(to_hash(v)), network_id.clone())
+ })
+ .collect(),
+ relays: relays.iter().map(map_relay).collect(),
+ pool_metadata: match pool_metadata {
+ Nullable::Some(md) => Some(PoolMetadata {
+ url: md.url.clone(),
+ hash: md.hash.to_vec(),
+ }),
+ _ => None,
+ },
+ })
+}
+
+/// Derive our TxCertificate from a Pallas Certificate
+pub fn map_certificate(
+ cert: &MultiEraCert,
+ tx_identifier: TxIdentifier,
+ cert_index: usize,
+ network_id: NetworkId,
+) -> Result {
+ match cert {
+ MultiEraCert::NotApplicable => Err(anyhow!("Not applicable cert!")),
+
+ MultiEraCert::AlonzoCompatible(cert) => match cert.as_ref().as_ref() {
+ alonzo::Certificate::StakeRegistration(cred) => Ok(TxCertificateWithPos {
+ cert: TxCertificate::StakeRegistration(map_stake_address(cred, network_id)),
+ tx_identifier,
+ cert_index: cert_index.try_into().unwrap(),
+ }),
+ alonzo::Certificate::StakeDeregistration(cred) => Ok(TxCertificateWithPos {
+ cert: TxCertificate::StakeDeregistration(map_stake_address(cred, network_id)),
+ tx_identifier,
+ cert_index: cert_index.try_into().unwrap(),
+ }),
+ alonzo::Certificate::StakeDelegation(cred, pool_key_hash) => Ok(TxCertificateWithPos {
+ cert: TxCertificate::StakeDelegation(StakeDelegation {
+ stake_address: map_stake_address(cred, network_id),
+ operator: to_pool_id(pool_key_hash),
+ }),
+ tx_identifier,
+ cert_index: cert_index.try_into().unwrap(),
+ }),
+ alonzo::Certificate::PoolRegistration {
+ operator,
+ vrf_keyhash,
+ pledge,
+ cost,
+ margin,
+ reward_account,
+ pool_owners,
+ relays,
+ pool_metadata,
+ } => Ok(TxCertificateWithPos {
+ cert: TxCertificate::PoolRegistration(to_pool_reg(
+ operator,
+ vrf_keyhash,
+ pledge,
+ cost,
+ margin,
+ reward_account,
+ pool_owners,
+ relays,
+ pool_metadata,
+ network_id,
+ false,
+ )?),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ }),
+ alonzo::Certificate::PoolRetirement(pool_key_hash, epoch) => Ok(TxCertificateWithPos {
+ cert: TxCertificate::PoolRetirement(PoolRetirement {
+ operator: to_pool_id(pool_key_hash),
+ epoch: *epoch,
+ }),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ }),
+ alonzo::Certificate::GenesisKeyDelegation(
+ genesis_hash,
+ genesis_delegate_hash,
+ vrf_key_hash,
+ ) => Ok(TxCertificateWithPos {
+ cert: TxCertificate::GenesisKeyDelegation(GenesisKeyDelegation {
+ genesis_hash: genesis_to_hash(genesis_hash),
+ genesis_delegate_hash: genesis_delegate_to_hash(genesis_delegate_hash),
+ vrf_key_hash: to_vrf_key(vrf_key_hash),
+ }),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ }),
+ alonzo::Certificate::MoveInstantaneousRewardsCert(mir) => Ok(TxCertificateWithPos {
+ cert: TxCertificate::MoveInstantaneousReward(MoveInstantaneousReward {
+ source: match mir.source {
+ alonzo::InstantaneousRewardSource::Reserves => {
+ InstantaneousRewardSource::Reserves
+ }
+ alonzo::InstantaneousRewardSource::Treasury => {
+ InstantaneousRewardSource::Treasury
+ }
+ },
+ target: match &mir.target {
+ alonzo::InstantaneousRewardTarget::StakeCredentials(creds) => {
+ InstantaneousRewardTarget::StakeAddresses(
+ creds
+ .iter()
+ .map(|(sc, v)| (map_stake_address(sc, network_id.clone()), *v))
+ .collect(),
+ )
+ }
+ alonzo::InstantaneousRewardTarget::OtherAccountingPot(n) => {
+ InstantaneousRewardTarget::OtherAccountingPot(*n)
+ }
+ },
+ }),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ }),
+ },
+
+ // Now repeated for a different type!
+ MultiEraCert::Conway(cert) => {
+ match cert.as_ref().as_ref() {
+ conway::Certificate::StakeRegistration(cred) => Ok(TxCertificateWithPos {
+ cert: TxCertificate::StakeRegistration(map_stake_address(cred, network_id)),
+ tx_identifier,
+
+ cert_index: cert_index.try_into().unwrap(),
+ }),
+
+ conway::Certificate::StakeDeregistration(cred) => Ok(TxCertificateWithPos {
+ cert: TxCertificate::StakeDeregistration(map_stake_address(cred, network_id)),
+ tx_identifier,
+ cert_index: cert_index.try_into().unwrap(),
+ }),
+
+ conway::Certificate::StakeDelegation(cred, pool_key_hash) => {
+ Ok(TxCertificateWithPos {
+ cert: TxCertificate::StakeDelegation(StakeDelegation {
+ stake_address: map_stake_address(cred, network_id),
+ operator: to_pool_id(pool_key_hash),
+ }),
+ tx_identifier,
+ cert_index: cert_index.try_into().unwrap(),
+ })
+ }
+
+ conway::Certificate::PoolRegistration {
+ // TODO relays, pool_metadata
+ operator,
+ vrf_keyhash,
+ pledge,
+ cost,
+ margin,
+ reward_account,
+ pool_owners,
+ relays,
+ pool_metadata,
+ } => Ok(TxCertificateWithPos {
+ cert: TxCertificate::PoolRegistration(to_pool_reg(
+ operator,
+ vrf_keyhash,
+ pledge,
+ cost,
+ margin,
+ reward_account,
+ pool_owners,
+ relays,
+ pool_metadata,
+ network_id,
+ // Force networkId - in mainnet epoch 208, one SPO (c63dab6d780a) uses
+ // an e0 (testnet!) address, and this then fails to match their actual
+ // reward account (e1). Feels like this should have been
+ // a validation failure, but clearly wasn't!
+ true,
+ )?),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ }),
+ conway::Certificate::PoolRetirement(pool_key_hash, epoch) => {
+ Ok(TxCertificateWithPos {
+ cert: TxCertificate::PoolRetirement(PoolRetirement {
+ operator: to_pool_id(pool_key_hash),
+ epoch: *epoch,
+ }),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ })
+ }
+
+ conway::Certificate::Reg(cred, coin) => Ok(TxCertificateWithPos {
+ cert: TxCertificate::Registration(Registration {
+ stake_address: map_stake_address(cred, network_id),
+ deposit: *coin,
+ }),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ }),
+
+ conway::Certificate::UnReg(cred, coin) => Ok(TxCertificateWithPos {
+ cert: TxCertificate::Deregistration(Deregistration {
+ stake_address: map_stake_address(cred, network_id),
+ refund: *coin,
+ }),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ }),
+
+ conway::Certificate::VoteDeleg(cred, drep) => Ok(TxCertificateWithPos {
+ cert: TxCertificate::VoteDelegation(VoteDelegation {
+ stake_address: map_stake_address(cred, network_id),
+ drep: map_drep(drep),
+ }),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ }),
+
+ conway::Certificate::StakeVoteDeleg(cred, pool_key_hash, drep) => {
+ Ok(TxCertificateWithPos {
+ cert: TxCertificate::StakeAndVoteDelegation(StakeAndVoteDelegation {
+ stake_address: map_stake_address(cred, network_id),
+ operator: to_pool_id(pool_key_hash),
+ drep: map_drep(drep),
+ }),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ })
+ }
+
+ conway::Certificate::StakeRegDeleg(cred, pool_key_hash, coin) => {
+ Ok(TxCertificateWithPos {
+ cert: TxCertificate::StakeRegistrationAndDelegation(
+ StakeRegistrationAndDelegation {
+ stake_address: map_stake_address(cred, network_id),
+ operator: to_pool_id(pool_key_hash),
+ deposit: *coin,
+ },
+ ),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ })
+ }
+
+ conway::Certificate::VoteRegDeleg(cred, drep, coin) => Ok(TxCertificateWithPos {
+ cert: TxCertificate::StakeRegistrationAndVoteDelegation(
+ StakeRegistrationAndVoteDelegation {
+ stake_address: map_stake_address(cred, network_id),
+ drep: map_drep(drep),
+ deposit: *coin,
+ },
+ ),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ }),
+
+ conway::Certificate::StakeVoteRegDeleg(cred, pool_key_hash, drep, coin) => {
+ Ok(TxCertificateWithPos {
+ cert: TxCertificate::StakeRegistrationAndStakeAndVoteDelegation(
+ StakeRegistrationAndStakeAndVoteDelegation {
+ stake_address: map_stake_address(cred, network_id),
+ operator: to_pool_id(pool_key_hash),
+ drep: map_drep(drep),
+ deposit: *coin,
+ },
+ ),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ })
+ }
+
+ conway::Certificate::AuthCommitteeHot(cold_cred, hot_cred) => {
+ Ok(TxCertificateWithPos {
+ cert: TxCertificate::AuthCommitteeHot(AuthCommitteeHot {
+ cold_credential: map_stake_credential(cold_cred),
+ hot_credential: map_stake_credential(hot_cred),
+ }),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ })
+ }
+
+ conway::Certificate::ResignCommitteeCold(cold_cred, anchor) => {
+ Ok(TxCertificateWithPos {
+ cert: TxCertificate::ResignCommitteeCold(ResignCommitteeCold {
+ cold_credential: map_stake_credential(cold_cred),
+ anchor: map_nullable_anchor(anchor),
+ }),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ })
+ }
+
+ conway::Certificate::RegDRepCert(cred, coin, anchor) => Ok(TxCertificateWithPos {
+ cert: TxCertificate::DRepRegistration(DRepRegistration {
+ credential: map_stake_credential(cred),
+ deposit: *coin,
+ anchor: map_nullable_anchor(anchor),
+ }),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ }),
+
+ conway::Certificate::UnRegDRepCert(cred, coin) => Ok(TxCertificateWithPos {
+ cert: TxCertificate::DRepDeregistration(DRepDeregistration {
+ credential: map_stake_credential(cred),
+ refund: *coin,
+ }),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ }),
+
+ conway::Certificate::UpdateDRepCert(cred, anchor) => Ok(TxCertificateWithPos {
+ cert: TxCertificate::DRepUpdate(DRepUpdate {
+ credential: map_stake_credential(cred),
+ anchor: map_nullable_anchor(anchor),
+ }),
+ tx_identifier,
+ cert_index: cert_index as u64,
+ }),
+ }
+ }
+
+ _ => Err(anyhow!("Unknown certificate era {:?} ignored", cert)),
+ }
+}
diff --git a/codec/src/lib.rs b/codec/src/lib.rs
index 7cb7bc0d..88b65133 100644
--- a/codec/src/lib.rs
+++ b/codec/src/lib.rs
@@ -1,2 +1,15 @@
+pub mod address;
pub mod block;
-pub mod map_parameters;
+pub mod certs;
+pub mod parameter;
+pub mod tx;
+pub mod utils;
+pub mod utxo;
+
+pub use address::*;
+pub use block::*;
+pub use certs::*;
+pub use parameter::*;
+pub use tx::*;
+pub use utils::*;
+pub use utxo::*;
diff --git a/codec/src/map_parameters.rs b/codec/src/map_parameters.rs
deleted file mode 100644
index 150609d3..00000000
--- a/codec/src/map_parameters.rs
+++ /dev/null
@@ -1,1154 +0,0 @@
-//! Acropolis transaction unpacker module for Caryatid
-//! Performs conversion from Pallas library data to Acropolis
-
-use anyhow::{Result, anyhow, bail};
-use pallas::ledger::{
- primitives::{
- ExUnitPrices as PallasExUnitPrices, Nullable, ProtocolVersion as PallasProtocolVersion,
- Relay as PallasRelay, ScriptHash, StakeCredential as PallasStakeCredential, alonzo,
- babbage, conway,
- },
- traverse::{MultiEraCert, MultiEraPolicyAssets, MultiEraValue},
- *,
-};
-
-use acropolis_common::hash::Hash;
-use acropolis_common::{
- protocol_params::{Nonce, NonceVariant, ProtocolVersion},
- rational_number::RationalNumber,
- *,
-};
-use pallas_primitives::conway::PseudoScript;
-use std::{
- collections::{HashMap, HashSet},
- net::{Ipv4Addr, Ipv6Addr},
-};
-
-/// Map Pallas Network to our NetworkId
-pub fn map_network(network: addresses::Network) -> Result {
- match network {
- addresses::Network::Mainnet => Ok(NetworkId::Mainnet),
- addresses::Network::Testnet => Ok(NetworkId::Testnet),
- _ => Err(anyhow!("Unknown network in address")),
- }
-}
-
-/// Convert a Pallas Hash reference to an Acropolis Hash (owned)
-/// Works for any hash size N
-pub fn to_hash(pallas_hash: &pallas_primitives::Hash) -> Hash {
- Hash::try_from(pallas_hash.as_ref()).unwrap()
-}
-
-/// Convert a Pallas Hash reference to an Acropolis Hash (owned)
-/// Works for any hash size N
-pub fn genesis_to_hash(pallas_hash: &pallas_primitives::Genesishash) -> Hash<28> {
- Hash::try_from(pallas_hash.as_ref()).unwrap()
-}
-
-/// Convert a Pallas Hash reference to an Acropolis Hash (owned)
-/// Works for any hash size N
-pub fn genesis_delegate_to_hash(pallas_hash: &pallas_primitives::GenesisDelegateHash) -> PoolId {
- PoolId::try_from(pallas_hash.as_ref()).unwrap()
-}
-
-/// Convert a Pallas Hash<28> reference to an Acropolis PoolId
-pub fn to_pool_id(pallas_hash: &pallas_primitives::Hash<28>) -> PoolId {
- to_hash(pallas_hash).into()
-}
-
-/// Convert a Pallas Hash<32> reference to an Acropolis VRFKey
-pub fn to_vrf_key(pallas_hash: &pallas_primitives::Hash<32>) -> VrfKeyHash {
- VrfKeyHash::try_from(pallas_hash.as_ref()).unwrap()
-}
-
-/// Derive our Address from a Pallas address
-// This is essentially a 1:1 mapping but makes the Message definitions independent
-// of Pallas
-pub fn map_address(address: &addresses::Address) -> Result {
- match address {
- addresses::Address::Byron(byron_address) => Ok(Address::Byron(ByronAddress {
- payload: byron_address.payload.to_vec(),
- })),
-
- addresses::Address::Shelley(shelley_address) => Ok(Address::Shelley(ShelleyAddress {
- network: map_network(shelley_address.network())?,
-
- payment: match shelley_address.payment() {
- addresses::ShelleyPaymentPart::Key(hash) => {
- ShelleyAddressPaymentPart::PaymentKeyHash(to_hash(hash))
- }
- addresses::ShelleyPaymentPart::Script(hash) => {
- ShelleyAddressPaymentPart::ScriptHash(to_hash(hash))
- }
- },
-
- delegation: match shelley_address.delegation() {
- addresses::ShelleyDelegationPart::Null => ShelleyAddressDelegationPart::None,
- addresses::ShelleyDelegationPart::Key(hash) => {
- ShelleyAddressDelegationPart::StakeKeyHash(to_hash(hash))
- }
- addresses::ShelleyDelegationPart::Script(hash) => {
- ShelleyAddressDelegationPart::ScriptHash(to_hash(hash))
- }
- addresses::ShelleyDelegationPart::Pointer(pointer) => {
- ShelleyAddressDelegationPart::Pointer(ShelleyAddressPointer {
- slot: pointer.slot(),
- tx_index: pointer.tx_idx(),
- cert_index: pointer.cert_idx(),
- })
- }
- },
- })),
-
- addresses::Address::Stake(stake_address) => Ok(Address::Stake(StakeAddress {
- network: map_network(stake_address.network())?,
- credential: match stake_address.payload() {
- addresses::StakePayload::Stake(hash) => StakeCredential::AddrKeyHash(to_hash(hash)),
- addresses::StakePayload::Script(hash) => StakeCredential::ScriptHash(to_hash(hash)),
- },
- })),
- }
-}
-
-/// Map a Pallas StakeCredential to ours
-pub fn map_stake_credential(cred: &PallasStakeCredential) -> StakeCredential {
- match cred {
- PallasStakeCredential::AddrKeyhash(key_hash) => {
- StakeCredential::AddrKeyHash(to_hash(key_hash))
- }
- PallasStakeCredential::ScriptHash(script_hash) => {
- StakeCredential::ScriptHash(to_hash(script_hash))
- }
- }
-}
-
-/// Map a PallasStakeCredential to our StakeAddress
-pub fn map_stake_address(cred: &PallasStakeCredential, network_id: NetworkId) -> StakeAddress {
- let payload = match cred {
- PallasStakeCredential::AddrKeyhash(key_hash) => {
- StakeCredential::AddrKeyHash(to_hash(key_hash))
- }
- PallasStakeCredential::ScriptHash(script_hash) => {
- StakeCredential::ScriptHash(to_hash(script_hash))
- }
- };
-
- StakeAddress::new(payload, network_id)
-}
-
-/// Map a Pallas DRep to our DRepChoice
-pub fn map_drep(drep: &conway::DRep) -> DRepChoice {
- match drep {
- conway::DRep::Key(key_hash) => DRepChoice::Key(to_hash(key_hash)),
- conway::DRep::Script(script_hash) => DRepChoice::Script(to_hash(script_hash)),
- conway::DRep::Abstain => DRepChoice::Abstain,
- conway::DRep::NoConfidence => DRepChoice::NoConfidence,
- }
-}
-
-pub fn map_nullable(
- f: impl FnOnce(&Src) -> Dst,
- nullable_src: &Nullable,
-) -> Option {
- match nullable_src {
- Nullable::Some(src) => Some(f(src)),
- _ => None,
- }
-}
-
-pub fn map_nullable_result(
- f: impl FnOnce(&Src) -> Result,
- nullable_src: &Nullable,
-) -> Result