package nsdadapter import ( "context" "errors" "fmt" "time" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter/igw" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdxml" ) // IGWClient — узкий интерфейс из igw.Client, нужный Sender'у. Введён // ради тестируемости (mock в тестах) и независимости от ИШ-реализации. type IGWClient interface { SendPackage(ctx context.Context, channel, packageType string, body []byte) (string, error) ListIncoming(ctx context.Context, channel string, since time.Time, packageType string) ([]igw.Package, error) } // Sender — реализация m2mcore.NSDSender поверх REST ИШ НРД. type Sender struct { profile Profile client IGWClient } // NewSender собирает Sender для указанного профиля. func NewSender(profile Profile, client IGWClient) *Sender { return &Sender{profile: profile, client: client} } // Send сериализует M2MTransferRequest в windows-1251, маршрутизирует к // типу пакета #M2MTR и отправляет в ИШ. M2MTransferResponse в этом // канале возвращается асинхронно через ListIncoming, поэтому Send // возвращает nil-response — реальный ответ забирает поллер. func (s *Sender) Send(ctx context.Context, req *m2m.M2MTransferRequest) (*m2m.M2MTransferResponse, error) { if req == nil { return nil, errors.New("nsdadapter: Send: req=nil") } if err := req.Validate(); err != nil { return nil, fmt.Errorf("nsdadapter: req.Validate: %w", err) } pkgType, err := RouteToPackageType(KindTransferRequest) if err != nil { return nil, err } // ИШ ждёт ZIP-архив (Type=ARCHIVE): doc.xml + config.xml с типом пакета. // Сырой XML ИШ отвергает ("Bad signature" при распаковке). PackRequest // собирает корректный ZIP с config.xml (#M2MTR). body, err := igw.PackRequest(req, "M2MTransferRequest.xml") if err != nil { return nil, fmt.Errorf("nsdadapter: PackRequest: %w", err) } // ИШ резолвит канал по СОСТАВНОМУ коду: <код канала>+<код депонента> // (так его формирует ИШ при создании канала: напр. TEST3+MC0413600000). channel := s.profile.Channel + string(req.Header.SenderCode) pkgID, err := s.client.SendPackage(ctx, channel, string(pkgType), body) if err != nil { return nil, fmt.Errorf("nsdadapter: SendPackage: %w", err) } // Возвращаем псевдо-Response с GUID-ом для трассировки. Реальный // M2MTransferResponse от НРД придёт через входящие пакеты, его // обработает поллер cmd/nsd-adapter. _ = pkgID return nil, nil } // SendDecision сериализует и отправляет M2MTransferDecision. func (s *Sender) SendDecision(ctx context.Context, decision *m2m.M2MTransferDecision) error { if decision == nil { return errors.New("nsdadapter: SendDecision: decision=nil") } if err := decision.Validate(); err != nil { return fmt.Errorf("nsdadapter: decision.Validate: %w", err) } xmlBytes, err := nsdxml.Marshal(decision) if err != nil { return fmt.Errorf("nsdadapter: marshal Decision: %w", err) } pkgType, err := RouteToPackageType(KindTransferDecision) if err != nil { return err } body, err := igw.PackXML(xmlBytes, "M2MTransferDecision.xml", string(pkgType)) if err != nil { return fmt.Errorf("nsdadapter: PackXML: %w", err) } channel := s.profile.Channel + string(decision.Header.SenderCode) if _, err := s.client.SendPackage(ctx, channel, string(pkgType), body); err != nil { return fmt.Errorf("nsdadapter: SendPackage: %w", err) } return nil }