1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7fn trimmed_non_empty(value: impl AsRef<str>) -> Option<String> {
8 let trimmed = value.as_ref().trim();
9
10 (!trimmed.is_empty()).then(|| trimmed.to_string())
11}
12
13fn is_ascii_safe(value: &str) -> bool {
14 value
15 .chars()
16 .all(|character| character.is_ascii() && !character.is_ascii_control())
17}
18
19macro_rules! impl_trimmed_text_type {
20 ($type_name:ident, $error_name:ident, $empty_message:literal) => {
21 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
22 pub enum $error_name {
23 Empty,
24 }
25
26 impl fmt::Display for $error_name {
27 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match self {
29 Self::Empty => formatter.write_str($empty_message),
30 }
31 }
32 }
33
34 impl Error for $error_name {}
35
36 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
37 pub struct $type_name(String);
38
39 impl $type_name {
40 pub fn new(value: impl AsRef<str>) -> Result<Self, $error_name> {
46 trimmed_non_empty(value).ok_or($error_name::Empty).map(Self)
47 }
48
49 #[must_use]
50 pub fn as_str(&self) -> &str {
51 &self.0
52 }
53 }
54
55 impl AsRef<str> for $type_name {
56 fn as_ref(&self) -> &str {
57 self.as_str()
58 }
59 }
60
61 impl fmt::Display for $type_name {
62 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
63 formatter.write_str(self.as_str())
64 }
65 }
66
67 impl FromStr for $type_name {
68 type Err = $error_name;
69
70 fn from_str(value: &str) -> Result<Self, Self::Err> {
71 Self::new(value)
72 }
73 }
74
75 impl TryFrom<&str> for $type_name {
76 type Error = $error_name;
77
78 fn try_from(value: &str) -> Result<Self, Self::Error> {
79 Self::new(value)
80 }
81 }
82 };
83}
84
85impl_trimmed_text_type!(
86 AddressLine,
87 AddressLineError,
88 "address line cannot be empty"
89);
90impl_trimmed_text_type!(
91 AddressLine1,
92 AddressLine1Error,
93 "address line 1 cannot be empty"
94);
95impl_trimmed_text_type!(
96 AddressLine2,
97 AddressLine2Error,
98 "address line 2 cannot be empty"
99);
100impl_trimmed_text_type!(
101 StreetNumber,
102 StreetNumberError,
103 "street number cannot be empty"
104);
105impl_trimmed_text_type!(StreetName, StreetNameError, "street name cannot be empty");
106impl_trimmed_text_type!(
107 UnitDesignator,
108 UnitDesignatorError,
109 "unit designator cannot be empty"
110);
111impl_trimmed_text_type!(UnitNumber, UnitNumberError, "unit number cannot be empty");
112impl_trimmed_text_type!(Locality, LocalityError, "locality cannot be empty");
113impl_trimmed_text_type!(
114 AdministrativeArea,
115 AdministrativeAreaError,
116 "administrative area cannot be empty"
117);
118impl_trimmed_text_type!(
119 CountrySubdivision,
120 CountrySubdivisionError,
121 "country subdivision cannot be empty"
122);
123
124impl From<AddressLine1> for AddressLine {
125 fn from(value: AddressLine1) -> Self {
126 Self(value.0)
127 }
128}
129
130impl From<AddressLine2> for AddressLine {
131 fn from(value: AddressLine2) -> Self {
132 Self(value.0)
133 }
134}
135
136#[derive(Clone, Copy, Debug, Eq, PartialEq)]
137pub enum PostalCodeError {
138 Empty,
139 InvalidCharacter,
140}
141
142impl fmt::Display for PostalCodeError {
143 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
144 match self {
145 Self::Empty => formatter.write_str("postal code cannot be empty"),
146 Self::InvalidCharacter => {
147 formatter.write_str("postal code must contain only ASCII-safe characters")
148 },
149 }
150 }
151}
152
153impl Error for PostalCodeError {}
154
155#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
156pub struct PostalCode(String);
157
158impl PostalCode {
159 pub fn new(value: impl AsRef<str>) -> Result<Self, PostalCodeError> {
167 let trimmed = value.as_ref().trim();
168
169 if trimmed.is_empty() {
170 return Err(PostalCodeError::Empty);
171 }
172
173 if !is_ascii_safe(trimmed) {
174 return Err(PostalCodeError::InvalidCharacter);
175 }
176
177 Ok(Self(trimmed.to_string()))
178 }
179
180 #[must_use]
181 pub fn as_str(&self) -> &str {
182 &self.0
183 }
184}
185
186impl AsRef<str> for PostalCode {
187 fn as_ref(&self) -> &str {
188 self.as_str()
189 }
190}
191
192impl fmt::Display for PostalCode {
193 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
194 formatter.write_str(self.as_str())
195 }
196}
197
198impl FromStr for PostalCode {
199 type Err = PostalCodeError;
200
201 fn from_str(value: &str) -> Result<Self, Self::Err> {
202 Self::new(value)
203 }
204}
205
206impl TryFrom<&str> for PostalCode {
207 type Error = PostalCodeError;
208
209 fn try_from(value: &str) -> Result<Self, Self::Error> {
210 Self::new(value)
211 }
212}
213
214#[derive(Clone, Copy, Debug, Eq, PartialEq)]
215pub enum AddressCountryCodeError {
216 Empty,
217 InvalidLength,
218 InvalidCharacter,
219}
220
221impl fmt::Display for AddressCountryCodeError {
222 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
223 match self {
224 Self::Empty => formatter.write_str("address country code cannot be empty"),
225 Self::InvalidLength => formatter
226 .write_str("address country code must contain exactly two alphabetic characters"),
227 Self::InvalidCharacter => formatter
228 .write_str("address country code must contain only ASCII alphabetic characters"),
229 }
230 }
231}
232
233impl Error for AddressCountryCodeError {}
234
235#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
236pub struct AddressCountryCode(String);
237
238impl AddressCountryCode {
239 pub fn new(value: impl AsRef<str>) -> Result<Self, AddressCountryCodeError> {
250 let trimmed = value.as_ref().trim();
251 let character_count = trimmed.chars().count();
252
253 if trimmed.is_empty() {
254 return Err(AddressCountryCodeError::Empty);
255 }
256
257 if character_count != 2 {
258 return Err(AddressCountryCodeError::InvalidLength);
259 }
260
261 if !trimmed
262 .chars()
263 .all(|character| character.is_ascii_alphabetic())
264 {
265 return Err(AddressCountryCodeError::InvalidCharacter);
266 }
267
268 Ok(Self(trimmed.to_ascii_uppercase()))
269 }
270
271 #[must_use]
272 pub fn as_str(&self) -> &str {
273 &self.0
274 }
275}
276
277impl AsRef<str> for AddressCountryCode {
278 fn as_ref(&self) -> &str {
279 self.as_str()
280 }
281}
282
283impl fmt::Display for AddressCountryCode {
284 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
285 formatter.write_str(self.as_str())
286 }
287}
288
289impl FromStr for AddressCountryCode {
290 type Err = AddressCountryCodeError;
291
292 fn from_str(value: &str) -> Result<Self, Self::Err> {
293 Self::new(value)
294 }
295}
296
297impl TryFrom<&str> for AddressCountryCode {
298 type Error = AddressCountryCodeError;
299
300 fn try_from(value: &str) -> Result<Self, Self::Error> {
301 Self::new(value)
302 }
303}
304
305#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
306pub enum AddressComponentKind {
307 Line1,
308 Line2,
309 StreetNumber,
310 StreetName,
311 UnitDesignator,
312 UnitNumber,
313 Locality,
314 AdministrativeArea,
315 PostalCode,
316 Country,
317 CountrySubdivision,
318 Region,
319 Landmark,
320 CareOf,
321 Attention,
322 Organization,
323 Other,
324}
325
326#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
327pub enum AddressFormatHint {
328 #[default]
329 Unknown,
330 UnitedStatesLike,
331 CanadaLike,
332 UnitedKingdomLike,
333 EuropeanLike,
334 JapanLike,
335 International,
336 Custom,
337}
338
339#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
340pub enum AddressValidationLevel {
341 #[default]
342 Unvalidated,
343 SyntaxChecked,
344 ComponentChecked,
345 PostalChecked,
346 Geocoded,
347 Verified,
348}
349
350#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
351pub enum AddressKind {
352 #[default]
353 Physical,
354 Mailing,
355 Billing,
356 Shipping,
357 Legal,
358 Registered,
359 Service,
360 Other,
361}
362
363#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
364pub enum AddressUsageKind {
365 Residential,
366 Commercial,
367 Industrial,
368 Government,
369 Educational,
370 Healthcare,
371 Mixed,
372 #[default]
373 Unknown,
374}
375
376#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
377pub enum AddressPrecision {
378 #[default]
379 Unknown,
380 Country,
381 Region,
382 Locality,
383 PostalCode,
384 Street,
385 Building,
386 Unit,
387}
388
389#[derive(Clone, Debug, Eq, Hash, PartialEq)]
390pub struct StreetAddress {
391 pub number: StreetNumber,
392 pub name: StreetName,
393 pub unit_designator: Option<UnitDesignator>,
394 pub unit_number: Option<UnitNumber>,
395}
396
397impl StreetAddress {
398 #[must_use]
399 pub const fn new(number: StreetNumber, name: StreetName) -> Self {
400 Self {
401 number,
402 name,
403 unit_designator: None,
404 unit_number: None,
405 }
406 }
407
408 #[must_use]
409 pub fn with_unit(mut self, designator: UnitDesignator, number: UnitNumber) -> Self {
410 self.unit_designator = Some(designator);
411 self.unit_number = Some(number);
412 self
413 }
414}
415
416#[derive(Clone, Debug, Eq, PartialEq)]
417pub enum AddressError {
418 Line(AddressLineError),
419 PostalCode(PostalCodeError),
420 Locality(LocalityError),
421 AdministrativeArea(AdministrativeAreaError),
422 CountryCode(AddressCountryCodeError),
423}
424
425impl fmt::Display for AddressError {
426 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
427 match self {
428 Self::Line(error) => error.fmt(formatter),
429 Self::PostalCode(error) => error.fmt(formatter),
430 Self::Locality(error) => error.fmt(formatter),
431 Self::AdministrativeArea(error) => error.fmt(formatter),
432 Self::CountryCode(error) => error.fmt(formatter),
433 }
434 }
435}
436
437impl Error for AddressError {
438 fn source(&self) -> Option<&(dyn Error + 'static)> {
439 match self {
440 Self::Line(error) => Some(error),
441 Self::PostalCode(error) => Some(error),
442 Self::Locality(error) => Some(error),
443 Self::AdministrativeArea(error) => Some(error),
444 Self::CountryCode(error) => Some(error),
445 }
446 }
447}
448
449impl From<AddressLineError> for AddressError {
450 fn from(value: AddressLineError) -> Self {
451 Self::Line(value)
452 }
453}
454
455impl From<PostalCodeError> for AddressError {
456 fn from(value: PostalCodeError) -> Self {
457 Self::PostalCode(value)
458 }
459}
460
461impl From<LocalityError> for AddressError {
462 fn from(value: LocalityError) -> Self {
463 Self::Locality(value)
464 }
465}
466
467impl From<AdministrativeAreaError> for AddressError {
468 fn from(value: AdministrativeAreaError) -> Self {
469 Self::AdministrativeArea(value)
470 }
471}
472
473impl From<AddressCountryCodeError> for AddressError {
474 fn from(value: AddressCountryCodeError) -> Self {
475 Self::CountryCode(value)
476 }
477}
478
479#[derive(Clone, Debug, Eq, PartialEq)]
480pub struct Address {
481 pub lines: Vec<AddressLine>,
482 pub locality: Option<Locality>,
483 pub administrative_area: Option<AdministrativeArea>,
484 pub postal_code: Option<PostalCode>,
485 pub country_code: Option<AddressCountryCode>,
486 pub kind: AddressKind,
487 pub usage: AddressUsageKind,
488 pub format_hint: AddressFormatHint,
489 pub validation_level: AddressValidationLevel,
490 pub precision: AddressPrecision,
491}
492
493impl Address {
494 #[must_use]
495 pub const fn new() -> Self {
496 Self {
497 lines: Vec::new(),
498 locality: None,
499 administrative_area: None,
500 postal_code: None,
501 country_code: None,
502 kind: AddressKind::Physical,
503 usage: AddressUsageKind::Unknown,
504 format_hint: AddressFormatHint::Unknown,
505 validation_level: AddressValidationLevel::Unvalidated,
506 precision: AddressPrecision::Unknown,
507 }
508 }
509
510 #[must_use]
511 pub fn with_line(mut self, line: AddressLine) -> Self {
512 self.lines.push(line);
513 self
514 }
515
516 #[must_use]
517 pub const fn is_empty(&self) -> bool {
518 self.lines.is_empty()
519 && self.locality.is_none()
520 && self.administrative_area.is_none()
521 && self.postal_code.is_none()
522 && self.country_code.is_none()
523 }
524
525 #[must_use]
526 pub const fn has_country(&self) -> bool {
527 self.country_code.is_some()
528 }
529
530 #[must_use]
531 pub const fn has_postal_code(&self) -> bool {
532 self.postal_code.is_some()
533 }
534
535 #[must_use]
536 pub const fn has_locality(&self) -> bool {
537 self.locality.is_some()
538 }
539
540 #[must_use]
541 pub fn component_count(&self) -> usize {
542 self.lines.len()
543 + usize::from(self.locality.is_some())
544 + usize::from(self.administrative_area.is_some())
545 + usize::from(self.postal_code.is_some())
546 + usize::from(self.country_code.is_some())
547 }
548}
549
550impl Default for Address {
551 fn default() -> Self {
552 Self::new()
553 }
554}
555
556#[cfg(test)]
557mod tests {
558 use super::{
559 Address, AddressCountryCode, AddressCountryCodeError, AddressError, AddressLine,
560 AddressLine1, AddressLine1Error, AddressLine2, AddressLine2Error, AddressLineError,
561 AdministrativeArea, AdministrativeAreaError, CountrySubdivision, CountrySubdivisionError,
562 Locality, LocalityError, PostalCode, PostalCodeError, StreetAddress, StreetName,
563 StreetNameError, StreetNumber, StreetNumberError, UnitDesignator, UnitDesignatorError,
564 UnitNumber, UnitNumberError,
565 };
566
567 macro_rules! non_empty_text_tests {
568 ($valid_name:ident, $invalid_name:ident, $type_name:ty, $error_name:ty, $input:literal, $expected:literal) => {
569 #[test]
570 fn $valid_name() -> Result<(), $error_name> {
571 let value = <$type_name>::new($input)?;
572
573 assert_eq!(value.as_str(), $expected);
574 Ok(())
575 }
576
577 #[test]
578 fn $invalid_name() {
579 assert_eq!(<$type_name>::new(" "), Err(<$error_name>::Empty));
580 }
581 };
582 }
583
584 non_empty_text_tests!(
585 address_line_accepts_trimmed_text,
586 address_line_rejects_empty_text,
587 AddressLine,
588 AddressLineError,
589 " 123 Main St ",
590 "123 Main St"
591 );
592 non_empty_text_tests!(
593 address_line1_accepts_trimmed_text,
594 address_line1_rejects_empty_text,
595 AddressLine1,
596 AddressLine1Error,
597 " 456 Oak Ave ",
598 "456 Oak Ave"
599 );
600 non_empty_text_tests!(
601 address_line2_accepts_trimmed_text,
602 address_line2_rejects_empty_text,
603 AddressLine2,
604 AddressLine2Error,
605 " Suite 9 ",
606 "Suite 9"
607 );
608 non_empty_text_tests!(
609 street_number_accepts_trimmed_text,
610 street_number_rejects_empty_text,
611 StreetNumber,
612 StreetNumberError,
613 " 221B ",
614 "221B"
615 );
616 non_empty_text_tests!(
617 street_name_accepts_trimmed_text,
618 street_name_rejects_empty_text,
619 StreetName,
620 StreetNameError,
621 " Baker Street ",
622 "Baker Street"
623 );
624 non_empty_text_tests!(
625 unit_designator_accepts_trimmed_text,
626 unit_designator_rejects_empty_text,
627 UnitDesignator,
628 UnitDesignatorError,
629 " Apt ",
630 "Apt"
631 );
632 non_empty_text_tests!(
633 unit_number_accepts_trimmed_text,
634 unit_number_rejects_empty_text,
635 UnitNumber,
636 UnitNumberError,
637 " 4B ",
638 "4B"
639 );
640 non_empty_text_tests!(
641 locality_accepts_trimmed_text,
642 locality_rejects_empty_text,
643 Locality,
644 LocalityError,
645 " London ",
646 "London"
647 );
648 non_empty_text_tests!(
649 administrative_area_accepts_trimmed_text,
650 administrative_area_rejects_empty_text,
651 AdministrativeArea,
652 AdministrativeAreaError,
653 " Indiana ",
654 "Indiana"
655 );
656 non_empty_text_tests!(
657 country_subdivision_accepts_trimmed_text,
658 country_subdivision_rejects_empty_text,
659 CountrySubdivision,
660 CountrySubdivisionError,
661 " Ontario ",
662 "Ontario"
663 );
664
665 #[test]
666 fn postal_code_accepts_numeric_zip() -> Result<(), PostalCodeError> {
667 let postal_code = PostalCode::new("46802")?;
668
669 assert_eq!(postal_code.as_str(), "46802");
670 Ok(())
671 }
672
673 #[test]
674 fn postal_code_accepts_alphanumeric_code() -> Result<(), PostalCodeError> {
675 let postal_code = PostalCode::new("SW1A 1AA")?;
676
677 assert_eq!(postal_code.as_str(), "SW1A 1AA");
678 Ok(())
679 }
680
681 #[test]
682 fn postal_code_rejects_empty_text() {
683 assert_eq!(PostalCode::new(" "), Err(PostalCodeError::Empty));
684 }
685
686 #[test]
687 fn postal_code_rejects_non_ascii_text() {
688 assert_eq!(
689 PostalCode::new("〒100-0001"),
690 Err(PostalCodeError::InvalidCharacter)
691 );
692 }
693
694 #[test]
695 fn country_code_accepts_uppercase_alpha2() -> Result<(), AddressCountryCodeError> {
696 let country_code = AddressCountryCode::new("US")?;
697
698 assert_eq!(country_code.as_str(), "US");
699 Ok(())
700 }
701
702 #[test]
703 fn country_code_normalizes_lowercase_input() -> Result<(), AddressCountryCodeError> {
704 let country_code = AddressCountryCode::new("us")?;
705
706 assert_eq!(country_code.as_str(), "US");
707 Ok(())
708 }
709
710 #[test]
711 fn country_code_rejects_invalid_length() {
712 assert_eq!(
713 AddressCountryCode::new("USA"),
714 Err(AddressCountryCodeError::InvalidLength)
715 );
716 }
717
718 #[test]
719 fn country_code_rejects_invalid_characters() {
720 assert_eq!(
721 AddressCountryCode::new("1A"),
722 Err(AddressCountryCodeError::InvalidCharacter)
723 );
724 }
725
726 #[test]
727 fn country_code_rejects_empty_text() {
728 assert_eq!(
729 AddressCountryCode::new(" "),
730 Err(AddressCountryCodeError::Empty)
731 );
732 }
733
734 #[test]
735 fn display_and_from_str_round_trip_for_address_line() -> Result<(), AddressLineError> {
736 let line = "123 Main St".parse::<AddressLine>()?;
737
738 assert_eq!(line.to_string(), "123 Main St");
739 Ok(())
740 }
741
742 #[test]
743 fn display_and_from_str_round_trip_for_country_code() -> Result<(), AddressCountryCodeError> {
744 let country_code = "us".parse::<AddressCountryCode>()?;
745
746 assert_eq!(country_code.to_string(), "US");
747 Ok(())
748 }
749
750 #[test]
751 fn try_from_supports_validated_newtypes() -> Result<(), AddressCountryCodeError> {
752 let country_code = AddressCountryCode::try_from("ca")?;
753
754 assert_eq!(country_code.as_str(), "CA");
755 Ok(())
756 }
757
758 #[test]
759 fn address_line_variants_convert_to_address_line() -> Result<(), AddressLine1Error> {
760 let line = AddressLine::from(AddressLine1::new("Apartment Lobby")?);
761
762 assert_eq!(line.as_str(), "Apartment Lobby");
763 Ok(())
764 }
765
766 #[test]
767 fn address_line2_converts_to_address_line() -> Result<(), AddressLine2Error> {
768 let line = AddressLine::from(AddressLine2::new("Floor 3")?);
769
770 assert_eq!(line.as_str(), "Floor 3");
771 Ok(())
772 }
773
774 #[test]
775 fn street_address_builder_sets_unit_fields() -> Result<(), Box<dyn std::error::Error>> {
776 let street_address =
777 StreetAddress::new(StreetNumber::new("221B")?, StreetName::new("Baker Street")?)
778 .with_unit(UnitDesignator::new("Apt")?, UnitNumber::new("4B")?);
779
780 assert_eq!(street_address.number.as_str(), "221B");
781 assert_eq!(street_address.name.as_str(), "Baker Street");
782 assert_eq!(
783 street_address
784 .unit_designator
785 .as_ref()
786 .map(UnitDesignator::as_str),
787 Some("Apt")
788 );
789 assert_eq!(
790 street_address.unit_number.as_ref().map(UnitNumber::as_str),
791 Some("4B")
792 );
793 Ok(())
794 }
795
796 #[test]
797 fn empty_address_is_empty() {
798 assert!(Address::new().is_empty());
799 }
800
801 #[test]
802 fn address_with_one_line_is_not_empty() -> Result<(), AddressLineError> {
803 let address = Address::new().with_line(AddressLine::new("123 Main St")?);
804
805 assert!(!address.is_empty());
806 Ok(())
807 }
808
809 #[test]
810 fn address_helpers_report_present_components() -> Result<(), Box<dyn std::error::Error>> {
811 let address = Address {
812 locality: Some(Locality::new("Fort Wayne")?),
813 postal_code: Some(PostalCode::new("46802")?),
814 country_code: Some(AddressCountryCode::new("us")?),
815 ..Address::new().with_line(AddressLine::new("123 Main St")?)
816 };
817
818 assert!(address.has_locality());
819 assert!(address.has_postal_code());
820 assert!(address.has_country());
821 Ok(())
822 }
823
824 #[test]
825 fn address_component_count_tracks_structural_components()
826 -> Result<(), Box<dyn std::error::Error>> {
827 let address = Address {
828 locality: Some(Locality::new("Fort Wayne")?),
829 administrative_area: Some(AdministrativeArea::new("Indiana")?),
830 postal_code: Some(PostalCode::new("46802")?),
831 country_code: Some(AddressCountryCode::new("us")?),
832 ..Address::new().with_line(AddressLine::new("123 Main St")?)
833 };
834
835 assert_eq!(address.component_count(), 5);
836 Ok(())
837 }
838
839 #[test]
840 fn address_error_wraps_component_errors() {
841 let error = AddressError::from(AddressCountryCodeError::InvalidCharacter);
842
843 assert_eq!(
844 error.to_string(),
845 "address country code must contain only ASCII alphabetic characters"
846 );
847 }
848}