stacks_core/
contract_name.rs1use 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
15pub const CONTRACT_MIN_NAME_LENGTH: usize = 1;
17pub const CONTRACT_MAX_NAME_LENGTH: usize = 40;
19
20pub 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
29pub 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)]
39pub enum ContractNameError {
41 #[error(
42 "Length should be between {} and {}",
43 CONTRACT_MIN_NAME_LENGTH,
44 CONTRACT_MAX_NAME_LENGTH
45 )]
46 InvalidLength,
48 #[error("Format should follow the contract name specification")]
49 InvalidFormat,
51}
52
53#[derive(PartialEq, Eq, Debug, Clone)]
54pub struct ContractName(String);
56
57impl ContractName {
58 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#[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}