добавлен приём пакетов Health Auto Export
- POST /api/v1/ingest: токен, лимит тела, gzip, durable-запись тела в сырой архив, учёт доставки в SQLite - код ответа отражает доставку, а не разбор: битый JSON — 400, непонятое содержимое — 200, данные уже сохранены и доразберутся позже - сохраняется полный набор заголовков запроса с вычисткой секретов по имени и по совпадению значения с токеном
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
// Package ident — единая точка генерации и разбора идентификаторов (ULID).
|
||||
//
|
||||
// ULID сортируется по времени создания, компактен в логах и URL (без дефисов)
|
||||
// и глобально уникален across сущностей: поиск по голому id находит все
|
||||
// упоминания. Канонический вид — нижний регистр.
|
||||
package ident
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/oklog/ulid/v2"
|
||||
)
|
||||
|
||||
// ErrInvalid — синтаксически некорректный идентификатор. Такой id трактуем
|
||||
// как несуществующую сущность, без похода в БД.
|
||||
var ErrInvalid = errors.New("некорректный идентификатор")
|
||||
|
||||
// NewID возвращает новый ULID в нижнем регистре.
|
||||
func NewID() string {
|
||||
return strings.ToLower(ulid.Make().String())
|
||||
}
|
||||
|
||||
// Parse валидирует внешний идентификатор и приводит его к каноническому виду.
|
||||
// Вызывается на входных границах (HTTP, CLI) до запроса к БД: сравнение строк
|
||||
// в SQLite побайтовое, а base32 ULID при декодировании нечувствителен к
|
||||
// регистру.
|
||||
func Parse(s string) (string, error) {
|
||||
id, err := ulid.ParseStrict(strings.ToUpper(strings.TrimSpace(s)))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: %q", ErrInvalid, s)
|
||||
}
|
||||
return strings.ToLower(id.String()), nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package ident_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.vakhrushev.me/av/healthlog/internal/ident"
|
||||
)
|
||||
|
||||
func TestNewIDIsLowercaseULID(t *testing.T) {
|
||||
id := ident.NewID()
|
||||
|
||||
if len(id) != 26 {
|
||||
t.Errorf("длина = %d, ожидалось 26: %q", len(id), id)
|
||||
}
|
||||
if id != strings.ToLower(id) {
|
||||
t.Errorf("id не в нижнем регистре: %q", id)
|
||||
}
|
||||
if ident.NewID() == id {
|
||||
t.Error("два вызова вернули один и тот же id")
|
||||
}
|
||||
}
|
||||
|
||||
// Идентификаторы сортируемы по времени создания — на этом держится
|
||||
// хронология выборок и корреляция в логах.
|
||||
func TestNewIDSortsByCreationTime(t *testing.T) {
|
||||
prev := ident.NewID()
|
||||
for range 10 {
|
||||
next := ident.NewID()
|
||||
if next <= prev {
|
||||
t.Fatalf("порядок нарушен: %q после %q", next, prev)
|
||||
}
|
||||
prev = next
|
||||
}
|
||||
}
|
||||
|
||||
// Внешний id приводится к каноническому виду: сравнение строк в SQLite
|
||||
// побайтовое, а base32 ULID при декодировании нечувствителен к регистру.
|
||||
func TestParseNormalizesCase(t *testing.T) {
|
||||
id := ident.NewID()
|
||||
|
||||
got, err := ident.Parse(strings.ToUpper(id))
|
||||
if err != nil {
|
||||
t.Fatalf("Parse: %v", err)
|
||||
}
|
||||
if got != id {
|
||||
t.Errorf("Parse вернул %q, ожидалось %q", got, id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRejectsGarbage(t *testing.T) {
|
||||
for _, s := range []string{"", "не-id", "01k1", strings.Repeat("z", 26)} {
|
||||
if _, err := ident.Parse(s); !errors.Is(err, ident.ErrInvalid) {
|
||||
t.Errorf("Parse(%q): ошибка = %v, ожидалась ErrInvalid", s, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user