1use std::fmt::Display;
7use std::hash::{Hash, Hasher};
8
9use serde_json::Value;
10use thiserror::Error;
11
12pub trait AttributeType: Eq + Clone + Hash {}
18impl<T> AttributeType for T where T: Eq + Clone + Hash {}
19
20#[derive(Debug, Clone, Eq, PartialEq, Hash)]
22pub struct Attribute {
23 flattened_field_name: String,
24 value: AttributeValue,
25}
26
27#[derive(Debug, Clone)]
29pub enum AttributeValue {
30 Empty,
31 Bool(bool),
32 Integer(i64),
33 Real(f64),
34 String(String),
35}
36
37impl AttributeValue {
38 pub fn as_str(&self) -> Option<&str> {
39 match self {
40 AttributeValue::String(s) => Some(s),
41 _ => None,
42 }
43 }
44 pub fn as_bool(&self) -> Option<bool> {
45 match self {
46 AttributeValue::Bool(b) => Some(*b),
47 _ => None,
48 }
49 }
50 pub fn as_float(&self) -> Option<f64> {
51 match self {
52 AttributeValue::Real(r) => Some(*r),
53 _ => None,
54 }
55 }
56
57 pub fn as_integer(&self) -> Option<i64> {
58 match self {
59 AttributeValue::Integer(n) => Some(*n),
60 _ => None,
61 }
62 }
63
64 pub fn is_empty(&self) -> bool {
65 matches!(self, AttributeValue::Empty)
66 }
67
68 fn to_bits_helper(f: &f64) -> u64 {
73 if f.is_nan() {
74 f64::NAN.to_bits()
75 } else if *f == 0.0 {
76 0.0_f64.to_bits()
77 } else {
78 f.to_bits()
79 }
80 }
81}
82
83impl Hash for AttributeValue {
86 fn hash<H: Hasher>(&self, state: &mut H) {
87 core::mem::discriminant(self).hash(state);
88 match self {
89 AttributeValue::Bool(v) => state.write_u8(if *v { 1 } else { 0 }),
90 AttributeValue::Integer(i) => state.write_i64(*i),
91 AttributeValue::Real(f) => state.write_u64(Self::to_bits_helper(f)),
92 AttributeValue::String(s) => s.hash(state),
93 AttributeValue::Empty => {}
94 }
95 }
96}
97
98impl PartialEq for AttributeValue {
99 fn eq(&self, other: &Self) -> bool {
100 match (self, other) {
101 (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
102 (Self::Integer(l0), Self::Integer(r0)) => l0 == r0,
103 (Self::Real(l0), Self::Real(r0)) => {
104 Self::to_bits_helper(l0) == Self::to_bits_helper(r0)
105 }
106 (Self::String(l0), Self::String(r0)) => l0 == r0,
107 (Self::Empty, Self::Empty) => true,
108 _ => false,
109 }
110 }
111}
112
113impl Eq for AttributeValue {}
114
115impl Attribute {
116 pub fn from_json_value(
121 field_name: &str,
122 json_value: &serde_json::Value,
123 ) -> Result<Self, JsonConversionError> {
124 Ok(Self {
125 flattened_field_name: field_name.to_owned(),
126 value: AttributeValue::try_from(json_value)?,
127 })
128 }
129
130 pub fn field_name(&self) -> &String {
132 &self.flattened_field_name
133 }
134
135 pub fn value(&self) -> &AttributeValue {
137 &self.value
138 }
139
140 pub fn from_value(flattened_field_name: impl Into<String>, value: AttributeValue) -> Self {
142 Self {
143 flattened_field_name: flattened_field_name.into(),
144 value,
145 }
146 }
147}
148
149impl Display for AttributeValue {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 match self {
152 AttributeValue::Empty => write!(f, ""),
153 AttributeValue::Bool(b) => write!(f, "{}", b),
154 AttributeValue::Integer(n) => write!(f, "{}", n),
155 AttributeValue::Real(r) => write!(f, "{}", r),
156 AttributeValue::String(s) => write!(f, "{}", s),
157 }
158 }
159}
160
161impl Display for Attribute {
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 write!(f, "{}={}", self.flattened_field_name, self.value)
164 }
165}
166
167impl TryFrom<&serde_json::Value> for AttributeValue {
169 type Error = JsonConversionError;
170
171 fn try_from(json_value: &Value) -> Result<Self, Self::Error> {
172 match json_value {
173 Value::Null => Err(JsonConversionError::NullValue),
174 Value::Bool(v) => Ok(AttributeValue::Bool(*v)),
175 Value::Number(n) => {
176 if let Some(i) = n.as_i64() {
177 Ok(AttributeValue::Integer(i))
178 } else if let Some(f) = n.as_f64() {
179 Ok(AttributeValue::Real(f))
180 } else {
181 Err(JsonConversionError::Unsupported(n.clone()))
182 }
183 }
184 Value::String(s) => Ok(AttributeValue::String(s.clone())),
185 Value::Array(_values) => Err(JsonConversionError::ObjectsNotSupported),
186 Value::Object(_) => Err(JsonConversionError::ObjectsNotSupported),
187 }
188 }
189}
190
191#[derive(Debug, Error)]
192pub enum JsonConversionError {
193 #[error("Value {0} is not an i64 nor f64")]
194 Unsupported(serde_json::Number),
195 #[error("Nested objects are not supported")]
196 ObjectsNotSupported,
197 #[error("Value is null")]
198 NullValue,
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use serde_json::json;
205
206 #[test]
207 fn label_value_debug_clone_eq() {
208 let v = AttributeValue::String("test_value".to_string());
209 let c = v.clone();
210 assert_eq!(v, c);
211 let _dbg = format!("{:?}", v);
212 }
213
214 #[test]
215 fn label_info_clone_and_getters() {
216 let original = Attribute::from_value("a.b", AttributeValue::Integer(7));
217 let cloned = original.clone();
218
219 assert_eq!(cloned.field_name(), original.field_name());
220 assert_eq!(cloned.value(), original.value());
221 assert_eq!(cloned.value(), &AttributeValue::Integer(7));
222 }
223
224 #[test]
225 fn label_value_try_from_primitives() {
226 let cases = vec![
227 (json!(true), AttributeValue::Bool(true)),
228 (json!(42), AttributeValue::Integer(42)),
229 (json!(-5), AttributeValue::Integer(-5)),
230 (json!(3.68), AttributeValue::Real(3.68)),
231 (json!("hello"), AttributeValue::String("hello".to_string())),
232 ];
233
234 for (j, expected) in cases {
235 let got = AttributeValue::try_from(&j).unwrap();
236 assert_eq!(got, expected);
237 }
238 }
239
240 #[test]
241 fn label_value_try_from_arrays_returns_error() {
242 let j = json!([1, "x", false, [2, 3], 4.5]);
243 let err = AttributeValue::try_from(&j).unwrap_err();
244 match err {
245 JsonConversionError::ObjectsNotSupported => {}
246 _ => panic!("expected ObjectsNotSupported for arrays"),
247 }
248 }
249
250 #[test]
251 fn label_value_try_from_object_is_error() {
252 let obj = json!({"a": 1});
253 let err = AttributeValue::try_from(&obj).unwrap_err();
254 match err {
255 JsonConversionError::ObjectsNotSupported => {}
256 _ => panic!("expected ObjectsNotSupported"),
257 }
258 }
259
260 #[test]
261 fn from_json_value_ok_paths() {
262 let entry = Attribute::from_json_value("n", &json!("test")).unwrap();
263 assert_eq!(entry.field_name(), "n");
264 assert_eq!(entry.value(), &AttributeValue::String("test".to_string()));
265 }
266
267 #[test]
268 fn from_json_value_arrays_fail() {
269 let json_array = json!([1, "text", true, [1, 2, 3], 42.5]);
270 let err = Attribute::from_json_value("n", &json_array).unwrap_err();
271 match err {
272 JsonConversionError::ObjectsNotSupported => {}
273 _ => panic!("expected ObjectsNotSupported for arrays"),
274 }
275 }
276
277 #[test]
278 fn from_json_value_object_errors() {
279 let obj = json!({"k": "v"});
280 let err = Attribute::from_json_value("n", &obj).unwrap_err();
281 match err {
282 JsonConversionError::ObjectsNotSupported => {}
283 _ => panic!("expected ObjectsNotSupported"),
284 }
285 }
286
287 #[test]
288 fn from_json_value_array_with_object_errors() {
289 let j = json!([1, {"o": 1}, 3]);
290 let err = Attribute::from_json_value("arr", &j).unwrap_err();
291 match err {
292 JsonConversionError::ObjectsNotSupported => {}
293 _ => panic!("expected ObjectsNotSupported"),
294 }
295 }
296
297 #[test]
299 fn attribute_value_as_str_tests() {
300 assert_eq!(
301 AttributeValue::String("hello".to_string()).as_str(),
302 Some("hello")
303 );
304 assert_eq!(AttributeValue::Empty.as_str(), None);
305 assert_eq!(AttributeValue::Bool(true).as_str(), None);
306 assert_eq!(AttributeValue::Integer(42).as_str(), None);
307 assert_eq!(AttributeValue::Real(2.5).as_str(), None);
308 }
309
310 #[test]
311 fn attribute_value_as_bool_tests() {
312 assert_eq!(AttributeValue::Bool(true).as_bool(), Some(true));
313 assert_eq!(AttributeValue::Bool(false).as_bool(), Some(false));
314 assert_eq!(AttributeValue::String("true".to_string()).as_bool(), None);
315 assert_eq!(AttributeValue::Integer(1).as_bool(), None);
316 assert_eq!(AttributeValue::Real(1.0).as_bool(), None);
317 assert_eq!(AttributeValue::Empty.as_bool(), None);
318 }
319
320 #[test]
321 fn attribute_value_as_float_tests() {
322 assert_eq!(AttributeValue::Real(2.5).as_float(), Some(2.5));
323 assert_eq!(AttributeValue::Real(-2.5).as_float(), Some(-2.5));
324 assert_eq!(AttributeValue::Real(0.0).as_float(), Some(0.0));
325 assert!(AttributeValue::Real(f64::NAN).as_float().unwrap().is_nan());
327 assert_eq!(
328 AttributeValue::Real(f64::INFINITY).as_float(),
329 Some(f64::INFINITY)
330 );
331 assert_eq!(
332 AttributeValue::Real(f64::NEG_INFINITY).as_float(),
333 Some(f64::NEG_INFINITY)
334 );
335 assert_eq!(AttributeValue::Integer(42).as_float(), None);
336 assert_eq!(AttributeValue::String("3.14".to_string()).as_float(), None);
337 assert_eq!(AttributeValue::Bool(true).as_float(), None);
338 assert_eq!(AttributeValue::Empty.as_float(), None);
339 }
340
341 #[test]
342 fn attribute_value_as_integer_tests() {
343 assert_eq!(AttributeValue::Integer(42).as_integer(), Some(42));
344 assert_eq!(AttributeValue::Integer(-100).as_integer(), Some(-100));
345 assert_eq!(AttributeValue::Integer(0).as_integer(), Some(0));
346 assert_eq!(AttributeValue::Real(42.0).as_integer(), None);
347 assert_eq!(AttributeValue::String("42".to_string()).as_integer(), None);
348 assert_eq!(AttributeValue::Bool(false).as_integer(), None);
349 assert_eq!(AttributeValue::Empty.as_integer(), None);
350 }
351
352 #[test]
353 fn attribute_value_is_empty_tests() {
354 assert!(AttributeValue::Empty.is_empty());
355 assert!(!AttributeValue::Bool(false).is_empty());
356 assert!(!AttributeValue::Integer(0).is_empty());
357 assert!(!AttributeValue::Real(0.0).is_empty());
358 assert!(!AttributeValue::String("".to_string()).is_empty());
359 }
360
361 #[test]
362 fn attribute_value_display_formatting() {
363 assert_eq!(format!("{}", AttributeValue::Empty), "");
364 assert_eq!(format!("{}", AttributeValue::Bool(true)), "true");
365 assert_eq!(format!("{}", AttributeValue::Bool(false)), "false");
366 assert_eq!(format!("{}", AttributeValue::Integer(42)), "42");
367 assert_eq!(format!("{}", AttributeValue::Integer(-100)), "-100");
368 assert_eq!(format!("{}", AttributeValue::Real(2.5)), "2.5");
369 assert_eq!(format!("{}", AttributeValue::Real(-2.5)), "-2.5");
370 assert_eq!(
371 format!("{}", AttributeValue::String("hello".to_string())),
372 "hello"
373 );
374 }
375
376 #[test]
377 fn attribute_display_formatting() {
378 let attr = Attribute::from_value("field.name", AttributeValue::String("value".to_string()));
379 assert_eq!(format!("{}", attr), "field.name=value");
380
381 let attr_int = Attribute::from_value("count", AttributeValue::Integer(42));
382 assert_eq!(format!("{}", attr_int), "count=42");
383
384 let attr_bool = Attribute::from_value("enabled", AttributeValue::Bool(true));
385 assert_eq!(format!("{}", attr_bool), "enabled=true");
386
387 let attr_empty = Attribute::from_value("optional", AttributeValue::Empty);
388 assert_eq!(format!("{}", attr_empty), "optional=");
389 }
390
391 #[test]
392 fn attribute_from_value_with_different_string_types() {
393 let attr1 = Attribute::from_value("test", AttributeValue::Integer(1));
395 assert_eq!(attr1.field_name(), "test");
396
397 let attr2 = Attribute::from_value("test".to_string(), AttributeValue::Integer(2));
399 assert_eq!(attr2.field_name(), "test");
400
401 let field_name = String::from("dynamic_field");
403 let attr3 = Attribute::from_value(field_name, AttributeValue::Bool(true));
404 assert_eq!(attr3.field_name(), "dynamic_field");
405 }
406
407 #[test]
408 fn json_conversion_edge_cases() {
409 let large_int = json!(9223372036854775807i64); let attr_val = AttributeValue::try_from(&large_int).unwrap();
412 assert_eq!(attr_val.as_integer(), Some(9223372036854775807));
413
414 let small_int = json!(-9223372036854775808i64); let attr_val = AttributeValue::try_from(&small_int).unwrap();
417 assert_eq!(attr_val.as_integer(), Some(-9223372036854775808));
418
419 let float_json = json!(1.23456789);
421 let attr_val = AttributeValue::try_from(&float_json).unwrap();
422 assert_eq!(attr_val.as_float(), Some(1.23456789));
423
424 let zero_float = json!(0.0);
426 let attr_val = AttributeValue::try_from(&zero_float).unwrap();
427 assert_eq!(attr_val.as_float(), Some(0.0));
428
429 let neg_float = json!(-42.5);
431 let attr_val = AttributeValue::try_from(&neg_float).unwrap();
432 assert_eq!(attr_val.as_float(), Some(-42.5));
433 }
434
435 #[test]
436 fn json_conversion_empty_strings_and_arrays() {
437 let empty_str = json!("");
439 let attr_val = AttributeValue::try_from(&empty_str).unwrap();
440 assert_eq!(attr_val, AttributeValue::String("".to_string()));
441 assert_eq!(attr_val.as_str(), Some(""));
442
443 let empty_array = json!([]);
445 let err = AttributeValue::try_from(&empty_array).unwrap_err();
446 match err {
447 JsonConversionError::ObjectsNotSupported => {}
448 _ => panic!("expected ObjectsNotSupported for arrays"),
449 }
450 }
451
452 #[test]
453 fn json_conversion_error_display() {
454 let invalid_number = serde_json::Number::from_f64(f64::NAN);
456 if let Some(num) = invalid_number {
457 let error = JsonConversionError::Unsupported(num.clone());
458 let error_msg = format!("{}", error);
459 assert!(error_msg.contains("is not an i64 nor f64"));
460 }
461
462 let error = JsonConversionError::ObjectsNotSupported;
464 let error_msg = format!("{}", error);
465 assert_eq!(error_msg, "Nested objects are not supported");
466 }
467
468 #[test]
469 fn attribute_value_partial_eq_comprehensive() {
470 let values = vec![
472 AttributeValue::Empty,
473 AttributeValue::Bool(true),
474 AttributeValue::Bool(false),
475 AttributeValue::Integer(0),
476 AttributeValue::Integer(42),
477 AttributeValue::Real(0.0),
478 AttributeValue::Real(42.3),
479 AttributeValue::Real(42.1),
480 AttributeValue::String("".to_string()),
481 AttributeValue::String("test".to_string()),
482 ];
483
484 for value in &values {
486 assert_eq!(value, value);
487 }
488
489 for (i, val1) in values.iter().enumerate() {
491 for (j, val2) in values.iter().enumerate() {
492 if i != j {
493 assert_ne!(val1, val2);
494 }
495 }
496 }
497
498 assert_eq!(
500 AttributeValue::String("test".to_string()),
501 AttributeValue::String("test".to_string())
502 );
503 assert_ne!(
504 AttributeValue::String("test1".to_string()),
505 AttributeValue::String("test2".to_string())
506 );
507 }
508
509 #[test]
510 fn attribute_value_float_eq_corner_cases() {
511 let nan1 = AttributeValue::Real(f64::NAN);
513 let nan2 = AttributeValue::Real(f64::NAN);
514 assert_eq!(nan1, nan2); let pos_zero = AttributeValue::Real(0.0);
518 let neg_zero = AttributeValue::Real(-0.0);
519 assert_eq!(pos_zero, neg_zero); let pos_inf = AttributeValue::Real(f64::INFINITY);
523 let neg_inf = AttributeValue::Real(f64::NEG_INFINITY);
524 assert_ne!(pos_inf, neg_inf);
525
526 assert_ne!(nan1, AttributeValue::Real(0.0));
528 assert_ne!(nan1, AttributeValue::Real(1.0));
529 assert_ne!(nan1, pos_inf);
530
531 assert_eq!(AttributeValue::Real(42.5), AttributeValue::Real(42.5));
533 assert_ne!(AttributeValue::Real(42.5), AttributeValue::Real(42.6));
534
535 let subnormal1 = AttributeValue::Real(f64::MIN_POSITIVE / 2.0);
537 let subnormal2 = AttributeValue::Real(f64::MIN_POSITIVE / 2.0);
538 assert_eq!(subnormal1, subnormal2);
539
540 assert_eq!(
542 AttributeValue::Real(f64::MAX),
543 AttributeValue::Real(f64::MAX)
544 );
545 assert_eq!(
546 AttributeValue::Real(f64::MIN),
547 AttributeValue::Real(f64::MIN)
548 );
549 assert_eq!(
550 AttributeValue::Real(f64::MIN_POSITIVE),
551 AttributeValue::Real(f64::MIN_POSITIVE)
552 );
553 }
554
555 #[test]
556 fn attribute_value_hash_corner_cases() {
557 use std::collections::hash_map::DefaultHasher;
558 use std::hash::{Hash, Hasher};
559
560 fn get_hash<T: Hash>(value: &T) -> u64 {
562 let mut hasher = DefaultHasher::new();
563 value.hash(&mut hasher);
564 hasher.finish()
565 }
566
567 let nan1 = AttributeValue::Real(f64::NAN);
569 let nan2 = AttributeValue::Real(f64::NAN);
570 assert_eq!(nan1, nan2);
571 assert_eq!(get_hash(&nan1), get_hash(&nan2));
572
573 let pos_zero = AttributeValue::Real(0.0);
575 let neg_zero = AttributeValue::Real(-0.0);
576 assert_eq!(pos_zero, neg_zero);
577 assert_eq!(get_hash(&pos_zero), get_hash(&neg_zero));
578
579 let pos_inf = AttributeValue::Real(f64::INFINITY);
581 let neg_inf = AttributeValue::Real(f64::NEG_INFINITY);
582 assert_ne!(pos_inf, neg_inf);
583 assert_ne!(get_hash(&pos_inf), get_hash(&neg_inf));
584
585 let val1 = AttributeValue::Real(42.5);
587 let val2 = AttributeValue::Real(42.5);
588 assert_eq!(val1, val2);
589 assert_eq!(get_hash(&val1), get_hash(&val2));
590
591 let int_val = AttributeValue::Integer(42);
593 let float_val = AttributeValue::Real(42.0);
594 assert_ne!(int_val, float_val);
595 let empty1 = AttributeValue::Empty;
599 let empty2 = AttributeValue::Empty;
600 assert_eq!(get_hash(&empty1), get_hash(&empty2));
601
602 let bool_true1 = AttributeValue::Bool(true);
604 let bool_true2 = AttributeValue::Bool(true);
605 let bool_false = AttributeValue::Bool(false);
606 assert_eq!(get_hash(&bool_true1), get_hash(&bool_true2));
607 assert_ne!(get_hash(&bool_true1), get_hash(&bool_false));
608
609 let str1 = AttributeValue::String("test".to_string());
611 let str2 = AttributeValue::String("test".to_string());
612 let str3 = AttributeValue::String("different".to_string());
613 assert_eq!(get_hash(&str1), get_hash(&str2));
614 assert_ne!(get_hash(&str1), get_hash(&str3));
615 }
616
617 #[test]
618 fn attribute_value_to_bits_helper_edge_cases() {
619 let nan_bits = AttributeValue::to_bits_helper(&f64::NAN);
621 assert_eq!(nan_bits, f64::NAN.to_bits());
622
623 let pos_zero_bits = AttributeValue::to_bits_helper(&0.0);
625 let neg_zero_bits = AttributeValue::to_bits_helper(&-0.0);
626 assert_eq!(pos_zero_bits, 0.0_f64.to_bits());
627 assert_eq!(neg_zero_bits, 0.0_f64.to_bits());
628 assert_eq!(pos_zero_bits, neg_zero_bits);
629
630 let pos_inf_bits = AttributeValue::to_bits_helper(&f64::INFINITY);
632 let neg_inf_bits = AttributeValue::to_bits_helper(&f64::NEG_INFINITY);
633 assert_eq!(pos_inf_bits, f64::INFINITY.to_bits());
634 assert_eq!(neg_inf_bits, f64::NEG_INFINITY.to_bits());
635 assert_ne!(pos_inf_bits, neg_inf_bits);
636
637 let normal_value = 42.5;
639 let normal_bits = AttributeValue::to_bits_helper(&normal_value);
640 assert_eq!(normal_bits, normal_value.to_bits());
641
642 let neg_value = -42.5;
644 let neg_bits = AttributeValue::to_bits_helper(&neg_value);
645 assert_eq!(neg_bits, neg_value.to_bits());
646
647 let subnormal = f64::MIN_POSITIVE / 2.0;
649 let subnormal_bits = AttributeValue::to_bits_helper(&subnormal);
650 assert_eq!(subnormal_bits, subnormal.to_bits());
651
652 assert_eq!(
654 AttributeValue::to_bits_helper(&f64::MAX),
655 f64::MAX.to_bits()
656 );
657 assert_eq!(
658 AttributeValue::to_bits_helper(&f64::MIN),
659 f64::MIN.to_bits()
660 );
661 assert_eq!(
662 AttributeValue::to_bits_helper(&f64::MIN_POSITIVE),
663 f64::MIN_POSITIVE.to_bits()
664 );
665 }
666
667 #[test]
668 fn attribute_value_eq_hash_consistency() {
669 use std::collections::hash_map::DefaultHasher;
673 use std::hash::{Hash, Hasher};
674
675 fn get_hash<T: Hash>(value: &T) -> u64 {
676 let mut hasher = DefaultHasher::new();
677 value.hash(&mut hasher);
678 hasher.finish()
679 }
680
681 let test_cases = vec![
682 (
684 AttributeValue::Real(f64::NAN),
685 AttributeValue::Real(f64::NAN),
686 ),
687 (AttributeValue::Real(0.0), AttributeValue::Real(-0.0)),
689 (
691 AttributeValue::Real(f64::INFINITY),
692 AttributeValue::Real(f64::NEG_INFINITY),
693 ),
694 (AttributeValue::Real(42.5), AttributeValue::Real(42.5)),
696 (AttributeValue::Integer(100), AttributeValue::Integer(100)),
697 (AttributeValue::Bool(true), AttributeValue::Bool(true)),
698 (AttributeValue::Bool(false), AttributeValue::Bool(false)),
699 (
700 AttributeValue::String("test".to_string()),
701 AttributeValue::String("test".to_string()),
702 ),
703 (AttributeValue::Empty, AttributeValue::Empty),
704 ];
705
706 for (val1, val2) in test_cases {
707 if val1 == val2 {
709 assert_eq!(
710 get_hash(&val1),
711 get_hash(&val2),
712 "Equal values must have equal hashes: {:?} == {:?}",
713 val1,
714 val2
715 );
716 }
717 }
718 }
719}