use crate::{errors, Config};
use frame_support::{ensure, traits::Get, BoundedVec};
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_runtime::{traits::SaturatedConversion, RuntimeDebug};
use sp_std::str;
#[cfg(any(test, feature = "runtime-benchmarks"))]
use sp_std::{convert::TryInto, vec::Vec};
use crate::utils as crate_utils;
pub type ServiceEndpointId<T> = BoundedVec<u8, <T as Config>::MaxServiceIdLength>;
pub(crate) type ServiceEndpointType<T> = BoundedVec<u8, <T as Config>::MaxServiceTypeLength>;
pub(crate) type ServiceEndpointTypeEntries<T> =
BoundedVec<ServiceEndpointType<T>, <T as Config>::MaxNumberOfTypesPerService>;
pub(crate) type ServiceEndpointUrl<T> = BoundedVec<u8, <T as Config>::MaxServiceUrlLength>;
pub(crate) type ServiceEndpointUrlEntries<T> =
BoundedVec<ServiceEndpointUrl<T>, <T as Config>::MaxNumberOfUrlsPerService>;
#[derive(Clone, Decode, RuntimeDebug, Encode, PartialEq, Eq, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
#[codec(mel_bound())]
pub struct DidEndpoint<T: Config> {
pub id: ServiceEndpointId<T>,
pub service_types: ServiceEndpointTypeEntries<T>,
pub urls: ServiceEndpointUrlEntries<T>,
}
impl<T: Config> DidEndpoint<T> {
pub(crate) fn validate_against_constraints(&self) -> Result<(), errors::InputError> {
ensure!(
self.service_types.len() <= T::MaxNumberOfTypesPerService::get().saturated_into(),
errors::InputError::MaxTypeCountExceeded
);
ensure!(
self.urls.len() <= T::MaxNumberOfUrlsPerService::get().saturated_into(),
errors::InputError::MaxUrlCountExceeded
);
ensure!(
self.id.len() <= T::MaxServiceIdLength::get().saturated_into(),
errors::InputError::MaxIdLengthExceeded
);
let str_id = str::from_utf8(&self.id).map_err(|_| errors::InputError::InvalidEncoding)?;
ensure!(
crate_utils::is_valid_uri_fragment(str_id),
errors::InputError::InvalidEncoding
);
self.service_types.iter().try_for_each(|s_type| {
ensure!(
s_type.len() <= T::MaxServiceTypeLength::get().saturated_into(),
errors::InputError::MaxTypeLengthExceeded
);
let str_type = str::from_utf8(s_type).map_err(|_| errors::InputError::InvalidEncoding)?;
ensure!(
crate_utils::is_valid_ascii_string(str_type),
errors::InputError::InvalidEncoding
);
Ok(())
})?;
for s_url in self.urls.iter() {
ensure!(
s_url.len() <= T::MaxServiceUrlLength::get().saturated_into(),
errors::InputError::MaxUrlLengthExceeded
);
let str_url = str::from_utf8(s_url).map_err(|_| errors::InputError::InvalidEncoding)?;
ensure!(crate_utils::is_valid_uri(str_url), errors::InputError::InvalidEncoding);
}
Ok(())
}
}
#[cfg(any(test, feature = "runtime-benchmarks", feature = "mock"))]
impl<T: Config> DidEndpoint<T> {
pub(crate) fn new(id: Vec<u8>, types: Vec<Vec<u8>>, urls: Vec<Vec<u8>>) -> Self {
let bounded_id = id.try_into().expect("Service ID too long.");
let bounded_types = types
.iter()
.map(|el| el.to_vec().try_into().expect("Service type too long."))
.collect::<Vec<ServiceEndpointType<T>>>()
.try_into()
.expect("Too many types for the given service.");
let bounded_urls = urls
.iter()
.map(|el| el.to_vec().try_into().expect("Service URL too long."))
.collect::<Vec<ServiceEndpointUrl<T>>>()
.try_into()
.expect("Too many URLs for the given service.");
Self {
id: bounded_id,
service_types: bounded_types,
urls: bounded_urls,
}
}
}
pub mod utils {
use super::*;
pub(crate) fn validate_new_service_endpoints<T: Config>(
endpoints: &[DidEndpoint<T>],
) -> Result<(), errors::InputError> {
ensure!(
endpoints.len() <= T::MaxNumberOfServicesPerDid::get().saturated_into(),
errors::InputError::MaxServicesCountExceeded
);
endpoints
.iter()
.try_for_each(DidEndpoint::<T>::validate_against_constraints)?;
Ok(())
}
}