package service import ( "fmt" "hy2xs-admin/model/constant" ) // Доменные отказы пиров несут КОД, а не только текст. // // Раньше причина отказа существовала исключительно в виде человеческой фразы: // `fmt.Errorf("name %s already exists", …)`. Слой контроллеров отдавал её // панели как есть, панель показывала её тостом, и всё, что могло бы захотеть // отреагировать на причину — подсветить нужное поле формы, перевести фразу на // язык оператора, — было вынуждено сравнивать текст. Такое сравнение молча // ломается при первой же правке формулировки. // // Код и имя поля объявляются здесь, рядом с местом, которое отказ порождает. // PeerError — отказ операции над пиром с машиночитаемой причиной. type PeerError struct { // Code — причина из constant.ErrCode*. Code string // Field — поле формы, к которому относится отказ; пусто, если отказ // относится к операции целиком. Field string // Message — человекочитаемое объяснение для клиента без UI. Message string } func (e *PeerError) Error() string { return e.Message } // ErrPeerNameRequired — имя пира не передано. // // Правило `required` в DTO ловит это раньше, но CreatePeer вызывается и из // тестов, и потенциально из будущих внутренних путей, поэтому проверка // остаётся, а не превращается в предположение. var ErrPeerNameRequired = &PeerError{ Code: constant.ErrCodeRequired, Field: "name", Message: "имя пира обязательно", } // ErrPeerNameReserved — имя принадлежит пиру установщика. // // Имя зарезервировано и когда сам пир уже удалён: иначе обычный пир занял бы // его и оказался под защитой, предназначенной не ему. var ErrPeerNameReserved = &PeerError{ Code: constant.ErrCodePeerNameReserved, Field: "name", Message: fmt.Sprintf( "имя %q зарезервировано за пиром установщика", ReservedBootstrapPeerName, ), } // PeerDisconnectError — долговременное состояние применено, активную сессию // завершить не удалось. // // Причина отказа `/kick` в сообщение НЕ попадает. Она приходит из слоя proxy в // виде транспортного текста («http connection error») и оператору не говорит // ничего, а диагностическая ценность у неё есть только в журнале, куда она и // пишется вызывающим. Оператору нужно другое: что именно уже сделано и что // осталось сделать. // // Отказ относится к операции целиком, а не к полю формы: поля, которое можно // было бы исправить, здесь нет. func PeerDisconnectError() *PeerError { return &PeerError{ Code: constant.ErrCodePeerDisconnectFailed, Message: "новые подключения пира запрещены, но завершить его активные " + "сессии не удалось: Traffic Stats API Hysteria недоступен. " + "Уже установленное соединение может продолжать работать до " + "переподключения клиента", } } // PeerNameTakenError — имя уже занято другим пиром. func PeerNameTakenError(name string) *PeerError { return &PeerError{ Code: constant.ErrCodePeerNameTaken, Field: "name", Message: fmt.Sprintf("пир с именем %q уже существует", name), } }