httpapi: точки метрики за период отдаются одним запросом

- `GET /api/v1/metrics/{name}?from&to&layer` — ряд точек за период; конверт
  объявляет слой, измеренный род, его применимость к отданному ряду и границу
  окна измерения, а сам ряд собирается из одного слоя, выбранного по охвату
  точек внутри периода
- use-case вынесен в `internal/points`, чтение — одним входом `store.ReadSeries`
  под одной транзакцией; правило выбора слоя остаётся в домене и приходит в
  хранилище колбэком
- `writeJSON` перестал экранировать HTML-символы и перестал глушить отказ
  записи: дословность содержимого точки иначе не удерживается, а оборванное
  тело уходило под видом успешного `200`
This commit is contained in:
av
2026-08-04 18:46:45 +03:00
parent b819b77f62
commit 29ca8d415c
36 changed files with 4721 additions and 58 deletions
+26 -15
View File
@@ -856,25 +856,36 @@ func headerLayer(aggregation string) Layer {
// finer возвращает более мелкий из двух слоёв.
func finer(a, b Layer) Layer {
if rank(a) < rank(b) {
if Rank(a) < Rank(b) {
return a
}
return b
}
func rank(l Layer) int {
switch l {
case LayerSample:
return 0
case LayerRaw:
return 1
case LayerMinute:
return 2
case LayerHour:
return 3
case LayerDay:
return 4
default:
return 5
// Layers — ЕДИНСТВЕННЫЙ словарь слоёв, упорядоченный от самого мелкого к самому
// крупному.
//
// Один, потому что иначе их становится четыре: порядок для разбора, перечень
// для выборки охватов, проверка параметра запроса и текст отказа клиенту. Ни
// компилятор, ни тест их не сверяют — новая константа `Layer` скомпилировалась
// бы, получила бы ранг «крупнее всех», не попала бы в выборку охватов и
// отвергалась бы маршрутом как незнакомая. Симптомом был бы пустой ряд при
// непустых данных. Случай не гипотетический: слой `sample` наполнится импортом
// родного экспорта Apple.
//
// Срез, а не карта: порядок здесь и есть содержание.
var Layers = []Layer{LayerSample, LayerRaw, LayerMinute, LayerHour, LayerDay}
// Known отвечает, знаком ли слой.
func Known(l Layer) bool { return Rank(l) < len(Layers) }
// Rank — место слоя в порядке от мелкого к крупному. Незнакомый слой считается
// крупнее любого известного.
func Rank(l Layer) int {
for i, known := range Layers {
if known == l {
return i
}
}
return len(Layers)
}