stacks_core/
contract_name.rs

1//! Contract name type and parsing
2use std::{
3	borrow::Borrow,
4	fmt::{Display, Formatter},
5	io::{self, Read},
6	ops::Deref,
7};
8
9use once_cell::sync::Lazy;
10use regex::Regex;
11use thiserror::Error;
12
13use crate::codec::Codec;
14
15/// Minimum contract name length
16pub const CONTRACT_MIN_NAME_LENGTH: usize = 1;
17/// Maximum contract name length
18pub const CONTRACT_MAX_NAME_LENGTH: usize = 40;
19
20/// Regex string for contract names
21pub static CONTRACT_NAME_REGEX_STRING: Lazy<String> = Lazy::new(|| {
22	format!(
23		r#"([a-zA-Z](([a-zA-Z0-9]|[-_])){{{},{}}})"#,
24		CONTRACT_MIN_NAME_LENGTH - 1,
25		CONTRACT_MAX_NAME_LENGTH - 1
26	)
27});
28
29/// Regex for contract names
30pub static CONTRACT_NAME_REGEX: Lazy<Regex> = Lazy::new(|| {
31	regex::Regex::new(
32		format!("^{}$|^__transient$", CONTRACT_NAME_REGEX_STRING.as_str())
33			.as_str(),
34	)
35	.unwrap()
36});
37
38#[derive(Error, Debug)]
39/// Error type for contract name parsing
40pub enum ContractNameError {
41	#[error(
42		"Length should be between {} and {}",
43		CONTRACT_MIN_NAME_LENGTH,
44		CONTRACT_MAX_NAME_LENGTH
45	)]
46	/// Invalid length
47	InvalidLength,
48	#[error("Format should follow the contract name specification")]
49	/// Invalid format
50	InvalidFormat,
51}
52
53#[derive(PartialEq, Eq, Debug, Clone)]
54/// Contract name type
55pub struct ContractName(String);
56
57impl ContractName {
58	/// Create a new contract name from the given string
59	pub fn new(contract_name: &str) -> Result<Self, ContractNameError> {
60		if contract_name.len() < CONTRACT_MIN_NAME_LENGTH
61			&& contract_name.len() > CONTRACT_MAX_NAME_LENGTH
62		{
63			Err(ContractNameError::InvalidLength)
64		} else if CONTRACT_NAME_REGEX.is_match(contract_name) {
65			Ok(Self(contract_name.to_string()))
66		} else {
67			Err(ContractNameError::InvalidFormat)
68		}
69	}
70}
71
72impl Codec for ContractName {
73	fn codec_serialize<W: io::Write>(&self, dest: &mut W) -> io::Result<()> {
74		dest.write_all(&[self.len() as u8])?;
75		dest.write_all(self.as_bytes())
76	}
77
78	fn codec_deserialize<R: io::Read>(data: &mut R) -> io::Result<Self>
79	where
80		Self: Sized,
81	{
82		let mut length_buffer = [0u8; 1];
83		data.read_exact(&mut length_buffer)?;
84		let contract_name_length = length_buffer[0] as usize;
85
86		let mut name_buffer = Vec::with_capacity(contract_name_length);
87		data.take(contract_name_length as u64)
88			.read_to_end(&mut name_buffer)?;
89
90		let contract_name_string = String::from_utf8(name_buffer)
91			.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
92
93		Self::new(&contract_name_string)
94			.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
95	}
96}
97
98impl TryFrom<&str> for ContractName {
99	type Error = ContractNameError;
100
101	fn try_from(value: &str) -> Result<Self, Self::Error> {
102		ContractName::new(value)
103	}
104}
105
106impl AsRef<str> for ContractName {
107	fn as_ref(&self) -> &str {
108		self.0.as_ref()
109	}
110}
111
112impl Deref for ContractName {
113	type Target = str;
114
115	fn deref(&self) -> &Self::Target {
116		&self.0
117	}
118}
119
120impl Borrow<str> for ContractName {
121	fn borrow(&self) -> &str {
122		self.as_ref()
123	}
124}
125
126// From conversion is fallible for this type
127#[allow(clippy::from_over_into)]
128impl Into<String> for ContractName {
129	fn into(self) -> String {
130		self.0
131	}
132}
133
134impl Display for ContractName {
135	fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
136		self.0.fmt(f)
137	}
138}