httpapi: точки метрики за период отдаются одним запросом
- `GET /api/v1/metrics/{name}?from&to&layer` — ряд точек за период; конверт
объявляет слой, измеренный род, его применимость к отданному ряду и границу
окна измерения, а сам ряд собирается из одного слоя, выбранного по охвату
точек внутри периода
- use-case вынесен в `internal/points`, чтение — одним входом `store.ReadSeries`
под одной транзакцией; правило выбора слоя остаётся в домене и приходит в
хранилище колбэком
- `writeJSON` перестал экранировать HTML-символы и перестал глушить отказ
записи: дословность содержимого точки иначе не удерживается, а оборванное
тело уходило под видом успешного `200`
This commit is contained in:
@@ -0,0 +1,442 @@
|
||||
# points Specification
|
||||
|
||||
## Purpose
|
||||
|
||||
Ряд точек одной метрики за период — то, ради чего собирается витрина. Каталог
|
||||
отвечает «что у тебя есть», этот маршрут — «дай значения». Форма запроса и его
|
||||
разбор, правило выбора слоя, состав конверта и объявление измеренного рода
|
||||
вместе с границей окна измерения живут здесь; общие правила читающих маршрутов —
|
||||
в capability `read-api`.
|
||||
|
||||
## Requirements
|
||||
### Requirement: Точки метрики за период отдаются одним запросом
|
||||
|
||||
Система SHALL отдавать значения одной метрики за запрошенный период по
|
||||
`GET /api/v1/metrics/{name}` с обязательными параметрами `from` и `to`, не
|
||||
требуя от потребителя доступа к файлу базы и не требуя второго запроса за
|
||||
смыслом отданных чисел.
|
||||
|
||||
Имя метрики берётся из пути **ровно один раз декодированным** и далее
|
||||
дословно: система его не нормализует и не сверяет со списком известных — имена
|
||||
приходят из тела доставки и хранилищу не принадлежат. Повторное декодирование
|
||||
MUST NOT происходить: имя, само содержащее процентную последовательность
|
||||
(`a%41b`), после второго декодирования становится именем **другой** метрики, и
|
||||
маршрут отвечает `200` с её данными. Имя, которое путём не
|
||||
выражается (пустое), маршрутом недостижимо; каталог такую метрику показывает, и
|
||||
это названная цена адресации именем в пути, а не молчание.
|
||||
|
||||
Маршрут закрыт токеном чтения тем же контуром, что и каталог, и MUST NOT
|
||||
перехватывать маршрут каталога `GET /api/v1/metrics`.
|
||||
|
||||
#### Scenario: Период запрошен
|
||||
|
||||
- **GIVEN** метрика, у которой в витрине есть объекты внутри периода
|
||||
- **WHEN** потребитель запрашивает `GET /api/v1/metrics/{name}?from=…&to=…` с
|
||||
действующим токеном чтения
|
||||
- **THEN** ответ `200` несёт значения этой метрики за этот период
|
||||
|
||||
#### Scenario: Каталог остаётся достижим
|
||||
|
||||
- **WHEN** потребитель запрашивает `GET /api/v1/metrics`
|
||||
- **THEN** отвечает каталог, а не маршрут точек
|
||||
|
||||
#### Scenario: Имя метрики закодировано в пути
|
||||
|
||||
- **GIVEN** метрика, чьё имя содержит символ, требующий процентного кодирования
|
||||
- **WHEN** потребитель запрашивает её точки, закодировав имя
|
||||
- **THEN** отвечают точки этой метрики, а не пустой ряд
|
||||
|
||||
#### Scenario: Имя метрики само содержит процентную последовательность
|
||||
|
||||
- **GIVEN** метрики с именами `a%41b` и `aAb` в витрине
|
||||
- **WHEN** потребитель запрашивает `a%41b`, закодировав имя
|
||||
- **THEN** отвечают точки `a%41b`, а не точки `aAb`
|
||||
|
||||
#### Scenario: Токен чтения отсутствует
|
||||
|
||||
- **WHEN** запрос точек приходит без действующего токена чтения при непустом
|
||||
списке токенов чтения
|
||||
- **THEN** ответ `401`, и тело ответа собрано объявленным типом транспорта
|
||||
|
||||
### Requirement: Конверт ответа объявляет слой, род свёртки, его применимость и границу окна измерения
|
||||
|
||||
Система SHALL сопровождать точки конвертом, в котором ВСЕГДА присутствуют поля
|
||||
`metric`, `from`, `to`, `layer`, `bucket`, `aggregation` и `points`, а объект
|
||||
`aggregation` MUST всегда нести поля `style`, `applicable` и `last_hour`. Поле,
|
||||
которому нечего сообщить, MUST уезжать как `null` и MUST NOT исчезать из ответа
|
||||
и MUST NOT подменяться нулевым значением своего типа.
|
||||
|
||||
Смысл полей:
|
||||
|
||||
- `metric` — имя метрики, как оно пришло путём после декодирования;
|
||||
- `from` и `to` — фактически применённые границы периода, нормализованные к UTC
|
||||
в RFC 3339;
|
||||
- `layer` — слой, из которого собран ряд;
|
||||
- `bucket` — сетка свёртки; `null`, когда свёртки не было;
|
||||
- `aggregation.style` — измеренный род метрики (`cumulative` / `instant` /
|
||||
`unknown`) тем же правилом и тем же окном, что у каталога;
|
||||
- `aggregation.applicable` — применим ли объявленный род к **отданному ряду**;
|
||||
- `aggregation.last_hour` — ярлык самого свежего часа окна измерения; `null`,
|
||||
когда окно пусто;
|
||||
- `points` — ряд, пустой коллекцией `[]`, а не `null`.
|
||||
|
||||
`last_hour` обязателен именно потому, что окно измерения считается в **общих
|
||||
часах**, а не в часах календаря: выключенная минутная автоматизация HAE
|
||||
останавливает пополнение общих часов, окно замирает и продолжает объявлять род.
|
||||
Без `last_hour` у клиента нет ни одного способа это увидеть.
|
||||
|
||||
#### Scenario: Свёртки не было
|
||||
|
||||
- **WHEN** маршрут отвечает на запрос без сетки
|
||||
- **THEN** в ответе присутствуют `metric`, `from`, `to`, `layer`, `bucket`,
|
||||
`aggregation` со всеми тремя полями и `points`, причём `bucket` равен `null`
|
||||
|
||||
#### Scenario: Окно измерения замерло
|
||||
|
||||
- **GIVEN** метрика, чьи самые свежие общие часы старше конца запрошенного
|
||||
периода
|
||||
- **WHEN** маршрут отвечает
|
||||
- **THEN** `aggregation.last_hour` называет ярлык самого свежего общего часа, а
|
||||
не конец периода и не текущее время
|
||||
|
||||
#### Scenario: Окна измерения нет вовсе
|
||||
|
||||
- **GIVEN** метрика, у которой нет ни одного общего часа минутного и часового
|
||||
слоёв
|
||||
- **WHEN** маршрут отвечает
|
||||
- **THEN** `aggregation.style` равен `"unknown"`, а `aggregation.last_hour`
|
||||
равен `null`
|
||||
|
||||
### Requirement: Род, объявленный в ответе, едет вместе со своей применимостью к отданному ряду
|
||||
|
||||
Система SHALL объявлять `aggregation.applicable` равным `false` всегда, когда
|
||||
объявленный род нельзя применить к отданному ряду, и MUST считать неприменимым
|
||||
род `cumulative` на **нижнем слое HAE** (`raw`), род `unknown` и любой род при
|
||||
отсутствии выбранного слоя. Система MUST NOT досчитывать свёртку по
|
||||
неизмеренному роду и MUST NOT выражать неизвестность отсутствием поля.
|
||||
|
||||
Поле существует потому, что род — свойство **метрики**, а слой — свойство
|
||||
**отданного ряда**, и их сочетание бывает опасным: нижний слой HAE это
|
||||
интерполяция, а не сэмплы, и сумма по нему завышает втрое. Конверт, объявляющий
|
||||
`cumulative` рядом с рядом из `raw` и молчащий о неприменимости, приглашает
|
||||
потребителя сложить интерполяцию самостоятельно — система при этом не
|
||||
складывает ничего, а решение у потребителя уже принято по завышенному числу.
|
||||
|
||||
#### Scenario: Род метрики не измерен
|
||||
|
||||
- **GIVEN** метрика, у которой род агрегации не измерен
|
||||
- **WHEN** потребитель запрашивает её точки за период
|
||||
- **THEN** ответ `200` несёт точки как есть, `aggregation.style` равен
|
||||
`"unknown"`, а `aggregation.applicable` равен `false`
|
||||
|
||||
#### Scenario: Накопительная метрика отдана нижним слоем
|
||||
|
||||
- **GIVEN** метрика с измеренным родом `cumulative`, ряд которой собран из слоя
|
||||
`raw`
|
||||
- **WHEN** маршрут отвечает
|
||||
- **THEN** `aggregation.style` равен `"cumulative"`, а
|
||||
`aggregation.applicable` равен `false`
|
||||
|
||||
#### Scenario: Род применим
|
||||
|
||||
- **GIVEN** метрика с измеренным родом, ряд которой собран из слоя `minute`,
|
||||
`hour` или `sample`
|
||||
- **WHEN** маршрут отвечает
|
||||
- **THEN** `aggregation.applicable` равен `true`
|
||||
|
||||
### Requirement: Слой выбирается по охвату точек внутри периода и не меняется внутри ответа
|
||||
|
||||
Система SHALL собирать ряд ровно из одного слоя и MUST NOT склеивать в одном
|
||||
ответе точки разных слоёв. При отсутствии параметра `layer` система SHALL брать
|
||||
слой с наибольшим **охватом** внутри запрошенного периода, а при равном охвате —
|
||||
самый мелкий слой в порядке `sample`, `raw`, `minute`, `hour`, `day`.
|
||||
|
||||
**Охват меряется метками точек, а не часами объектов.** Охват слоя есть длина
|
||||
пересечения отрезка `[первая метка слоя, последняя метка слоя]` с запрошенным
|
||||
периодом; слой с пустым пересечением из выбора MUST выбывать. Мера названа
|
||||
именно так потому, что объекты адресуются часом, а ряд отбирается точной меткой:
|
||||
на периоде короче часа множество «слоёв с объектами» и множество «слоёв с
|
||||
точками» расходятся, и слой, выбранный по часам, отдал бы пустой ряд при
|
||||
непустых данных соседнего слоя.
|
||||
|
||||
Охват сравнивается между слоями, а не с запрошенным периодом: границы данных
|
||||
законно короче запроса и законно имеют дыры внутри.
|
||||
|
||||
Заданный параметр `layer` отменяет правило целиком: система SHALL отдавать
|
||||
запрошенный разрез, в том числе пустым, и SHALL называть его в ответе.
|
||||
|
||||
#### Scenario: Мелкий слой охватывает меньше крупного
|
||||
|
||||
- **GIVEN** метрика, у которой внутри периода нижний слой покрывает несколько
|
||||
дней, а часовой — весь период
|
||||
- **WHEN** потребитель запрашивает период без параметра `layer`
|
||||
- **THEN** ряд собран из часового слоя, и `layer` называет его
|
||||
|
||||
#### Scenario: Охваты равны
|
||||
|
||||
- **GIVEN** метрика, у которой два слоя охватывают внутри периода одно и то же
|
||||
- **WHEN** потребитель запрашивает период без параметра `layer`
|
||||
- **THEN** ряд собран из более мелкого слоя
|
||||
|
||||
#### Scenario: Период короче часа
|
||||
|
||||
- **GIVEN** период внутри одного часа, в котором у крупного слоя есть объект без
|
||||
единой точки внутри периода, а у мелкого — точки внутри периода
|
||||
- **WHEN** потребитель запрашивает период без параметра `layer`
|
||||
- **THEN** ряд собран из мелкого слоя и не пуст
|
||||
|
||||
#### Scenario: Слой задан явно
|
||||
|
||||
- **WHEN** потребитель задаёт `layer` явно
|
||||
- **THEN** ряд собран из этого слоя, даже если другой слой охватывает период
|
||||
шире, и `layer` в ответе равен запрошенному
|
||||
|
||||
#### Scenario: Имя слоя незнакомо
|
||||
|
||||
- **WHEN** параметр `layer` присутствует, а его значение не является одним из
|
||||
`sample`, `raw`, `minute`, `hour`, `day`
|
||||
- **THEN** ответ `400`, и умолчание молча не подставляется
|
||||
|
||||
#### Scenario: Значение слоя пусто
|
||||
|
||||
- **WHEN** параметр `layer` присутствует с пустым значением
|
||||
- **THEN** ответ `400`, а не автоматический выбор слоя
|
||||
|
||||
### Requirement: Период задаётся явно и разбирается строго
|
||||
|
||||
Система SHALL требовать оба параметра `from` и `to`, SHALL принимать их только в
|
||||
формате RFC 3339 с явным смещением зоны и SHALL толковать период как
|
||||
полуинтервал `[from, to)`. Отсутствующий параметр, неразбираемое значение,
|
||||
значение без явной зоны, `from >= to` и граница, чей год после приведения к UTC
|
||||
выходит за диапазон 1–9999, MUST давать `400` с человекочитаемым сообщением,
|
||||
которое MUST NOT содержать значений из запроса, и MUST NOT подменяться
|
||||
умолчанием.
|
||||
|
||||
Граница за пределами четырёхзначного года отвергается потому, что объекты
|
||||
адресуются строкой RFC 3339 и границы сравниваются лексикографически:
|
||||
`9999-12-31T23:00:00-07:00` становится `10000-01-01T06:00:00Z`, который как
|
||||
строка меньше любой настоящей метки, — и запрос молча отдал бы пустой ряд при
|
||||
непустых данных.
|
||||
|
||||
Полуинтервал взят потому, что соседние окна обязаны склеиваться без двойного
|
||||
счёта граничной точки. Явная зона обязательна потому, что вопрос «в какой зоне
|
||||
считать сутки» в проекте открыт: принять голую дату значило бы выбрать зону за
|
||||
клиента молча.
|
||||
|
||||
Параметр `bucket` этой версией маршрута не поддержан: система SHALL отвечать
|
||||
`400` на его присутствие с любым значением и MUST NOT игнорировать его молча.
|
||||
Молчаливое игнорирование дало бы клиенту, попросившему суточную сетку, полный
|
||||
минутный ряд — зеркало того самого промаха, ради которого соседняя задача
|
||||
различает «указали сетку» как информацию и как защиту. Прочие незнакомые
|
||||
параметры запроса система игнорирует.
|
||||
|
||||
#### Scenario: Параметр периода отсутствует
|
||||
|
||||
- **WHEN** в запросе нет `from` или нет `to`
|
||||
- **THEN** ответ `400`, и период умолчанием не подставляется
|
||||
|
||||
#### Scenario: Метка времени без зоны
|
||||
|
||||
- **WHEN** значение `from` или `to` записано без явного смещения зоны
|
||||
- **THEN** ответ `400`
|
||||
|
||||
#### Scenario: Границы периода вывернуты
|
||||
|
||||
- **WHEN** `from` не раньше `to`
|
||||
- **THEN** ответ `400`
|
||||
|
||||
#### Scenario: Граница выходит за четырёхзначный год
|
||||
|
||||
- **WHEN** граница периода после приведения к UTC попадает в год за пределами
|
||||
диапазона 1–9999
|
||||
- **THEN** ответ `400`, а не `200` с пустым рядом
|
||||
|
||||
#### Scenario: Запрошена сетка свёртки
|
||||
|
||||
- **WHEN** в запросе присутствует параметр `bucket`
|
||||
- **THEN** ответ `400`, называющий, что свёртка ещё не поддержана
|
||||
|
||||
#### Scenario: Точка стоит ровно на границе
|
||||
|
||||
- **GIVEN** точки с метками ровно в `from` и ровно в `to`
|
||||
- **WHEN** маршрут отвечает
|
||||
- **THEN** точка на `from` в ответе есть, а точка на `to` — нет
|
||||
|
||||
### Requirement: Значение точки уезжает дословно, нормализовано только время
|
||||
|
||||
Система SHALL отдавать каждую точку объектом `{ts, ts_end, tz_offset, units,
|
||||
values}`, где `values` MUST быть содержимым точки ровно в том виде, в каком его
|
||||
сохранило хранилище, без переименования полей, пересчёта единиц, отбрасывания
|
||||
незнакомого и **без экранирования**. `ts` и `ts_end` MUST быть нормализованными
|
||||
к UTC метками начала и конца координаты точки; у точки-измерения `ts_end` равен
|
||||
`ts`. `tz_offset` и `units` MUST принадлежать самой точке: смещение исходной
|
||||
зоны — её собственное, единицы — того объекта, из которого точка прочитана.
|
||||
|
||||
Точки MUST идти по возрастанию `(ts, ts_end)`. Порядок этим определён
|
||||
однозначно: пара `(начало, конец)` внутри одного слоя одной метрики есть ключ
|
||||
идентичности, и двух точек с равной парой в витрине не существует.
|
||||
|
||||
Принадлежность точки периоду определяется её **началом**: точка-интервал,
|
||||
начавшаяся раньше `from`, в ответ не входит, даже если её конец лежит внутри
|
||||
периода. Правило то же, каким час объекта берётся по началу точки; цена названа
|
||||
вслух — запрос «сон за ночь с полуночи» не увидит эпизод, начавшийся до неё.
|
||||
|
||||
`ts_end` присутствует потому, что идентичность точки — интервал, а не метка: под
|
||||
одной меткой лежит до трёх записей сна, и конверт с одним `ts` предлагал бы
|
||||
клиенту различать их, разбирая дословное содержимое.
|
||||
|
||||
#### Scenario: Точка-интервал
|
||||
|
||||
- **GIVEN** точка, у которой конец координаты отличается от начала
|
||||
- **WHEN** маршрут отвечает
|
||||
- **THEN** `ts_end` отличается от `ts` и называет конец координаты
|
||||
|
||||
#### Scenario: Содержимое не переписывается
|
||||
|
||||
- **WHEN** точка уезжает клиенту
|
||||
- **THEN** `values` побайтово совпадает с сохранённым содержимым точки
|
||||
|
||||
#### Scenario: Содержимое несёт символы, которые сериализатор склонен экранировать
|
||||
|
||||
- **GIVEN** точка, чьё содержимое несёт `&`, `<` или `>`
|
||||
- **WHEN** маршрут отвечает
|
||||
- **THEN** эти символы уезжают как есть, а не escape-последовательностями
|
||||
|
||||
### Requirement: Пустота периода — успех, а не отсутствие ресурса
|
||||
|
||||
Система SHALL отвечать `200` на запрос метрики, у которой нет данных в
|
||||
запрошенном периоде, и MUST отдавать `points` пустым списком. Система MUST NOT
|
||||
отвечать `404` по признаку «нет данных».
|
||||
|
||||
`layer` равен `null` **только** тогда, когда слой выбирала система и выбирать
|
||||
было не из чего. Заданный клиентом `layer` уезжает в ответе всегда, даже когда
|
||||
ряд пуст: иначе клиент, спросивший разрез поимённо, не отличил бы «этого разреза
|
||||
за период нет» от «маршрут проигнорировал параметр».
|
||||
|
||||
Различать опечатку в имени метрики и честную пустоту — работа каталога: список
|
||||
имён маршруту точек не принадлежит.
|
||||
|
||||
#### Scenario: Данных за период нет и слой не задан
|
||||
|
||||
- **WHEN** у метрики нет ни одной точки внутри периода и параметр `layer` не
|
||||
задан
|
||||
- **THEN** ответ `200`, `points` равен `[]`, `layer` равен `null`
|
||||
|
||||
#### Scenario: Данных за период нет, а слой задан
|
||||
|
||||
- **WHEN** у метрики нет точек запрошенного слоя внутри периода
|
||||
- **THEN** ответ `200`, `points` равен `[]`, `layer` равен запрошенному
|
||||
|
||||
#### Scenario: Имени метрики в витрине нет вовсе
|
||||
|
||||
- **WHEN** запрошено имя метрики, которого в витрине нет
|
||||
- **THEN** ответ `200` с пустым `points`, а не `404`
|
||||
|
||||
### Requirement: Ответ снимается одним снимком витрины
|
||||
|
||||
Система SHALL читать охваты слоёв, точки выбранного слоя и объекты окна
|
||||
измерения **в одной транзакции чтения**. Смесь «слой выбран до коммита свёртки,
|
||||
точки прочитаны после» и «род измерен на третьем состоянии» дала бы ответ,
|
||||
внутренне противоречивый и неотличимый от обычного свежего; пара проб версии
|
||||
такой ответ обнаруживает, но не предотвращает — она снимает метку, а тело всё
|
||||
равно уезжает.
|
||||
|
||||
#### Scenario: Свёртка коммитит во время чтения
|
||||
|
||||
- **WHEN** фоновая свёртка коммитит в витрину между выбором слоя и чтением точек
|
||||
- **THEN** ответ собран из одного снимка витрины, а не из двух состояний
|
||||
|
||||
### Requirement: Метка ответа включает канонизированную форму запроса и горизонт измерения
|
||||
|
||||
Система SHALL помечать ответ меткой, область действия которой MUST быть
|
||||
функцией канонизированной формы запроса — имени метрики, границ периода и слоя —
|
||||
и которая MUST включать **горизонт измерения**, огрублённый до часа, наравне с
|
||||
версией витрины. При невозможности подписать ответ система SHALL отдавать его
|
||||
без метки, а не отказом.
|
||||
|
||||
Область MUST быть **ограничена по длине и не выносить значения запроса наружу**.
|
||||
Имя метрики приходит из чужого тела дословно и ничем не ограничено: положенное
|
||||
в метку как есть, оно даёт заголовок в тысячи байт, а кавычка внутри имени по
|
||||
HTTP кончает метку — разбор обрежет её там, и условный запрос по такой метрике
|
||||
не сработает никогда. Тот же вход в лог уезжает обрезанным, и у метки предел
|
||||
обязан быть по той же причине.
|
||||
|
||||
Канонизация границ MUST сохранять точность, по которой отбираются точки: два
|
||||
запроса, различающиеся долей секунды, дают разные ряды, и одна метка на них
|
||||
подтвердила бы неизменность чужого набора данных.
|
||||
|
||||
Горизонт входит в метку по той же причине, по какой он входит в метку каталога:
|
||||
род объявляется по окну, ограниченному сверху `текущее время + запас`, и метка
|
||||
из будущего, лежащая в витрине, въезжает в окно **без единого коммита**, меняя
|
||||
`aggregation.style` и `last_hour`. Метка без горизонта подтвердила бы
|
||||
неизменность ответа, в котором род уже перевернулся. Форма запроса входит в
|
||||
область действия потому, что ответ этого маршрута есть функция параметров: метка
|
||||
без них однажды подтвердила бы неизменность чужого набора данных. Род и метка
|
||||
MUST сниматься с одного горизонта.
|
||||
|
||||
Цена названа: один полный ответ в час на потребителя при неизменившейся витрине.
|
||||
|
||||
#### Scenario: Запросы различаются параметром
|
||||
|
||||
- **WHEN** два запроса при одном состоянии витрины различаются метрикой,
|
||||
границей периода или слоем
|
||||
- **THEN** метки их ответов различны
|
||||
|
||||
#### Scenario: Запросы различаются только формой записи
|
||||
|
||||
- **WHEN** два запроса при одном состоянии витрины задают одно и то же разной
|
||||
записью смещения зоны
|
||||
- **THEN** метки их ответов совпадают
|
||||
|
||||
#### Scenario: Границы различаются долей секунды
|
||||
|
||||
- **WHEN** два запроса при одном состоянии витрины различаются границей периода
|
||||
на долю секунды и дают разные ряды
|
||||
- **THEN** метки их ответов различны
|
||||
|
||||
#### Scenario: Имя метрики длинное или содержит кавычку
|
||||
|
||||
- **WHEN** запрошена метрика, чьё имя длиной в тысячи символов или содержит
|
||||
кавычку
|
||||
- **THEN** метка ответа остаётся короткой и не содержит имени метрики
|
||||
|
||||
#### Scenario: Горизонт перешёл через час
|
||||
|
||||
- **WHEN** горизонт измерения перешёл через границу часа при неизменной версии
|
||||
витрины
|
||||
- **THEN** метка ответа изменилась
|
||||
|
||||
#### Scenario: Витрина изменилась во время чтения
|
||||
|
||||
- **WHEN** пробы версии витрины разошлись
|
||||
- **THEN** ответ уходит целиком и без метки
|
||||
|
||||
### Requirement: Исход маршрута точек наблюдаем, а данные о здоровье в лог не уезжают
|
||||
|
||||
Система SHALL писать один логирующий чекпоинт исхода на доменной границе
|
||||
маршрута и MUST NOT доводить до записи лога значения точек, содержимое `values`
|
||||
и границы запроса; имя метрики MUST быть обрезано по тому же пределу, что у
|
||||
каталога. Отмена запроса клиентом и занятость базы MUST писаться уровнем
|
||||
`DEBUG`, а настоящий отказ хранилища — уровнем `ERROR`.
|
||||
|
||||
Предупреждения измерения — противоречащий род и данные, помеченные будущим, —
|
||||
остаются привилегией каталога: маршрут точек их MUST NOT повторять. Агент
|
||||
опрашивает по расписанию, и `WARN` на каждый опрос обесценил бы уровень ровно
|
||||
так же, как обесценила бы его строка на каждый `304`.
|
||||
|
||||
#### Scenario: Клиент оборвал запрос
|
||||
|
||||
- **WHEN** потребитель обрывает запрос точек по своему тайм-ауту
|
||||
- **THEN** запись об этом уходит уровнем `DEBUG`, а не `ERROR`
|
||||
|
||||
#### Scenario: Метрика с противоречащим родом запрошена многократно
|
||||
|
||||
- **WHEN** потребитель повторно запрашивает точки метрики, у которой род
|
||||
противоречив
|
||||
- **THEN** маршрут точек предупреждений об этом не пишет
|
||||
|
||||
#### Scenario: Тело ответа оборвалось на середине
|
||||
|
||||
- **WHEN** запись тела ответа отказала после того, как код ответа уже отдан
|
||||
- **THEN** отказ записи оставляет собственный чекпоинт и не проходит молча под
|
||||
видом успешного ответа
|
||||
Reference in New Issue
Block a user