1use std::{borrow::Cow, fmt, ops::Deref, str::FromStr};
2
3use crate::{AccountIdRef, ParseAccountError};
4
5#[derive(Eq, Ord, Hash, Clone, Debug, PartialEq, PartialOrd)]
23#[cfg_attr(feature = "abi", derive(borsh::BorshSchema))]
24pub struct AccountId(pub(crate) Box<str>);
25
26impl AccountId {
27 pub const MIN_LEN: usize = crate::validation::MIN_LEN;
29 pub const MAX_LEN: usize = crate::validation::MAX_LEN;
31
32 #[doc(hidden)]
52 #[cfg(feature = "internal_unstable")]
53 #[deprecated = "AccountId construction without validation is illegal since nearcore#4440"]
54 pub fn new_unvalidated(account_id: String) -> Self {
55 Self(account_id.into_boxed_str())
56 }
57
58 pub fn validate(account_id: &str) -> Result<(), ParseAccountError> {
115 crate::validation::validate(account_id)
116 }
117}
118
119impl AsRef<str> for AccountId {
120 fn as_ref(&self) -> &str {
121 &self.0
122 }
123}
124
125impl AsRef<AccountIdRef> for AccountId {
126 fn as_ref(&self) -> &AccountIdRef {
127 self
128 }
129}
130
131impl Deref for AccountId {
132 type Target = AccountIdRef;
133
134 fn deref(&self) -> &Self::Target {
135 AccountIdRef::new_unvalidated(&self.0)
136 }
137}
138
139impl std::borrow::Borrow<AccountIdRef> for AccountId {
140 fn borrow(&self) -> &AccountIdRef {
141 AccountIdRef::new_unvalidated(self)
142 }
143}
144
145impl FromStr for AccountId {
146 type Err = ParseAccountError;
147
148 fn from_str(account_id: &str) -> Result<Self, Self::Err> {
149 crate::validation::validate(account_id)?;
150 Ok(Self(account_id.into()))
151 }
152}
153
154impl TryFrom<Box<str>> for AccountId {
155 type Error = ParseAccountError;
156
157 fn try_from(account_id: Box<str>) -> Result<Self, Self::Error> {
158 crate::validation::validate(&account_id)?;
159 Ok(Self(account_id))
160 }
161}
162
163impl TryFrom<String> for AccountId {
164 type Error = ParseAccountError;
165
166 fn try_from(account_id: String) -> Result<Self, Self::Error> {
167 crate::validation::validate(&account_id)?;
168 Ok(Self(account_id.into_boxed_str()))
169 }
170}
171
172impl fmt::Display for AccountId {
173 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
174 fmt::Display::fmt(&self.0, f)
175 }
176}
177
178impl From<AccountId> for String {
179 fn from(account_id: AccountId) -> Self {
180 account_id.0.into_string()
181 }
182}
183
184impl From<AccountId> for Box<str> {
185 fn from(value: AccountId) -> Box<str> {
186 value.0
187 }
188}
189
190impl PartialEq<AccountId> for AccountIdRef {
191 fn eq(&self, other: &AccountId) -> bool {
192 &self.0 == other.as_str()
193 }
194}
195
196impl PartialEq<AccountIdRef> for AccountId {
197 fn eq(&self, other: &AccountIdRef) -> bool {
198 self.as_str() == &other.0
199 }
200}
201
202impl<'a> PartialEq<AccountId> for &'a AccountIdRef {
203 fn eq(&self, other: &AccountId) -> bool {
204 &self.0 == other.as_str()
205 }
206}
207
208impl<'a> PartialEq<&'a AccountIdRef> for AccountId {
209 fn eq(&self, other: &&'a AccountIdRef) -> bool {
210 self.as_str() == &other.0
211 }
212}
213
214impl PartialEq<AccountId> for String {
215 fn eq(&self, other: &AccountId) -> bool {
216 self == other.as_str()
217 }
218}
219
220impl PartialEq<String> for AccountId {
221 fn eq(&self, other: &String) -> bool {
222 self.as_str() == other
223 }
224}
225
226impl PartialEq<AccountId> for str {
227 fn eq(&self, other: &AccountId) -> bool {
228 self == other.as_str()
229 }
230}
231
232impl PartialEq<str> for AccountId {
233 fn eq(&self, other: &str) -> bool {
234 self.as_str() == other
235 }
236}
237
238impl<'a> PartialEq<AccountId> for &'a str {
239 fn eq(&self, other: &AccountId) -> bool {
240 *self == other.as_str()
241 }
242}
243
244impl<'a> PartialEq<&'a str> for AccountId {
245 fn eq(&self, other: &&'a str) -> bool {
246 self.as_str() == *other
247 }
248}
249
250impl PartialOrd<AccountId> for AccountIdRef {
251 fn partial_cmp(&self, other: &AccountId) -> Option<std::cmp::Ordering> {
252 self.0.partial_cmp(other.as_str())
253 }
254}
255
256impl PartialOrd<AccountIdRef> for AccountId {
257 fn partial_cmp(&self, other: &AccountIdRef) -> Option<std::cmp::Ordering> {
258 self.as_str().partial_cmp(&other.0)
259 }
260}
261
262impl<'a> PartialOrd<AccountId> for &'a AccountIdRef {
263 fn partial_cmp(&self, other: &AccountId) -> Option<std::cmp::Ordering> {
264 self.0.partial_cmp(other.as_str())
265 }
266}
267
268impl<'a> PartialOrd<&'a AccountIdRef> for AccountId {
269 fn partial_cmp(&self, other: &&'a AccountIdRef) -> Option<std::cmp::Ordering> {
270 self.as_str().partial_cmp(&other.0)
271 }
272}
273
274impl PartialOrd<AccountId> for String {
275 fn partial_cmp(&self, other: &AccountId) -> Option<std::cmp::Ordering> {
276 self.as_str().partial_cmp(other.as_str())
277 }
278}
279
280impl PartialOrd<String> for AccountId {
281 fn partial_cmp(&self, other: &String) -> Option<std::cmp::Ordering> {
282 self.as_str().partial_cmp(other.as_str())
283 }
284}
285
286impl PartialOrd<AccountId> for str {
287 fn partial_cmp(&self, other: &AccountId) -> Option<std::cmp::Ordering> {
288 self.partial_cmp(other.as_str())
289 }
290}
291
292impl PartialOrd<str> for AccountId {
293 fn partial_cmp(&self, other: &str) -> Option<std::cmp::Ordering> {
294 self.as_str().partial_cmp(other)
295 }
296}
297
298impl<'a> PartialOrd<AccountId> for &'a str {
299 fn partial_cmp(&self, other: &AccountId) -> Option<std::cmp::Ordering> {
300 self.partial_cmp(&other.as_str())
301 }
302}
303
304impl<'a> PartialOrd<&'a str> for AccountId {
305 fn partial_cmp(&self, other: &&'a str) -> Option<std::cmp::Ordering> {
306 self.as_str().partial_cmp(*other)
307 }
308}
309
310impl<'a> From<AccountId> for Cow<'a, AccountIdRef> {
311 fn from(value: AccountId) -> Self {
312 Cow::Owned(value)
313 }
314}
315
316impl<'a> From<&'a AccountId> for Cow<'a, AccountIdRef> {
317 fn from(value: &'a AccountId) -> Self {
318 Cow::Borrowed(value)
319 }
320}
321
322impl<'a> From<Cow<'a, AccountIdRef>> for AccountId {
323 fn from(value: Cow<'a, AccountIdRef>) -> Self {
324 value.into_owned()
325 }
326}
327
328#[cfg(feature = "schemars-v0_8")]
329impl schemars_v0_8::JsonSchema for AccountId {
330 fn is_referenceable() -> bool {
331 false
332 }
333
334 fn schema_name() -> String {
335 "AccountId".to_string()
336 }
337
338 fn json_schema(_: &mut schemars_v0_8::gen::SchemaGenerator) -> schemars_v0_8::schema::Schema {
339 use schemars_v0_8::schema::{InstanceType, Metadata, Schema, SchemaObject, SingleOrVec};
340 Schema::Object(SchemaObject {
341 instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
342 metadata: Some(Box::new(Metadata {
343 description: Some("NEAR Account Identifier.\n\nThis is a unique, syntactically valid, human-readable account identifier on the NEAR network.\n\n[See the crate-level docs for information about validation.](index.html#account-id-rules)\n\nAlso see [Error kind precedence](AccountId#error-kind-precedence).\n\n## Examples\n\n``` use near_account_id::AccountId;\n\nlet alice: AccountId = \"alice.near\".parse().unwrap();\n\nassert!(\"ƒelicia.near\".parse::<AccountId>().is_err()); // (ƒ is not f) ```".to_string()),
344 ..Default::default()
345 })),
346 ..Default::default()
347 })
348 }
349}
350
351#[cfg(feature = "schemars-v1")]
352impl schemars_v1::JsonSchema for AccountId {
353 fn schema_name() -> std::borrow::Cow<'static, str> {
354 "AccountId".to_string().into()
355 }
356
357 fn json_schema(_: &mut schemars_v1::SchemaGenerator) -> schemars_v1::Schema {
358 schemars_v1::json_schema!({
359 "$schema": "https://json-schema.org/draft/2020-12/schema",
360 "description": "NEAR Account Identifier.\n\nThis is a unique, syntactically valid, human-readable account identifier on the NEAR network.\n\n[See the crate-level docs for information about validation.](index.html#account-id-rules)\n\nAlso see [Error kind precedence](AccountId#error-kind-precedence).\n\n## Examples\n\n```\nuse near_account_id::AccountId;\n\nlet alice: AccountId = \"alice.near\".parse().unwrap();\n\nassert!(\"ƒelicia.near\".parse::<AccountId>().is_err()); // (ƒ is not f)\n```",
361 "title": "AccountId",
362 "type": "string"
363 })
364 }
365}
366
367#[cfg(feature = "arbitrary")]
368impl<'a> arbitrary::Arbitrary<'a> for AccountId {
369 fn size_hint(depth: usize) -> (usize, Option<usize>) {
370 <&AccountIdRef as arbitrary::Arbitrary>::size_hint(depth)
371 }
372
373 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
374 Ok(u.arbitrary::<&AccountIdRef>()?.into())
375 }
376
377 fn arbitrary_take_rest(u: arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
378 Ok(<&AccountIdRef as arbitrary::Arbitrary>::arbitrary_take_rest(u)?.into())
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 #[allow(unused_imports)]
385 use super::*;
386
387 #[test]
388 #[cfg(feature = "arbitrary")]
389 fn test_arbitrary() {
390 let corpus = [
391 ("a|bcd", None),
392 ("ab|cde", Some("ab")),
393 ("a_-b", None),
394 ("ab_-c", Some("ab")),
395 ("a", None),
396 ("miraclx.near", Some("miraclx.near")),
397 (
398 "01234567890123456789012345678901234567890123456789012345678901234",
399 None,
400 ),
401 ];
402
403 for (input, expected_output) in corpus {
404 assert!(input.len() <= u8::MAX as usize);
405 let data = [input.as_bytes(), &[input.len() as _]].concat();
406 let mut u = arbitrary::Unstructured::new(&data);
407
408 assert_eq!(
409 u.arbitrary::<AccountId>().map(Into::<String>::into).ok(),
410 expected_output.map(Into::<String>::into)
411 );
412 }
413 }
414 #[test]
415 #[cfg(feature = "schemars-v1")]
416 fn test_schemars_v1() {
417 let schema = schemars_v1::schema_for!(AccountId);
418 let json_schema = serde_json::to_value(&schema).unwrap();
419 dbg!(&json_schema);
420 assert_eq!(
421 json_schema,
422 serde_json::json!({
423 "$schema": "https://json-schema.org/draft/2020-12/schema",
424 "description": "NEAR Account Identifier.\n\nThis is a unique, syntactically valid, human-readable account identifier on the NEAR network.\n\n[See the crate-level docs for information about validation.](index.html#account-id-rules)\n\nAlso see [Error kind precedence](AccountId#error-kind-precedence).\n\n## Examples\n\n```\nuse near_account_id::AccountId;\n\nlet alice: AccountId = \"alice.near\".parse().unwrap();\n\nassert!(\"ƒelicia.near\".parse::<AccountId>().is_err()); // (ƒ is not f)\n```",
425 "title": "AccountId",
426 "type": "string"
427 }
428 )
429 );
430 }
431
432 #[test]
433 #[cfg(feature = "schemars-v0_8")]
434 fn test_schemars_v0_8() {
435 let schema = schemars_v0_8::schema_for!(AccountId);
436 let json_schema = serde_json::to_value(&schema).unwrap();
437 dbg!(&json_schema);
438 assert_eq!(
439 json_schema,
440 serde_json::json!({
441 "$schema": "http://json-schema.org/draft-07/schema#",
442 "description": "NEAR Account Identifier.\n\nThis is a unique, syntactically valid, human-readable account identifier on the NEAR network.\n\n[See the crate-level docs for information about validation.](index.html#account-id-rules)\n\nAlso see [Error kind precedence](AccountId#error-kind-precedence).\n\n## Examples\n\n``` use near_account_id::AccountId;\n\nlet alice: AccountId = \"alice.near\".parse().unwrap();\n\nassert!(\"ƒelicia.near\".parse::<AccountId>().is_err()); // (ƒ is not f) ```",
443 "title": "AccountId",
444 "type": "string"
445 }
446 )
447 );
448 }
449}