use std::fmt::{Display, Formatter, Result as FMTResult};
use crate::error::ContractError;
use crate::os::vfs::vfs_resolve_symlink;
use crate::{ado_contract::ADOContract, os::vfs::vfs_resolve_path};
use cosmwasm_std::{Addr, Api, Deps, QuerierWrapper, Storage};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, JsonSchema,
)]
pub struct AndrAddr(String);
impl AndrAddr {
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
#[inline]
pub fn into_string(self) -> String {
self.0
}
#[inline]
pub fn from_string(addr: impl Into<String>) -> AndrAddr {
AndrAddr(addr.into())
}
pub fn validate(&self, api: &dyn Api) -> Result<(), ContractError> {
match self.is_vfs_path() || self.is_addr(api) {
true => Ok(()),
false => Err(ContractError::InvalidAddress {}),
}
}
pub fn get_raw_address(&self, deps: &Deps) -> Result<Addr, ContractError> {
if !self.is_vfs_path() {
return Ok(deps.api.addr_validate(&self.0)?);
}
let contract = ADOContract::default();
let vfs_contract = contract.get_vfs_address(deps.storage, &deps.querier)?;
self.get_raw_address_from_vfs(deps, vfs_contract)
}
pub fn get_raw_address_from_vfs(
&self,
deps: &Deps,
vfs_contract: impl Into<String>,
) -> Result<Addr, ContractError> {
match self.is_vfs_path() {
false => Ok(deps.api.addr_validate(&self.0)?),
true => {
let vfs_contract: String = vfs_contract.into();
let valid_vfs_path =
self.local_path_to_vfs_path(deps.storage, &deps.querier, vfs_contract.clone())?;
let vfs_addr = Addr::unchecked(vfs_contract);
vfs_resolve_path(valid_vfs_path.clone(), vfs_addr, &deps.querier)
.ok()
.ok_or(ContractError::InvalidPathname {
error: Some(format!(
"{:?} does not exist in the file system",
valid_vfs_path.0
)),
})
}
}
}
fn local_path_to_vfs_path(
&self,
storage: &dyn Storage,
querier: &QuerierWrapper,
vfs_contract: impl Into<String>,
) -> Result<AndrAddr, ContractError> {
match self.is_local_path() {
true => {
let app_contract = ADOContract::default().get_app_contract(storage)?;
match app_contract {
None => Err(ContractError::AppContractNotSpecified {}),
Some(app_contract) => {
let replaced =
AndrAddr(self.0.replace("./", &format!("/home/{app_contract}/")));
vfs_resolve_symlink(replaced, vfs_contract, querier)
}
}
}
false => Ok(self.clone()),
}
}
pub fn is_local_path(&self) -> bool {
self.0.starts_with("./")
}
pub fn is_vfs_path(&self) -> bool {
self.is_local_path()
|| self.0.starts_with('/')
|| self.0.split("://").count() > 1
|| self.0.split('/').count() > 1
|| self.0.starts_with('~')
}
pub fn is_addr(&self, api: &dyn Api) -> bool {
api.addr_validate(&self.0).is_ok()
}
pub fn get_chain(&self) -> Option<&str> {
match self.get_protocol() {
None => None,
Some(..) => {
let start = self.0.find("://").unwrap() + 3;
let end = self.0[start..]
.find('/')
.unwrap_or_else(|| self.0[start..].len());
Some(&self.0[start..start + end])
}
}
}
pub fn get_protocol(&self) -> Option<&str> {
if !self.is_vfs_path() {
None
} else {
let mut split = self.0.split("://");
if split.clone().count() == 1 {
None
} else {
Some(split.next().unwrap())
}
}
}
pub fn get_raw_path(&self) -> &str {
if !self.is_vfs_path() {
self.0.as_str()
} else {
match self.get_protocol() {
None => self.0.as_str(),
Some(..) => {
let start = self.0.find("://").unwrap() + 3;
let end = self.0[start..]
.find('/')
.unwrap_or_else(|| self.0[start..].len());
&self.0[start + end..]
}
}
}
}
pub fn get_root_dir(&self) -> &str {
match self.is_vfs_path() {
false => self.0.as_str(),
true => match self.is_local_path() {
true => self.0.as_str(),
false => {
let raw_path = self.get_raw_path();
if raw_path.starts_with('~') {
return "home";
}
raw_path.split('/').nth(1).unwrap()
}
},
}
}
}
impl Display for AndrAddr {
fn fmt(&self, f: &mut Formatter) -> FMTResult {
write!(f, "{}", &self.0)
}
}
impl AsRef<str> for AndrAddr {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl PartialEq<&str> for AndrAddr {
fn eq(&self, rhs: &&str) -> bool {
self.0 == *rhs
}
}
impl PartialEq<AndrAddr> for &str {
fn eq(&self, rhs: &AndrAddr) -> bool {
*self == rhs.0
}
}
impl PartialEq<String> for AndrAddr {
fn eq(&self, rhs: &String) -> bool {
&self.0 == rhs
}
}
impl PartialEq<AndrAddr> for String {
fn eq(&self, rhs: &AndrAddr) -> bool {
self == &rhs.0
}
}
impl From<AndrAddr> for String {
fn from(addr: AndrAddr) -> Self {
addr.0
}
}
impl From<&AndrAddr> for String {
fn from(addr: &AndrAddr) -> Self {
addr.0.clone()
}
}
#[cfg(test)]
mod tests {
use cosmwasm_std::testing::mock_dependencies;
use super::*;
#[test]
fn test_validate() {
let deps = mock_dependencies();
let addr = AndrAddr("cosmos1...".to_string());
assert!(addr.validate(&deps.api).is_ok());
let addr = AndrAddr("ibc://cosmoshub-4/home/user/app/component".to_string());
assert!(addr.validate(&deps.api).is_ok());
let addr = AndrAddr("/home/user/app/component".to_string());
assert!(addr.validate(&deps.api).is_ok());
let addr = AndrAddr("./user/app/component".to_string());
assert!(addr.validate(&deps.api).is_ok());
let addr = AndrAddr("1".to_string());
assert!(addr.validate(&deps.api).is_err());
}
#[test]
fn test_is_vfs() {
let addr = AndrAddr("/home/user/app/component".to_string());
assert!(addr.is_vfs_path());
let addr = AndrAddr("./user/app/component".to_string());
assert!(addr.is_vfs_path());
let addr = AndrAddr("ibc://chain/home/user/app/component".to_string());
assert!(addr.is_vfs_path());
let addr = AndrAddr("cosmos1...".to_string());
assert!(!addr.is_vfs_path());
}
#[test]
fn test_is_addr() {
let deps = mock_dependencies();
let addr = AndrAddr("cosmos1...".to_string());
assert!(addr.is_addr(&deps.api));
assert!(!addr.is_vfs_path());
}
#[test]
fn test_is_local_path() {
let addr = AndrAddr("./component".to_string());
assert!(addr.is_local_path());
assert!(addr.is_vfs_path());
}
#[test]
fn test_get_protocol() {
let addr = AndrAddr("cosmos1...".to_string());
assert!(addr.get_protocol().is_none());
let addr = AndrAddr("ibc://chain/home/user/app/component".to_string());
assert_eq!(addr.get_protocol().unwrap(), "ibc");
}
#[test]
fn test_get_chain() {
let addr = AndrAddr("cosmos1...".to_string());
assert!(addr.get_chain().is_none());
let addr = AndrAddr("ibc://chain/home/user/app/component".to_string());
assert_eq!(addr.get_chain().unwrap(), "chain");
let addr = AndrAddr("/home/user/app/component".to_string());
assert!(addr.get_chain().is_none());
}
#[test]
fn test_get_raw_path() {
let addr = AndrAddr("cosmos1...".to_string());
assert_eq!(addr.get_raw_path(), "cosmos1...");
let addr = AndrAddr("ibc://chain/user/app/component".to_string());
assert_eq!(addr.get_raw_path(), "/user/app/component");
let addr = AndrAddr("/chain/user/app/component".to_string());
assert_eq!(addr.get_raw_path(), "/chain/user/app/component");
}
#[test]
fn test_get_root_dir() {
let addr = AndrAddr("/home/user1".to_string());
assert_eq!(addr.get_root_dir(), "home");
let addr = AndrAddr("~user1".to_string());
assert_eq!(addr.get_root_dir(), "home");
let addr = AndrAddr("~/user1".to_string());
assert_eq!(addr.get_root_dir(), "home");
let addr = AndrAddr("ibc://chain/home/user1".to_string());
assert_eq!(addr.get_root_dir(), "home");
let addr = AndrAddr("cosmos1...".to_string());
assert_eq!(addr.get_root_dir(), "cosmos1...");
let addr = AndrAddr("./home/user1".to_string());
assert_eq!(addr.get_root_dir(), "./home/user1");
}
}