httpapi: точки метрики за период отдаются одним запросом
- `GET /api/v1/metrics/{name}?from&to&layer` — ряд точек за период; конверт
объявляет слой, измеренный род, его применимость к отданному ряду и границу
окна измерения, а сам ряд собирается из одного слоя, выбранного по охвату
точек внутри периода
- use-case вынесен в `internal/points`, чтение — одним входом `store.ReadSeries`
под одной транзакцией; правило выбора слоя остаётся в домене и приходит в
хранилище колбэком
- `writeJSON` перестал экранировать HTML-символы и перестал глушить отказ
записи: дословность содержимого точки иначе не удерживается, а оборванное
тело уходило под видом успешного `200`
This commit is contained in:
+26
-15
@@ -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)
|
||||
}
|
||||
|
||||
@@ -786,3 +786,37 @@ func TestParseЧужаяПричинаОбрезается(t *testing.T) {
|
||||
t.Errorf("текст ошибки %d Б: литерал из тела доехал до сообщения", len(err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
// Словарь слоёв ОДИН, и порядок в нём — от самого мелкого к самому крупному.
|
||||
//
|
||||
// Утверждается прямо, потому что из этого словаря выводятся четыре вещи: ранг
|
||||
// слоя при разборе, перечень слоёв для выборки охватов при чтении, проверка
|
||||
// параметра запроса и текст отказа клиенту. Разъехавшись, они дали бы пустой
|
||||
// ряд при непустых данных — и ни компилятор, ни другой тест этого не увидели бы.
|
||||
func TestСловарьСлоёвУпорядоченОтМелкогоККрупному(t *testing.T) {
|
||||
want := []hae.Layer{hae.LayerSample, hae.LayerRaw, hae.LayerMinute, hae.LayerHour, hae.LayerDay}
|
||||
|
||||
if len(hae.Layers) != len(want) {
|
||||
t.Fatalf("слоёв в словаре %d, ожидалось %d", len(hae.Layers), len(want))
|
||||
}
|
||||
for i, l := range want {
|
||||
if hae.Layers[i] != l {
|
||||
t.Errorf("слой %d — %q, ожидался %q", i, hae.Layers[i], l)
|
||||
}
|
||||
if hae.Rank(l) != i {
|
||||
t.Errorf("ранг %q = %d, ожидался %d", l, hae.Rank(l), i)
|
||||
}
|
||||
if !hae.Known(l) {
|
||||
t.Errorf("слой %q словарём не признан", l)
|
||||
}
|
||||
}
|
||||
|
||||
// Незнакомый слой крупнее любого известного и словарём не признан: иначе он
|
||||
// выиграл бы предпочтение при равном охвате.
|
||||
if got := hae.Rank("weekly"); got != len(hae.Layers) {
|
||||
t.Errorf("ранг незнакомого слоя %d, ожидался %d", got, len(hae.Layers))
|
||||
}
|
||||
if hae.Known("weekly") {
|
||||
t.Error("незнакомый слой признан словарём")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user