Files
Bridge-and-Join-s/internal/m2m/validators.go
T
zuevav da12a9ec1c feat(bj): модуль ЛК на PostgreSQL, обмен с DEPO, боевой ЛК с имитатором НРД, QUIK, НПР
- bj_exchange и «кран» Fancy убраны: данные модуля ЛК в PostgreSQL (схема lk)
- трансферные таблицы FTV_M2M: создание при установке, события, справочник участников
- сверка с DEPO: счёт, ФИО, дата рождения, ИИС по договору, квал-инвестор из опции DEPO
- боевой ЛК ЕСИА Финанс при тестовом ИШ: журнал, отказ через DEPO, статусы в ЛК
- онлайн-лицензирование в bj, план тестирования M2M для контрагента (интерактивный HTML)
- модули QUIK МНП, экспорт QUIK, НПР/УДС; документы и тестовые материалы

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 19:41:12 +03:00

180 lines
7.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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: символы из
// [0-9A-Z_/-], длина 1..12. Код НРД бумаги НЕ обязательно 12 символов и НЕ равен
// ISIN — напр. официальный тест-код Сбербанка на TEST3 = "SBER/03" (7 симв.,
// ответ НРД от 15.08.2026). Раньше требовали ровно 12 → падало на коротких кодах.
func (c SecurityCode) Validate() error {
if n := len(c); n < 1 || n > 12 {
return fmt.Errorf("%w: SecurityCode длина %d, ожидается 1..12", ErrInvalid, n)
}
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))
}