1 | use core::{ |
2 | cmp::Ordering, |
3 | fmt::{self, Debug, Display, Formatter}, |
4 | hash::{Hash, Hasher}, |
5 | str, |
6 | }; |
7 | use serde::{ |
8 | de::{Deserialize, Deserializer}, |
9 | ser::{Serialize, Serializer}, |
10 | }; |
11 | use static_assertions::assert_impl_all; |
12 | use std::{ |
13 | borrow::Cow, |
14 | ops::{Bound, RangeBounds}, |
15 | sync::Arc, |
16 | }; |
17 | |
18 | use crate::{serialized::Format, signature_parser::SignatureParser, Basic, Error, Result, Type}; |
19 | |
20 | // A data type similar to Cow and [`bytes::Bytes`] but unlike the former won't allow us to only keep |
21 | // the owned bytes in Arc and latter doesn't have a notion of borrowed data and would require API |
22 | // breakage. |
23 | // |
24 | // [`bytes::Bytes`]: https://docs.rs/bytes/0.5.6/bytes/struct.Bytes.html |
25 | #[derive (Debug, Clone)] |
26 | enum Bytes<'b> { |
27 | Borrowed(&'b [u8]), |
28 | Static(&'static [u8]), |
29 | Owned(Arc<[u8]>), |
30 | } |
31 | |
32 | impl<'b> Bytes<'b> { |
33 | const fn borrowed<'s: 'b>(bytes: &'s [u8]) -> Self { |
34 | Self::Borrowed(bytes) |
35 | } |
36 | |
37 | fn owned(bytes: Vec<u8>) -> Self { |
38 | Self::Owned(bytes.into()) |
39 | } |
40 | |
41 | /// This is faster than `Clone::clone` when `self` contains owned data. |
42 | fn as_ref(&self) -> Bytes<'_> { |
43 | match &self { |
44 | Bytes::Static(s: &&'static [u8]) => Bytes::Static(s), |
45 | Bytes::Borrowed(s: &&[u8]) => Bytes::Borrowed(s), |
46 | Bytes::Owned(s: &Arc<[u8]>) => Bytes::Borrowed(s), |
47 | } |
48 | } |
49 | } |
50 | |
51 | impl std::ops::Deref for Bytes<'_> { |
52 | type Target = [u8]; |
53 | |
54 | fn deref(&self) -> &[u8] { |
55 | match self { |
56 | Bytes::Borrowed(borrowed: &&[u8]) => borrowed, |
57 | Bytes::Static(borrowed: &&'static [u8]) => borrowed, |
58 | Bytes::Owned(owned: &Arc<[u8]>) => owned, |
59 | } |
60 | } |
61 | } |
62 | |
63 | impl Eq for Bytes<'_> {} |
64 | |
65 | impl PartialEq for Bytes<'_> { |
66 | fn eq(&self, other: &Self) -> bool { |
67 | **self == **other |
68 | } |
69 | } |
70 | |
71 | impl Ord for Bytes<'_> { |
72 | fn cmp(&self, other: &Self) -> Ordering { |
73 | (**self).cmp(&**other) |
74 | } |
75 | } |
76 | |
77 | impl PartialOrd for Bytes<'_> { |
78 | fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
79 | Some(self.cmp(other)) |
80 | } |
81 | } |
82 | |
83 | impl Hash for Bytes<'_> { |
84 | fn hash<H: Hasher>(&self, state: &mut H) { |
85 | (**self).hash(state) |
86 | } |
87 | } |
88 | |
89 | /// String that [identifies] the type of an encoded value. |
90 | /// |
91 | /// # Examples |
92 | /// |
93 | /// ``` |
94 | /// use zvariant::Signature; |
95 | /// |
96 | /// // Valid signatures |
97 | /// let s = Signature::try_from("" ).unwrap(); |
98 | /// assert_eq!(s, "" ); |
99 | /// # assert_eq!(s.n_complete_types(), Ok(0)); |
100 | /// let s = Signature::try_from("y" ).unwrap(); |
101 | /// assert_eq!(s, "y" ); |
102 | /// # assert_eq!(s.n_complete_types(), Ok(1)); |
103 | /// let s = Signature::try_from("xs" ).unwrap(); |
104 | /// assert_eq!(s, "xs" ); |
105 | /// # assert_eq!(s.n_complete_types(), Ok(2)); |
106 | /// let s = Signature::try_from("(ysa{sd})" ).unwrap(); |
107 | /// assert_eq!(s, "(ysa{sd})" ); |
108 | /// # assert_eq!(s.n_complete_types(), Ok(1)); |
109 | /// let s = Signature::try_from("a{sd}" ).unwrap(); |
110 | /// assert_eq!(s, "a{sd}" ); |
111 | /// # assert_eq!(s.n_complete_types(), Ok(1)); |
112 | /// |
113 | /// // Invalid signatures |
114 | /// Signature::try_from("z" ).unwrap_err(); |
115 | /// Signature::try_from("(xs" ).unwrap_err(); |
116 | /// Signature::try_from("xs)" ).unwrap_err(); |
117 | /// Signature::try_from("s/" ).unwrap_err(); |
118 | /// Signature::try_from("a" ).unwrap_err(); |
119 | /// Signature::try_from("a{yz}" ).unwrap_err(); |
120 | /// ``` |
121 | /// |
122 | /// This is implemented so that multiple instances can share the same underlying signature string. |
123 | /// Use [`slice`] method to create new signature that represents a portion of a signature |
124 | /// |
125 | /// [identifies]: https://dbus.freedesktop.org/doc/dbus-specification.html#type-system |
126 | /// [`slice`]: #method.slice |
127 | #[derive (Hash, Clone, PartialOrd, Ord)] |
128 | pub struct Signature<'a> { |
129 | bytes: Bytes<'a>, |
130 | pos: usize, |
131 | end: usize, |
132 | } |
133 | |
134 | assert_impl_all!(Signature<'_>: Send, Sync, Unpin); |
135 | |
136 | impl<'a> Signature<'a> { |
137 | /// The signature as a string. |
138 | pub fn as_str(&self) -> &str { |
139 | // SAFETY: non-UTF8 characters in Signature are rejected by safe constructors |
140 | unsafe { str::from_utf8_unchecked(self.as_bytes()) } |
141 | } |
142 | |
143 | /// The signature bytes. |
144 | pub fn as_bytes(&self) -> &[u8] { |
145 | &self.bytes[self.pos..self.end] |
146 | } |
147 | |
148 | /// This is faster than `Clone::clone` when `self` contains owned data. |
149 | pub fn as_ref(&self) -> Signature<'_> { |
150 | Signature { |
151 | bytes: self.bytes.as_ref(), |
152 | pos: self.pos, |
153 | end: self.end, |
154 | } |
155 | } |
156 | |
157 | /// Create a new Signature from given bytes. |
158 | /// |
159 | /// Since the passed bytes are not checked for correctness, it's provided for ease of |
160 | /// `Type` implementations. |
161 | /// |
162 | /// # Safety |
163 | /// |
164 | /// This method is unsafe as it allows creating a `str` that is not valid UTF-8. |
165 | pub unsafe fn from_bytes_unchecked<'s: 'a>(bytes: &'s [u8]) -> Self { |
166 | Self { |
167 | bytes: Bytes::borrowed(bytes), |
168 | pos: 0, |
169 | end: bytes.len(), |
170 | } |
171 | } |
172 | |
173 | /// Same as `from_bytes_unchecked`, except it takes a static reference. |
174 | /// |
175 | /// # Safety |
176 | /// |
177 | /// This method is unsafe as it allows creating a `str` that is not valid UTF-8. |
178 | pub unsafe fn from_static_bytes_unchecked(bytes: &'static [u8]) -> Self { |
179 | Self { |
180 | bytes: Bytes::Static(bytes), |
181 | pos: 0, |
182 | end: bytes.len(), |
183 | } |
184 | } |
185 | |
186 | /// Same as `from_bytes_unchecked`, except it takes a string reference. |
187 | pub const fn from_str_unchecked<'s: 'a>(signature: &'s str) -> Self { |
188 | Self { |
189 | bytes: Bytes::borrowed(signature.as_bytes()), |
190 | pos: 0, |
191 | end: signature.len(), |
192 | } |
193 | } |
194 | |
195 | /// Same as `from_str_unchecked`, except it takes a static string reference. |
196 | pub const fn from_static_str_unchecked(signature: &'static str) -> Self { |
197 | Self { |
198 | bytes: Bytes::Static(signature.as_bytes()), |
199 | pos: 0, |
200 | end: signature.len(), |
201 | } |
202 | } |
203 | |
204 | /// Same as `from_str_unchecked`, except it takes an owned `String`. |
205 | pub fn from_string_unchecked(signature: String) -> Self { |
206 | let bytes = signature.into_bytes(); |
207 | let end = bytes.len(); |
208 | |
209 | Self { |
210 | bytes: Bytes::owned(bytes), |
211 | pos: 0, |
212 | end, |
213 | } |
214 | } |
215 | |
216 | /// Same as `from_static_str_unchecked`, except it checks validity of the signature. |
217 | /// |
218 | /// It's recommended to use this method instead of `TryFrom<&str>` implementation for |
219 | /// `&'static str`. The former will ensure that [`Signature::to_owned`] and |
220 | /// [`Signature::into_owned`] do not clone the underlying bytes. |
221 | pub fn from_static_str(signature: &'static str) -> Result<Self> { |
222 | let bytes = signature.as_bytes(); |
223 | SignatureParser::validate(bytes)?; |
224 | |
225 | Ok(Self { |
226 | bytes: Bytes::Static(bytes), |
227 | pos: 0, |
228 | end: signature.len(), |
229 | }) |
230 | } |
231 | |
232 | /// Same as `from_static_bytes_unchecked`, except it checks validity of the signature. |
233 | /// |
234 | /// It's recommended to use this method instead of the `TryFrom<&[u8]>` implementation for |
235 | /// `&'static [u8]`. The former will ensure that [`Signature::to_owned`] and |
236 | /// [`Signature::into_owned`] do not clone the underlying bytes. |
237 | pub fn from_static_bytes(bytes: &'static [u8]) -> Result<Self> { |
238 | SignatureParser::validate(bytes)?; |
239 | |
240 | Ok(Self { |
241 | bytes: Bytes::Static(bytes), |
242 | pos: 0, |
243 | end: bytes.len(), |
244 | }) |
245 | } |
246 | |
247 | /// the signature's length. |
248 | pub fn len(&self) -> usize { |
249 | self.end - self.pos |
250 | } |
251 | |
252 | /// if the signature is empty. |
253 | pub fn is_empty(&self) -> bool { |
254 | self.as_bytes().is_empty() |
255 | } |
256 | |
257 | /// Creates an owned clone of `self`. |
258 | pub fn to_owned(&self) -> Signature<'static> { |
259 | match &self.bytes { |
260 | Bytes::Borrowed(_) => { |
261 | let bytes = Bytes::owned(self.as_bytes().to_vec()); |
262 | let pos = 0; |
263 | let end = bytes.len(); |
264 | |
265 | Signature { bytes, pos, end } |
266 | } |
267 | Bytes::Static(b) => Signature { |
268 | bytes: Bytes::Static(b), |
269 | pos: self.pos, |
270 | end: self.end, |
271 | }, |
272 | Bytes::Owned(owned) => Signature { |
273 | bytes: Bytes::Owned(owned.clone()), |
274 | pos: self.pos, |
275 | end: self.end, |
276 | }, |
277 | } |
278 | } |
279 | |
280 | /// Creates an owned clone of `self`. |
281 | pub fn into_owned(self) -> Signature<'static> { |
282 | self.to_owned() |
283 | } |
284 | |
285 | /// Returns a slice of `self` for the provided range. |
286 | /// |
287 | /// # Panics |
288 | /// |
289 | /// Requires that begin <= end and end <= self.len(), otherwise slicing will panic. |
290 | #[must_use ] |
291 | pub fn slice(&self, range: impl RangeBounds<usize>) -> Self { |
292 | let len = self.len(); |
293 | |
294 | let pos = match range.start_bound() { |
295 | Bound::Included(&n) => n, |
296 | Bound::Excluded(&n) => n + 1, |
297 | Bound::Unbounded => 0, |
298 | }; |
299 | |
300 | let end = match range.end_bound() { |
301 | Bound::Included(&n) => n + 1, |
302 | Bound::Excluded(&n) => n, |
303 | Bound::Unbounded => len, |
304 | }; |
305 | |
306 | assert!( |
307 | pos <= end, |
308 | "range start must not be greater than end: {:?} > {:?}" , |
309 | pos, |
310 | end, |
311 | ); |
312 | assert!(end <= len, "range end out of bounds: {:?} > {:?}" , end, len,); |
313 | |
314 | if end == pos { |
315 | return Self::from_str_unchecked("" ); |
316 | } |
317 | |
318 | let mut clone = self.clone(); |
319 | clone.pos += pos; |
320 | clone.end = self.pos + end; |
321 | |
322 | clone |
323 | } |
324 | |
325 | /// The number of complete types for the signature. |
326 | /// |
327 | /// # Errors |
328 | /// |
329 | /// If the signature is invalid, returns the first error. |
330 | pub fn n_complete_types(&self) -> Result<usize> { |
331 | let mut count = 0; |
332 | // SAFETY: the parser is only used to do counting |
333 | for s in unsafe { SignatureParser::from_bytes_unchecked(self.as_bytes())? } { |
334 | s?; |
335 | count += 1; |
336 | } |
337 | Ok(count) |
338 | } |
339 | } |
340 | |
341 | impl<'a> Debug for Signature<'a> { |
342 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
343 | f.debug_tuple(name:"Signature" ).field(&self.as_str()).finish() |
344 | } |
345 | } |
346 | |
347 | impl<'a> Basic for Signature<'a> { |
348 | const SIGNATURE_CHAR: char = 'g' ; |
349 | const SIGNATURE_STR: &'static str = "g" ; |
350 | |
351 | fn alignment(format: Format) -> usize { |
352 | match format { |
353 | Format::DBus => 1, |
354 | #[cfg (feature = "gvariant" )] |
355 | Format::GVariant => 1, |
356 | } |
357 | } |
358 | } |
359 | |
360 | impl<'a> Type for Signature<'a> { |
361 | fn signature() -> Signature<'static> { |
362 | Signature::from_static_str_unchecked(Self::SIGNATURE_STR) |
363 | } |
364 | } |
365 | |
366 | impl<'a> From<&Signature<'a>> for Signature<'a> { |
367 | fn from(signature: &Signature<'a>) -> Signature<'a> { |
368 | signature.clone() |
369 | } |
370 | } |
371 | |
372 | impl<'a> TryFrom<&'a [u8]> for Signature<'a> { |
373 | type Error = Error; |
374 | |
375 | fn try_from(value: &'a [u8]) -> Result<Self> { |
376 | SignatureParser::validate(signature:value)?; |
377 | |
378 | // SAFETY: validate checks UTF8 |
379 | unsafe { Ok(Self::from_bytes_unchecked(bytes:value)) } |
380 | } |
381 | } |
382 | |
383 | /// Try to create a Signature from a string. |
384 | impl<'a> TryFrom<&'a str> for Signature<'a> { |
385 | type Error = Error; |
386 | |
387 | fn try_from(value: &'a str) -> Result<Self> { |
388 | Self::try_from(value.as_bytes()) |
389 | } |
390 | } |
391 | |
392 | /// Try to create a Signature from a `Cow<str>.` |
393 | impl<'a> TryFrom<Cow<'a, str>> for Signature<'a> { |
394 | type Error = Error; |
395 | |
396 | fn try_from(value: Cow<'a, str>) -> Result<Self> { |
397 | match value { |
398 | Cow::Borrowed(v: &str) => Self::try_from(v), |
399 | Cow::Owned(v: String) => Self::try_from(v), |
400 | } |
401 | } |
402 | } |
403 | |
404 | impl<'a> TryFrom<String> for Signature<'a> { |
405 | type Error = Error; |
406 | |
407 | fn try_from(value: String) -> Result<Self> { |
408 | SignatureParser::validate(signature:value.as_bytes())?; |
409 | |
410 | Ok(Self::from_string_unchecked(signature:value)) |
411 | } |
412 | } |
413 | |
414 | impl<'a> From<Signature<'a>> for String { |
415 | fn from(value: Signature<'a>) -> String { |
416 | String::from(value.as_str()) |
417 | } |
418 | } |
419 | |
420 | impl<'a> From<&Signature<'a>> for String { |
421 | fn from(value: &Signature<'a>) -> String { |
422 | String::from(value.as_str()) |
423 | } |
424 | } |
425 | |
426 | impl<'a> std::ops::Deref for Signature<'a> { |
427 | type Target = str; |
428 | |
429 | fn deref(&self) -> &Self::Target { |
430 | self.as_str() |
431 | } |
432 | } |
433 | |
434 | /// Checks whether the string slice has balanced parentheses. |
435 | fn has_balanced_parentheses(signature_str: &str) -> bool { |
436 | signature_str.chars().fold(init:0, |count: i32, ch: char| match ch { |
437 | '(' => count + 1, |
438 | ')' if count != 0 => count - 1, |
439 | _ => count, |
440 | }) == 0 |
441 | } |
442 | |
443 | /// Determines whether the signature has outer parentheses and if so, return the |
444 | /// string slice without those parentheses. |
445 | fn without_outer_parentheses<'a, 'b>(sig: &'a Signature<'b>) -> &'a str |
446 | where |
447 | 'b: 'a, |
448 | { |
449 | let sig_str: &str = sig.as_str(); |
450 | |
451 | if let Some(subslice: &str) = sig_str.strip_prefix('(' ).and_then(|s: &str| s.strip_suffix(')' )) { |
452 | if has_balanced_parentheses(signature_str:subslice) { |
453 | return subslice; |
454 | } |
455 | } |
456 | sig_str |
457 | } |
458 | |
459 | /// Evaluate equality of two signatures, ignoring outer parentheses if needed. |
460 | impl<'a, 'b> PartialEq<Signature<'a>> for Signature<'b> { |
461 | fn eq(&self, other: &Signature<'_>) -> bool { |
462 | without_outer_parentheses(self) == without_outer_parentheses(sig:other) |
463 | } |
464 | } |
465 | |
466 | impl<'a> PartialEq<str> for Signature<'a> { |
467 | fn eq(&self, other: &str) -> bool { |
468 | self.as_bytes() == other.as_bytes() |
469 | } |
470 | } |
471 | |
472 | impl<'a> PartialEq<&str> for Signature<'a> { |
473 | fn eq(&self, other: &&str) -> bool { |
474 | self.as_bytes() == other.as_bytes() |
475 | } |
476 | } |
477 | |
478 | // According to the docs, `Eq` derive should only be used on structs if all its fields are |
479 | // are `Eq`. Hence the manual implementation. |
480 | impl Eq for Signature<'_> {} |
481 | |
482 | impl<'a> Display for Signature<'a> { |
483 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
484 | std::fmt::Display::fmt(&self.as_str(), f) |
485 | } |
486 | } |
487 | |
488 | impl<'a> Serialize for Signature<'a> { |
489 | fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> |
490 | where |
491 | S: Serializer, |
492 | { |
493 | serializer.serialize_str(self.as_str()) |
494 | } |
495 | } |
496 | |
497 | impl<'de: 'a, 'a> Deserialize<'de> for Signature<'a> { |
498 | fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error> |
499 | where |
500 | D: Deserializer<'de>, |
501 | { |
502 | let val: Cow<'a, str> = <std::borrow::Cow<'a, str>>::deserialize(deserializer)?; |
503 | |
504 | Self::try_from(val).map_err(op:serde::de::Error::custom) |
505 | } |
506 | } |
507 | |
508 | /// Owned [`Signature`](struct.Signature.html) |
509 | #[derive (Debug, Clone, PartialEq, Eq, serde::Serialize, Type)] |
510 | pub struct OwnedSignature(Signature<'static>); |
511 | |
512 | assert_impl_all!(OwnedSignature: Send, Sync, Unpin); |
513 | |
514 | impl OwnedSignature { |
515 | pub fn into_inner(self) -> Signature<'static> { |
516 | self.0 |
517 | } |
518 | } |
519 | |
520 | impl Basic for OwnedSignature { |
521 | const SIGNATURE_CHAR: char = Signature::SIGNATURE_CHAR; |
522 | const SIGNATURE_STR: &'static str = Signature::SIGNATURE_STR; |
523 | |
524 | fn alignment(format: Format) -> usize { |
525 | Signature::alignment(format) |
526 | } |
527 | } |
528 | |
529 | impl std::ops::Deref for OwnedSignature { |
530 | type Target = Signature<'static>; |
531 | |
532 | fn deref(&self) -> &Self::Target { |
533 | &self.0 |
534 | } |
535 | } |
536 | |
537 | impl std::convert::From<OwnedSignature> for Signature<'static> { |
538 | fn from(o: OwnedSignature) -> Self { |
539 | o.into_inner() |
540 | } |
541 | } |
542 | |
543 | impl<'a> std::convert::From<Signature<'a>> for OwnedSignature { |
544 | fn from(o: Signature<'a>) -> Self { |
545 | OwnedSignature(o.into_owned()) |
546 | } |
547 | } |
548 | |
549 | impl std::convert::From<OwnedSignature> for crate::Value<'static> { |
550 | fn from(o: OwnedSignature) -> Self { |
551 | o.into_inner().into() |
552 | } |
553 | } |
554 | |
555 | impl TryFrom<String> for OwnedSignature { |
556 | type Error = Error; |
557 | |
558 | fn try_from(value: String) -> Result<Self> { |
559 | Ok(Self(Signature::try_from(value)?)) |
560 | } |
561 | } |
562 | |
563 | impl<'de> Deserialize<'de> for OwnedSignature { |
564 | fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error> |
565 | where |
566 | D: Deserializer<'de>, |
567 | { |
568 | let val: String = String::deserialize(deserializer)?; |
569 | |
570 | OwnedSignature::try_from(val).map_err(op:serde::de::Error::custom) |
571 | } |
572 | } |
573 | |
574 | impl std::fmt::Display for OwnedSignature { |
575 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
576 | std::fmt::Display::fmt(&self.as_str(), f) |
577 | } |
578 | } |
579 | |
580 | #[cfg (test)] |
581 | mod tests { |
582 | use super::{Bytes, Signature}; |
583 | use std::sync::Arc; |
584 | |
585 | #[test ] |
586 | fn bytes_equality() { |
587 | let borrowed1 = Bytes::Borrowed(b"foo" ); |
588 | let borrowed2 = Bytes::Borrowed(b"foo" ); |
589 | let static1 = Bytes::Static(b"foo" ); |
590 | let static2 = Bytes::Static(b"foo" ); |
591 | let owned1 = Bytes::Owned(Arc::new(*b"foo" )); |
592 | let owned2 = Bytes::Owned(Arc::new(*b"foo" )); |
593 | |
594 | assert_eq!(borrowed1, borrowed2); |
595 | assert_eq!(static1, static2); |
596 | assert_eq!(owned1, owned2); |
597 | |
598 | assert_eq!(borrowed1, static1); |
599 | assert_eq!(static1, borrowed1); |
600 | |
601 | assert_eq!(static1, owned1); |
602 | assert_eq!(owned1, static1); |
603 | |
604 | assert_eq!(borrowed1, owned1); |
605 | assert_eq!(owned1, borrowed1); |
606 | } |
607 | |
608 | #[test ] |
609 | fn signature_slicing() { |
610 | let sig = Signature::from_str_unchecked("(asta{sv})" ); |
611 | assert_eq!(sig, "(asta{sv})" ); |
612 | |
613 | let slice = sig.slice(1..); |
614 | assert_eq!(slice.len(), sig.len() - 1); |
615 | assert_eq!(slice, &sig[1..]); |
616 | assert_eq!(slice.as_bytes()[1], b's' ); |
617 | assert_eq!(slice.as_bytes()[2], b't' ); |
618 | |
619 | let slice = slice.slice(2..3); |
620 | assert_eq!(slice.len(), 1); |
621 | assert_eq!(slice, "t" ); |
622 | assert_eq!(slice.slice(1..), "" ); |
623 | } |
624 | |
625 | #[test ] |
626 | fn signature_equality() { |
627 | let sig_a = Signature::from_str_unchecked("(asta{sv})" ); |
628 | let sig_b = Signature::from_str_unchecked("asta{sv}" ); |
629 | assert_eq!(sig_a, sig_b); |
630 | |
631 | let sig_a = Signature::from_str_unchecked("((so)ii(uu))" ); |
632 | let sig_b = Signature::from_str_unchecked("(so)ii(uu)" ); |
633 | assert_eq!(sig_a, sig_b); |
634 | |
635 | let sig_a = Signature::from_str_unchecked("(so)i" ); |
636 | let sig_b = Signature::from_str_unchecked("(so)u" ); |
637 | assert_ne!(sig_a, sig_b); |
638 | } |
639 | } |
640 | |