1use std::fs::File;
5use std::io::{self, BufRead};
6
7use super::error::RadiusError;
8
9#[derive(Debug, PartialEq)]
10pub enum SupportedAttributeTypes {
13 AsciiString,
15 ByteString,
17 Integer,
19 Integer64,
21 Date,
23 IPv4Addr,
25 IPv4Prefix,
27 IPv6Addr,
29 IPv6Prefix,
31 InterfaceId,
33 Enum,
35 Tlv,
37 Vsa,
39 Evs,
42 Concat,
45 Extended,
47 LongExtended
49}
50
51
52#[derive(Debug, PartialEq)]
53pub struct DictionaryAttribute {
55 name: String,
60 vendor_name: String,
61 code: u8,
62 code_type: Option<SupportedAttributeTypes>
63}
64
65impl DictionaryAttribute {
66 pub fn name(&self) -> &str {
68 &self.name
69 }
70
71 pub fn code(&self) -> u8 {
73 self.code
74 }
75
76 pub fn code_type(&self) -> &Option<SupportedAttributeTypes> {
78 &self.code_type
79 }
80}
81
82
83#[derive(Debug, PartialEq)]
84pub struct DictionaryValue {
86 attribute_name: String,
87 value_name: String,
88 vendor_name: String,
89 value: String
90}
91
92impl DictionaryValue {
93 pub fn name(&self) -> &str {
95 &self.value_name
96 }
97
98 pub fn attribute_name(&self) -> &str {
100 &self.attribute_name
101 }
102
103 pub fn value(&self) -> &str {
105 &self.value
106 }
107}
108
109
110#[derive(Debug, PartialEq)]
111pub struct DictionaryVendor {
113 name: String,
114 id: u8
115}
116
117
118const COMMENT_PREFIX: &str = "#";
119
120#[derive(Debug, Default, PartialEq)]
121pub struct Dictionary {
123 attributes: Vec<DictionaryAttribute>,
124 values: Vec<DictionaryValue>,
125 vendors: Vec<DictionaryVendor>
126}
127
128#[allow(unused)]
129impl Dictionary {
130 pub fn from_str(dictionary_str: &str) -> Result<Dictionary, RadiusError> {
132 let lines = read_str(dictionary_str);
133 Dictionary::from_lines(lines)
134 }
135
136 pub fn from_file(file_path: &str) -> Result<Dictionary, RadiusError> {
138 match read_file(file_path) {
139 Ok(lines) => Dictionary::from_lines(lines),
140 Err(error) => Err(error)
141 }
142 }
143
144 pub fn add_str(&mut self, dictionary_str: &str) -> Result<(), RadiusError> {
147 let lines = read_str(dictionary_str);
148 parse_lines(lines, &mut self.attributes, &mut self.values, &mut self.vendors)
149 }
150
151 pub fn add_file(&mut self, file_path: &str) -> Result<(), RadiusError> {
154 match read_file(file_path) {
155 Ok(lines) => parse_lines(
156 lines, &mut self.attributes, &mut self.values, &mut self.vendors
157 ),
158 Err(error) => Err(error)
159 }
160 }
161
162 pub fn attributes(&self) -> &[DictionaryAttribute] {
164 &self.attributes
165 }
166
167 pub fn values(&self) -> &[DictionaryValue] {
169 &self.values
170 }
171
172 pub fn vendors(&self) -> &[DictionaryVendor] {
174 &self.vendors
175 }
176
177 fn from_lines(lines: StringIterator) -> Result<Dictionary, RadiusError> {
178 let mut attributes: Vec<DictionaryAttribute> = Vec::new();
179 let mut values: Vec<DictionaryValue> = Vec::new();
180 let mut vendors: Vec<DictionaryVendor> = Vec::new();
181
182 match parse_lines(lines, &mut attributes, &mut values, &mut vendors) {
183 Ok(()) => Ok(Dictionary { attributes, values, vendors }),
184 Err(error) => Err(error),
185 }
186 }
187}
188
189fn assign_attribute_type(code_type: &str) -> Option<SupportedAttributeTypes> {
190 match code_type {
191 "text" => Some(SupportedAttributeTypes::AsciiString),
192 "string" => Some(SupportedAttributeTypes::ByteString),
193 "integer" => Some(SupportedAttributeTypes::Integer),
194 "integer64" => Some(SupportedAttributeTypes::Integer64),
195 "time" => Some(SupportedAttributeTypes::Date),
196 "ipv4addr" => Some(SupportedAttributeTypes::IPv4Addr),
197 "ipv4prefix" => Some(SupportedAttributeTypes::IPv4Prefix),
198 "ipv6addr" => Some(SupportedAttributeTypes::IPv6Addr),
199 "ipv6prefix" => Some(SupportedAttributeTypes::IPv6Prefix),
200 "ifid" => Some(SupportedAttributeTypes::InterfaceId),
201 "enum" => Some(SupportedAttributeTypes::Enum),
202 "tlv" => Some(SupportedAttributeTypes::Tlv),
203 "vsa" => Some(SupportedAttributeTypes::Vsa),
204 "evs" => Some(SupportedAttributeTypes::Evs),
205 "concat" => Some(SupportedAttributeTypes::Concat),
206 "extended" => Some(SupportedAttributeTypes::Extended),
207 "long-extended" => Some(SupportedAttributeTypes::LongExtended),
208 _ => None
209 }
210}
211
212type StringIterator = Box<dyn Iterator<Item = String>>;
213
214fn filter_lines<T: Iterator<Item = String> + 'static>(lines: T) -> StringIterator {
215 Box::new(
216 lines
217 .filter(|line| !line.is_empty())
218 .filter(|line| !line.contains(&COMMENT_PREFIX))
219 )
220}
221
222fn read_file(file_path: &str) -> Result<StringIterator, RadiusError> {
223 let reader = io::BufReader::new(File::open(file_path).map_err(|error| RadiusError::MalformedDictionaryError { error })?);
224 Ok(filter_lines(reader.lines().filter_map(Result::ok)))
225}
226
227fn read_str(dictionary_str: &str) -> StringIterator {
228 let lines: Vec<String> = dictionary_str.to_string().lines()
229 .map(|line| line.to_owned()).collect();
230 filter_lines(lines.into_iter())
231}
232
233fn parse_lines(lines: StringIterator, attributes: &mut Vec<DictionaryAttribute>, values: &mut Vec<DictionaryValue>, vendors: &mut Vec<DictionaryVendor>) -> Result<(), RadiusError>{
234 let mut vendor_name: String = String::new();
235
236 for line in lines {
237 let parsed_line: Vec<&str> = line.split_whitespace().filter(|&item| !item.is_empty()).collect();
238 match parsed_line[0] {
239 "ATTRIBUTE" => parse_attribute(parsed_line, &vendor_name, attributes),
240 "VALUE" => parse_value(parsed_line, &vendor_name, values),
241 "VENDOR" => parse_vendor(parsed_line, vendors),
242 "BEGIN-VENDOR" => { vendor_name.insert_str(0, parsed_line[1]) },
243 "END-VENDOR" => { vendor_name.clear() },
244 _ => continue
245 }
246 };
247
248 Ok(())
249}
250
251fn parse_attribute(parsed_line: Vec<&str>, vendor_name: &str, attributes: &mut Vec<DictionaryAttribute>) {
252 if let Ok(code) = parsed_line[2].parse::<u8>() {
253 attributes.push(DictionaryAttribute {
254 name: parsed_line[1].to_string(),
255 vendor_name: vendor_name.to_string(),
256 code,
257 code_type: assign_attribute_type(parsed_line[3])
258 });
259 }
260}
261
262fn parse_value(parsed_line: Vec<&str>, vendor_name: &str, values: &mut Vec<DictionaryValue>) {
263 values.push(DictionaryValue {
264 attribute_name: parsed_line[1].to_string(),
265 value_name: parsed_line[2].to_string(),
266 vendor_name: vendor_name.to_string(),
267 value: parsed_line[3].to_string()
268 })
269}
270
271fn parse_vendor(parsed_line: Vec<&str>, vendors: &mut Vec<DictionaryVendor>) {
272 if let Ok(id) = parsed_line[2].parse::<u8>() {
273 vendors.push(DictionaryVendor {
274 name: parsed_line[1].to_string(),
275 id,
276 })
277 }
278}
279
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn test_from_str() {
287 let dictionary_str = include_str!("../../dict_examples/test_dictionary_dict");
288
289 let dict = Dictionary::from_str(dictionary_str).unwrap();
290
291 let mut attributes: Vec<DictionaryAttribute> = Vec::new();
292 attributes.push(DictionaryAttribute {
293 name: "User-Name".to_string(),
294 vendor_name: "".to_string(),
295 code: 1,
296 code_type: Some(SupportedAttributeTypes::AsciiString)
297 });
298 attributes.push(DictionaryAttribute {
299 name: "NAS-IP-Address".to_string(),
300 vendor_name: "".to_string(),
301 code: 4,
302 code_type: Some(SupportedAttributeTypes::IPv4Addr)
303 });
304 attributes.push(DictionaryAttribute {
305 name: "NAS-Port-Id".to_string(),
306 vendor_name: "".to_string(),
307 code: 5,
308 code_type: Some(SupportedAttributeTypes::Integer)
309 });
310 attributes.push(DictionaryAttribute {
311 name: "Framed-Protocol".to_string(),
312 vendor_name: "".to_string(),
313 code: 7,
314 code_type: Some(SupportedAttributeTypes::Integer)
315 });
316 attributes.push(DictionaryAttribute {
317 name: "Chargeable-User-Identity".to_string(),
318 vendor_name: "".to_string(),
319 code: 89,
320 code_type: Some(SupportedAttributeTypes::ByteString)
321 });
322 attributes.push(DictionaryAttribute {
323 name: "Delegated-IPv6-Prefix".to_string(),
324 vendor_name: "".to_string(),
325 code: 123,
326 code_type: Some(SupportedAttributeTypes::IPv6Prefix)
327 });
328 attributes.push(DictionaryAttribute {
329 name: "MIP6-Feature-Vector".to_string(),
330 vendor_name: "".to_string(),
331 code: 124,
332 code_type: Some(SupportedAttributeTypes::Integer64)
333 });
334 attributes.push(DictionaryAttribute {
335 name: "Mobile-Node-Identifier".to_string(),
336 vendor_name: "".to_string(),
337 code: 145,
338 code_type: Some(SupportedAttributeTypes::ByteString)
339 });
340 attributes.push(DictionaryAttribute {
341 name: "PMIP6-Home-Interface-ID".to_string(),
342 vendor_name: "".to_string(),
343 code: 153,
344 code_type: Some(SupportedAttributeTypes::InterfaceId)
345 });
346 attributes.push(DictionaryAttribute {
347 name: "PMIP6-Home-IPv4-HoA".to_string(),
348 vendor_name: "".to_string(),
349 code: 155,
350 code_type: Some(SupportedAttributeTypes::IPv4Prefix)
351 });
352 attributes.push(DictionaryAttribute {
353 name: "Somevendor-Name".to_string(),
354 vendor_name: "Somevendor".to_string(),
355 code: 1,
356 code_type: Some(SupportedAttributeTypes::AsciiString)
357 });
358 attributes.push(DictionaryAttribute {
359 name: "Somevendor-Number".to_string(),
360 vendor_name: "Somevendor".to_string(),
361 code: 2,
362 code_type: Some(SupportedAttributeTypes::Integer)
363 });
364 attributes.push(DictionaryAttribute {
365 name: "Class".to_string(),
366 vendor_name: "".to_string(),
367 code: 25,
368 code_type: Some(SupportedAttributeTypes::ByteString)
369 });
370
371 let mut values: Vec<DictionaryValue> = Vec::new();
372 values.push(DictionaryValue {
373 attribute_name: "Framed-Protocol".to_string(),
374 value_name: "PPP".to_string(),
375 vendor_name: "".to_string(),
376 value: "1".to_string()
377 });
378 values.push(DictionaryValue {
379 attribute_name: "Somevendor-Number".to_string(),
380 value_name: "Two".to_string(),
381 vendor_name: "Somevendor".to_string(),
382 value: "2".to_string()
383 });
384
385 let mut vendors: Vec<DictionaryVendor> = Vec::new();
386 vendors.push(DictionaryVendor {
387 name: "Somevendor".to_string(),
388 id: 10,
389 });
390
391 let expected_dict = Dictionary { attributes, values, vendors };
392 assert_eq!(dict, expected_dict)
393 }
394
395 #[test]
396 fn test_from_file() {
397 let dictionary_path = "./dict_examples/test_dictionary_dict";
398
399 let dict = Dictionary::from_file(dictionary_path).unwrap();
400
401 let mut attributes: Vec<DictionaryAttribute> = Vec::new();
402 attributes.push(DictionaryAttribute {
403 name: "User-Name".to_string(),
404 vendor_name: "".to_string(),
405 code: 1,
406 code_type: Some(SupportedAttributeTypes::AsciiString)
407 });
408 attributes.push(DictionaryAttribute {
409 name: "NAS-IP-Address".to_string(),
410 vendor_name: "".to_string(),
411 code: 4,
412 code_type: Some(SupportedAttributeTypes::IPv4Addr)
413 });
414 attributes.push(DictionaryAttribute {
415 name: "NAS-Port-Id".to_string(),
416 vendor_name: "".to_string(),
417 code: 5,
418 code_type: Some(SupportedAttributeTypes::Integer)
419 });
420 attributes.push(DictionaryAttribute {
421 name: "Framed-Protocol".to_string(),
422 vendor_name: "".to_string(),
423 code: 7,
424 code_type: Some(SupportedAttributeTypes::Integer)
425 });
426 attributes.push(DictionaryAttribute {
427 name: "Chargeable-User-Identity".to_string(),
428 vendor_name: "".to_string(),
429 code: 89,
430 code_type: Some(SupportedAttributeTypes::ByteString)
431 });
432 attributes.push(DictionaryAttribute {
433 name: "Delegated-IPv6-Prefix".to_string(),
434 vendor_name: "".to_string(),
435 code: 123,
436 code_type: Some(SupportedAttributeTypes::IPv6Prefix)
437 });
438 attributes.push(DictionaryAttribute {
439 name: "MIP6-Feature-Vector".to_string(),
440 vendor_name: "".to_string(),
441 code: 124,
442 code_type: Some(SupportedAttributeTypes::Integer64)
443 });
444 attributes.push(DictionaryAttribute {
445 name: "Mobile-Node-Identifier".to_string(),
446 vendor_name: "".to_string(),
447 code: 145,
448 code_type: Some(SupportedAttributeTypes::ByteString)
449 });
450 attributes.push(DictionaryAttribute {
451 name: "PMIP6-Home-Interface-ID".to_string(),
452 vendor_name: "".to_string(),
453 code: 153,
454 code_type: Some(SupportedAttributeTypes::InterfaceId)
455 });
456 attributes.push(DictionaryAttribute {
457 name: "PMIP6-Home-IPv4-HoA".to_string(),
458 vendor_name: "".to_string(),
459 code: 155,
460 code_type: Some(SupportedAttributeTypes::IPv4Prefix)
461 });
462 attributes.push(DictionaryAttribute {
463 name: "Somevendor-Name".to_string(),
464 vendor_name: "Somevendor".to_string(),
465 code: 1,
466 code_type: Some(SupportedAttributeTypes::AsciiString)
467 });
468 attributes.push(DictionaryAttribute {
469 name: "Somevendor-Number".to_string(),
470 vendor_name: "Somevendor".to_string(),
471 code: 2,
472 code_type: Some(SupportedAttributeTypes::Integer)
473 });
474 attributes.push(DictionaryAttribute {
475 name: "Class".to_string(),
476 vendor_name: "".to_string(),
477 code: 25,
478 code_type: Some(SupportedAttributeTypes::ByteString)
479 });
480
481 let mut values: Vec<DictionaryValue> = Vec::new();
482 values.push(DictionaryValue {
483 attribute_name: "Framed-Protocol".to_string(),
484 value_name: "PPP".to_string(),
485 vendor_name: "".to_string(),
486 value: "1".to_string()
487 });
488 values.push(DictionaryValue {
489 attribute_name: "Somevendor-Number".to_string(),
490 value_name: "Two".to_string(),
491 vendor_name: "Somevendor".to_string(),
492 value: "2".to_string()
493 });
494
495 let mut vendors: Vec<DictionaryVendor> = Vec::new();
496 vendors.push(DictionaryVendor {
497 name: "Somevendor".to_string(),
498 id: 10,
499 });
500
501 let expected_dict = Dictionary { attributes, values, vendors };
502 assert_eq!(dict, expected_dict)
503 }
504
505 #[test]
506 fn test_add_str() {
507 let empty_dictionary_str = include_str!("../../dict_examples/empty_test_dictionary_dict");
508 let dictionary_str = include_str!("../../dict_examples/test_dictionary_dict");
509
510 let mut dict = Dictionary::from_str(empty_dictionary_str).unwrap();
511 dict.add_str(dictionary_str).unwrap();
512
513 let mut attributes: Vec<DictionaryAttribute> = Vec::new();
514 attributes.push(DictionaryAttribute {
515 name: "User-Name".to_string(),
516 vendor_name: "".to_string(),
517 code: 1,
518 code_type: Some(SupportedAttributeTypes::AsciiString)
519 });
520 attributes.push(DictionaryAttribute {
521 name: "NAS-IP-Address".to_string(),
522 vendor_name: "".to_string(),
523 code: 4,
524 code_type: Some(SupportedAttributeTypes::IPv4Addr)
525 });
526 attributes.push(DictionaryAttribute {
527 name: "NAS-Port-Id".to_string(),
528 vendor_name: "".to_string(),
529 code: 5,
530 code_type: Some(SupportedAttributeTypes::Integer)
531 });
532 attributes.push(DictionaryAttribute {
533 name: "Framed-Protocol".to_string(),
534 vendor_name: "".to_string(),
535 code: 7,
536 code_type: Some(SupportedAttributeTypes::Integer)
537 });
538 attributes.push(DictionaryAttribute {
539 name: "Chargeable-User-Identity".to_string(),
540 vendor_name: "".to_string(),
541 code: 89,
542 code_type: Some(SupportedAttributeTypes::ByteString)
543 });
544 attributes.push(DictionaryAttribute {
545 name: "Delegated-IPv6-Prefix".to_string(),
546 vendor_name: "".to_string(),
547 code: 123,
548 code_type: Some(SupportedAttributeTypes::IPv6Prefix)
549 });
550 attributes.push(DictionaryAttribute {
551 name: "MIP6-Feature-Vector".to_string(),
552 vendor_name: "".to_string(),
553 code: 124,
554 code_type: Some(SupportedAttributeTypes::Integer64)
555 });
556 attributes.push(DictionaryAttribute {
557 name: "Mobile-Node-Identifier".to_string(),
558 vendor_name: "".to_string(),
559 code: 145,
560 code_type: Some(SupportedAttributeTypes::ByteString)
561 });
562 attributes.push(DictionaryAttribute {
563 name: "PMIP6-Home-Interface-ID".to_string(),
564 vendor_name: "".to_string(),
565 code: 153,
566 code_type: Some(SupportedAttributeTypes::InterfaceId)
567 });
568 attributes.push(DictionaryAttribute {
569 name: "PMIP6-Home-IPv4-HoA".to_string(),
570 vendor_name: "".to_string(),
571 code: 155,
572 code_type: Some(SupportedAttributeTypes::IPv4Prefix)
573 });
574 attributes.push(DictionaryAttribute {
575 name: "Somevendor-Name".to_string(),
576 vendor_name: "Somevendor".to_string(),
577 code: 1,
578 code_type: Some(SupportedAttributeTypes::AsciiString)
579 });
580 attributes.push(DictionaryAttribute {
581 name: "Somevendor-Number".to_string(),
582 vendor_name: "Somevendor".to_string(),
583 code: 2,
584 code_type: Some(SupportedAttributeTypes::Integer)
585 });
586 attributes.push(DictionaryAttribute {
587 name: "Class".to_string(),
588 vendor_name: "".to_string(),
589 code: 25,
590 code_type: Some(SupportedAttributeTypes::ByteString)
591 });
592
593 let mut values: Vec<DictionaryValue> = Vec::new();
594 values.push(DictionaryValue {
595 attribute_name: "Framed-Protocol".to_string(),
596 value_name: "PPP".to_string(),
597 vendor_name: "".to_string(),
598 value: "1".to_string()
599 });
600 values.push(DictionaryValue {
601 attribute_name: "Somevendor-Number".to_string(),
602 value_name: "Two".to_string(),
603 vendor_name: "Somevendor".to_string(),
604 value: "2".to_string()
605 });
606
607 let mut vendors: Vec<DictionaryVendor> = Vec::new();
608 vendors.push(DictionaryVendor {
609 name: "Somevendor".to_string(),
610 id: 10,
611 });
612
613 let expected_dict = Dictionary { attributes, values, vendors };
614 assert_eq!(dict, expected_dict)
615 }
616
617 #[test]
618 fn test_add_file() {
619 let empty_dictionary_path = "./dict_examples/empty_test_dictionary_dict";
620 let dictionary_path = "./dict_examples/test_dictionary_dict";
621
622 let mut dict = Dictionary::from_file(empty_dictionary_path).unwrap();
623 dict.add_file(dictionary_path).unwrap();
624
625 let mut attributes: Vec<DictionaryAttribute> = Vec::new();
626 attributes.push(DictionaryAttribute {
627 name: "User-Name".to_string(),
628 vendor_name: "".to_string(),
629 code: 1,
630 code_type: Some(SupportedAttributeTypes::AsciiString)
631 });
632 attributes.push(DictionaryAttribute {
633 name: "NAS-IP-Address".to_string(),
634 vendor_name: "".to_string(),
635 code: 4,
636 code_type: Some(SupportedAttributeTypes::IPv4Addr)
637 });
638 attributes.push(DictionaryAttribute {
639 name: "NAS-Port-Id".to_string(),
640 vendor_name: "".to_string(),
641 code: 5,
642 code_type: Some(SupportedAttributeTypes::Integer)
643 });
644 attributes.push(DictionaryAttribute {
645 name: "Framed-Protocol".to_string(),
646 vendor_name: "".to_string(),
647 code: 7,
648 code_type: Some(SupportedAttributeTypes::Integer)
649 });
650 attributes.push(DictionaryAttribute {
651 name: "Chargeable-User-Identity".to_string(),
652 vendor_name: "".to_string(),
653 code: 89,
654 code_type: Some(SupportedAttributeTypes::ByteString)
655 });
656 attributes.push(DictionaryAttribute {
657 name: "Delegated-IPv6-Prefix".to_string(),
658 vendor_name: "".to_string(),
659 code: 123,
660 code_type: Some(SupportedAttributeTypes::IPv6Prefix)
661 });
662 attributes.push(DictionaryAttribute {
663 name: "MIP6-Feature-Vector".to_string(),
664 vendor_name: "".to_string(),
665 code: 124,
666 code_type: Some(SupportedAttributeTypes::Integer64)
667 });
668 attributes.push(DictionaryAttribute {
669 name: "Mobile-Node-Identifier".to_string(),
670 vendor_name: "".to_string(),
671 code: 145,
672 code_type: Some(SupportedAttributeTypes::ByteString)
673 });
674 attributes.push(DictionaryAttribute {
675 name: "PMIP6-Home-Interface-ID".to_string(),
676 vendor_name: "".to_string(),
677 code: 153,
678 code_type: Some(SupportedAttributeTypes::InterfaceId)
679 });
680 attributes.push(DictionaryAttribute {
681 name: "PMIP6-Home-IPv4-HoA".to_string(),
682 vendor_name: "".to_string(),
683 code: 155,
684 code_type: Some(SupportedAttributeTypes::IPv4Prefix)
685 });
686 attributes.push(DictionaryAttribute {
687 name: "Somevendor-Name".to_string(),
688 vendor_name: "Somevendor".to_string(),
689 code: 1,
690 code_type: Some(SupportedAttributeTypes::AsciiString)
691 });
692 attributes.push(DictionaryAttribute {
693 name: "Somevendor-Number".to_string(),
694 vendor_name: "Somevendor".to_string(),
695 code: 2,
696 code_type: Some(SupportedAttributeTypes::Integer)
697 });
698 attributes.push(DictionaryAttribute {
699 name: "Class".to_string(),
700 vendor_name: "".to_string(),
701 code: 25,
702 code_type: Some(SupportedAttributeTypes::ByteString)
703 });
704
705 let mut values: Vec<DictionaryValue> = Vec::new();
706 values.push(DictionaryValue {
707 attribute_name: "Framed-Protocol".to_string(),
708 value_name: "PPP".to_string(),
709 vendor_name: "".to_string(),
710 value: "1".to_string()
711 });
712 values.push(DictionaryValue {
713 attribute_name: "Somevendor-Number".to_string(),
714 value_name: "Two".to_string(),
715 vendor_name: "Somevendor".to_string(),
716 value: "2".to_string()
717 });
718
719 let mut vendors: Vec<DictionaryVendor> = Vec::new();
720 vendors.push(DictionaryVendor {
721 name: "Somevendor".to_string(),
722 id: 10,
723 });
724
725 let expected_dict = Dictionary { attributes, values, vendors };
726 assert_eq!(dict, expected_dict)
727 }
728}