приём и чтение записей сведены к одному контракту приложения
- адреса приложения переехали в своё пространство `/app/`, опрос готовности убран целиком: рубеж и причину остановки владелец узнаёт карточкой записи, текст — отдельным адресом названного вида - заведена единая точка отображения доменной ошибки и слой, приводящий к той же форме отказы библиотеки: тело несёт машиночитаемый код рядом с сообщением - у записи появились имя файла отправителя, длительность и размер своими колонками, а у ленты владельца — свой индекс: без него страница сканировала весь архив сервиса
This commit is contained in:
@@ -10,6 +10,43 @@ import (
|
||||
// него не кладётся никогда.
|
||||
var ErrOwnerRequired = errors.New("owner is required to accept a record")
|
||||
|
||||
// ErrRecordUnreadable — присланную запись не удалось прочитать: источник
|
||||
// метаданных не разобрал её содержимое. Причина отказа — сама запись, а не сбой
|
||||
// сервиса, и код ответа обязан называть причину, а не место.
|
||||
//
|
||||
// Заводится sentinel'ом, а не остаётся голой ошибкой источника метаданных:
|
||||
// ветвь по умолчанию отдала бы `500`, и «файл негоден» читалось бы как «сломался
|
||||
// сервер». Своих данных отказу нести нечего — имя файла в него не кладётся
|
||||
// никогда.
|
||||
var ErrRecordUnreadable = errors.New("uploaded record cannot be read")
|
||||
|
||||
// ErrRecordTooLarge — присланная запись длиннее потолка размера. Самый частый
|
||||
// отказ у человека на мобильной сети, и прежде он уходил телом ограничителя тела
|
||||
// — мимо единой формы отказа.
|
||||
var ErrRecordTooLarge = errors.New("uploaded record exceeds size limit")
|
||||
|
||||
// ErrTextNotReady — текста запрошенного вида у записи ещё нет. Состояние, а не
|
||||
// отсутствие: запись есть и принадлежит спрашивающему, просто конвейер до этого
|
||||
// вида не дошёл. Отвечать на это тем же, чем отвечает чужая запись, нельзя —
|
||||
// человек увидел бы «не найдено» на своей записи, загруженной минуту назад.
|
||||
var ErrTextNotReady = errors.New("requested text view is not ready yet")
|
||||
|
||||
// ErrBadRequest — во входе запроса негодное значение: неизвестный вид текста,
|
||||
// нечитаемый ключ страницы, отрицательный размер. Отличается от ErrRecordUnreadable
|
||||
// тем, что негодна **просьба**, а не присланная запись.
|
||||
var ErrBadRequest = errors.New("request input is not valid")
|
||||
|
||||
// ErrUnauthorized — сессии нет вовсе. Первая строка таблицы отображения, и без
|
||||
// собственного признака она собиралась бы руками мимо единой точки: правка формы
|
||||
// тела не доехала бы до неё, и два места разошлись бы молча.
|
||||
var ErrUnauthorized = errors.New("session is required")
|
||||
|
||||
// ErrNotFound — под корнем приложения такого адреса нет. Отличается от
|
||||
// JobNotFoundError тем, что не найдена **просьба**, а не запись: тело у ответа
|
||||
// то же, но повод другой, и смешивать их в одном признаке значило бы называть
|
||||
// отсутствующий адрес отсутствующей записью.
|
||||
var ErrNotFound = errors.New("address not found")
|
||||
|
||||
type JobNotFoundError struct {
|
||||
State string
|
||||
Message string
|
||||
|
||||
@@ -71,8 +71,41 @@ type AcquiredRecord struct {
|
||||
Holder string
|
||||
}
|
||||
|
||||
// RecordCursor — положение в ленте записей, заданное **полным** ключом
|
||||
// сортировки. Одного времени мало: у записей, принятых одним запросом, оно
|
||||
// совпадает, и порядок между ними иначе не определён.
|
||||
type RecordCursor struct {
|
||||
CreatedAt string
|
||||
ID string
|
||||
}
|
||||
|
||||
// RecordQuery — что спрашивают у ленты записей.
|
||||
type RecordQuery struct {
|
||||
// OwnerID обязателен: пустой не совпадает ни с одной записью.
|
||||
OwnerID string
|
||||
// Filter — состояние записи. Пустой значит «все».
|
||||
Filter *entity.ListFilter
|
||||
// Cursor — положение, с которого продолжать. Пустой значит «сначала».
|
||||
Cursor *RecordCursor
|
||||
Limit int
|
||||
}
|
||||
|
||||
// RecordPage — страница ленты. Ключ следующей страницы пуст, когда страница
|
||||
// последняя.
|
||||
type RecordPage struct {
|
||||
Items []*entity.AudioRecord
|
||||
NextCursor *RecordCursor
|
||||
TotalItems int
|
||||
}
|
||||
|
||||
type AudioRecordRepository interface {
|
||||
Create(record *entity.AudioRecord) error
|
||||
// List отдаёт страницу записей владельца, новыми сверху, не читая ни
|
||||
// расшифровки, ни структуры реплик.
|
||||
List(q RecordQuery) (*RecordPage, error)
|
||||
// ResolveTopicNames разрешает темы названиями одним запросом на страницу и
|
||||
// сужает их владельцем: словарь тем свой у каждого человека.
|
||||
ResolveTopicNames(ownerID string, ids []string) (map[string]string, error)
|
||||
// Save сохраняет запись, захват которой держит holder. Захват, доставшийся
|
||||
// за время работы другому, даёт LostAcquisitionError и запись не проводит.
|
||||
// Пустой holder снимает эту условность и в конвейере не употребляется: все
|
||||
|
||||
Reference in New Issue
Block a user