package m2m import ( "errors" "fmt" "regexp" ) // ErrInvalid — базовая ошибка валидации простого типа M2M. var ErrInvalid = errors.New("m2m: invalid value") // Скомпилированные паттерны из XSD НРД (M2MTypesNSD.xsd). var ( reReferenceID = regexp.MustCompile(`^M2M[A-Z0-9]{13}$`) reISIN = regexp.MustCompile(`^[A-Z]{2}[A-Z0-9]{9}[0-9]$`) reOrganizationINN = regexp.MustCompile(`^[0-9]{10}$`) reDeponentCode = regexp.MustCompile(`^[A-Z0-9]+$`) reUUID = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`) reSecurityCode = regexp.MustCompile(`^[0-9A-Z_/-]+$`) reIdentityDocSerial = regexp.MustCompile(`^\S+$`) ) // Validate проверяет, что значение соответствует ReferenceIDType // (M2M + 13 символов [A-Z0-9], ровно 16 символов). func (r ReferenceID) Validate() error { if len(r) != 16 { return fmt.Errorf("%w: ReferenceID длина %d, ожидается 16", ErrInvalid, len(r)) } if !reReferenceID.MatchString(string(r)) { return fmt.Errorf("%w: ReferenceID %q не соответствует ^M2M[A-Z0-9]{13}$", ErrInvalid, string(r)) } return nil } // Validate проверяет, что значение соответствует ISINtype // (2 буквы + 9 буквоцифр + цифра, ровно 12 символов). func (i ISIN) Validate() error { if len(i) != 12 { return fmt.Errorf("%w: ISIN длина %d, ожидается 12", ErrInvalid, len(i)) } if !reISIN.MatchString(string(i)) { return fmt.Errorf("%w: ISIN %q не соответствует формату", ErrInvalid, string(i)) } return nil } // Validate проверяет, что значение соответствует OrganizationINNType // (ровно 10 цифр). func (n OrganizationINN) Validate() error { if len(n) != 10 { return fmt.Errorf("%w: ИНН длина %d, ожидается 10", ErrInvalid, len(n)) } if !reOrganizationINN.MatchString(string(n)) { return fmt.Errorf("%w: ИНН %q содержит не цифры", ErrInvalid, string(n)) } return nil } // Validate проверяет, что значение соответствует DeponentCodeType // (1..12 символов из [A-Z0-9]). func (c DeponentCode) Validate() error { if len(c) == 0 || len(c) > 12 { return fmt.Errorf("%w: DeponentCode длина %d, ожидается 1..12", ErrInvalid, len(c)) } if !reDeponentCode.MatchString(string(c)) { return fmt.Errorf("%w: DeponentCode %q содержит недопустимые символы", ErrInvalid, string(c)) } return nil } // Validate проверяет, что значение соответствует UUIDType (XSD НРД): // 8-4-4-4-12 шестнадцатеричных символов, всего 36 с дефисами. Биты // версии/варианта по UUID RFC не контролируются — XSD НРД допускает // произвольный hex (в эталонах встречается "11111111-1111-..."). func (u UUID) Validate() error { if len(u) != 36 { return fmt.Errorf("%w: UUID длина %d, ожидается 36", ErrInvalid, len(u)) } if !reUUID.MatchString(string(u)) { return fmt.Errorf("%w: UUID %q не соответствует формату [hex]-[hex]-...", ErrInvalid, string(u)) } return nil } // Validate проверяет, что значение соответствует SecurityCodeType // (ровно 12 символов из [0-9A-Z_/-]). func (c SecurityCode) Validate() error { if len(c) != 12 { return fmt.Errorf("%w: SecurityCode длина %d, ожидается 12", ErrInvalid, len(c)) } if !reSecurityCode.MatchString(string(c)) { return fmt.Errorf("%w: SecurityCode %q содержит недопустимые символы", ErrInvalid, string(c)) } return nil } // Validate проверяет, что значение соответствует IdentityDocSerialType // (хотя бы 1 символ, без пробельных). func (s IdentityDocSerial) Validate() error { if len(s) == 0 { return fmt.Errorf("%w: IdentityDocSerial пустая строка", ErrInvalid) } if !reIdentityDocSerial.MatchString(string(s)) { return fmt.Errorf("%w: IdentityDocSerial %q содержит пробельные символы", ErrInvalid, string(s)) } return nil } // Validate проверяет, что значение соответствует AccountIDType // (1..50 символов). func (a AccountID) Validate() error { if len(a) == 0 || len(a) > 50 { return fmt.Errorf("%w: AccountID длина %d, ожидается 1..50", ErrInvalid, len(a)) } return nil } // Validate проверяет принадлежность к множеству {INFO, ERROR}. func (s StatusCode) Validate() error { switch s { case StatusInfo, StatusError: return nil } return fmt.Errorf("%w: StatusCode %q вне {INFO, ERROR}", ErrInvalid, string(s)) } // Validate проверяет принадлежность к множеству {T12, T03}. func (t IIAContractType) Validate() error { switch t { case IIAContractT12, IIAContractT03: return nil } return fmt.Errorf("%w: IIAContractType %q вне {T12, T03}", ErrInvalid, string(t)) } // Validate проверяет принадлежность к множеству {BOND, SHAR, MFUN}. func (s SecurityClassification) Validate() error { switch s { case SecurityBond, SecurityShar, SecurityMfun: return nil } return fmt.Errorf("%w: SecurityClassification %q вне {BOND, SHAR, MFUN}", ErrInvalid, string(s)) } // Validate проверяет принадлежность к множеству {ORDN, PREF, UKWN}. func (c SecurityCategory) Validate() error { switch c { case CategoryOrdn, CategoryPref, CategoryUkwn: return nil } return fmt.Errorf("%w: SecurityCategory %q вне {ORDN, PREF, UKWN}", ErrInvalid, string(c)) } // validIdentityDocumentCodes — справочник допустимых кодов документов // из XSD НРД (IdentityDocumentCodeEnum). var validIdentityDocumentCodes = map[IdentityDocumentCode]struct{}{ DocCode01: {}, DocCode02: {}, DocCode03: {}, DocCode04: {}, DocCode05: {}, DocCode06: {}, DocCode07: {}, DocCode09: {}, DocCode10: {}, DocCode11: {}, DocCode12: {}, DocCode13: {}, DocCode14: {}, DocCode21: {}, DocCode22: {}, DocCode23: {}, DocCode26: {}, DocCode27: {}, DocCode91: {}, } // Validate проверяет принадлежность к справочнику кодов документов НРД. func (c IdentityDocumentCode) Validate() error { if _, ok := validIdentityDocumentCodes[c]; ok { return nil } return fmt.Errorf("%w: IdentityDocumentCode %q вне справочника НРД", ErrInvalid, string(c)) } // Validate проверяет принадлежность к множеству {SGDN}. func (s IsolationStatus) Validate() error { if s == IsolationSGDN { return nil } return fmt.Errorf("%w: IsolationStatus %q вне {SGDN}", ErrInvalid, string(s)) }