Help us shape the future of Go! Take the 2026 Go Developer Survey

validate

package
v1.16.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package validate содержит общие валидаторы полей API Kachō, общие для всех сервисов (Folder.Name, Network.Name, Subnet.Name и т. п.).

Все валидаторы возвращают gRPC ошибку `InvalidArgument` с `BadRequest.field_violations[]` через `pkg/errors.InvalidArgument()`.

Контракт валидации полей:

  • Name: единственная форма имени ресурса в дереве — DNS label по RFC 1123, `^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$` (строчные буквы, цифры, дефис; первый и последний символ — буква или цифра; 1..63). Пустая строка именем не является: на правке она отвергается `<field> is required`, на создании означает «назови сам» и заменяется NameOrDefault на имя, производное от id.
  • Description: до 256 символов.
  • Labels: до 64 пар; ключ `^[a-z][-_./\\@a-z0-9]{0,62}$` (1..63 байта); значение 0..63 байта.
  • UpdateMask: каждое поле должно быть известно сервисом; неизвестное — `InvalidArgument`.

Index

Constants

View Source
const (
	// MaxNameLen — максимум для Name полей ресурсов.
	MaxNameLen = 63
	// MaxDescriptionLen — лимит описания.
	MaxDescriptionLen = 256
	// MaxLabels — максимальное число label-пар на ресурс.
	MaxLabels = 64
	// MaxLabelKeyLen — длина ключа label.
	MaxLabelKeyLen = 63
	// MaxLabelValueLen — длина значения label.
	MaxLabelValueLen = 63
	// MaxPageSize — верхняя граница для page_size в List RPC.
	MaxPageSize int64 = 1000
	// DefaultPageSize — значение по-умолчанию, когда клиент не задал page_size.
	DefaultPageSize int64 = 50
)
View Source
const EnvExtraResourceIDHyphenPrefixes = "EXTRA_RESOURCE_ID_HYPHEN_PREFIXES"

EnvExtraResourceIDHyphenPrefixes — имя env-переменной с ДОПОЛНИТЕЛЬНЫМИ hyphen-form prefix'ами (comma-separated, напр. "foo,bar" — сам prefix БЕЗ дефиса). Параллель к EnvExtraResourceIDPrefixes, но для going-forward формы "<prefix>-<base32>" (B3). Назначение то же: новый домен на hyphen-канон маршрутизируется на authz-edge api-gateway БЕЗ релиза corelib — оператор задаёт prefix через config. Канонические platform-prefix'ы (см. ids.KnownHyphenPrefixes) остаются захардкожены; config-путь — только расширение вперёд.

View Source
const EnvExtraResourceIDPrefixes = "EXTRA_RESOURCE_ID_PREFIXES"

EnvExtraResourceIDPrefixes — имя env-переменной с ДОПОЛНИТЕЛЬНЫМИ известными 3-символьными resource-id prefix'ами (comma-separated, напр. "xyz,qqq"). Читается один раз при инициализации пакета и мёржится в базовый набор.

Назначение — снять blast-radius «новый домен → InvalidArgument на authz-edge api-gateway, пока corelib не отредактирован и не перевыпущен»: оператор api-gateway задаёт префикс нового семейства через config/env, БЕЗ релиза corelib. Базовые платформенные prefix'ы остаются захардкожены (стабильны, покрыты регрессионным guard-тестом); config-путь — только расширение вперёд.

View Source
const LegacyEnvExtraResourceIDHyphenPrefixes = "KACHO_EXTRA_RESOURCE_ID_HYPHEN_PREFIXES"

LegacyEnvExtraResourceIDHyphenPrefixes — прежнее написание, принимаемое ОКНОМ перехода (`PRO-Robotech/corelib#11`).

View Source
const LegacyEnvExtraResourceIDPrefixes = "KACHO_EXTRA_RESOURCE_ID_PREFIXES"

LegacyEnvExtraResourceIDPrefixes — прежнее написание, принимаемое ОКНОМ перехода (`PRO-Robotech/corelib#11`).

View Source
const NameForm = nameform.Form

NameForm — единственная форма имени ресурса в дереве (RFC 1123 DNS label).

Сама строка объявлена в `pkg/validate/nameform` — пакете БЕЗ транспорта, потому что ту же форму обязан читать слой домена, которому grpc запрещён (см. документацию того пакета). Здесь — только ссылка: копии литерала не заводится, поэтому расходиться нечему.

Variables

This section is empty.

Functions

func CanonFieldName

func CanonFieldName(name string) string

CanonFieldName приводит имя поля к форме контракта (`hostClasses` → `host_classes`), посегментно относительно точки.

func Description

func Description(field, value string) error

Description проверяет длину поля description (UTF-8).

func FieldNameEq

func FieldNameEq(a, b string) bool

FieldNameEq отвечает, называют ли две строки ОДНО поле контракта.

Форму имени в `update_mask` выбирает не сервис. Край разбирает тело запроса через protojson, и тот приводит `updateMask` к именам полей контракта: клиент прислал `countryCode`, сервис получил `country_code`. Прямой вызов gRPC несёт то, что положил вызывающий. Сравнение строк ДОСЛОВНО поэтому отвечает «нет» на верном входе — и делает это тихо: поле объявлено изменяемым, запрос принят, ответ успешен, значение не изменилось.

Класс не виден, пока все изменяемые поля односложны: у `status` и `labels` обе формы совпадают. Первое многословное поле вскрывает его на пути ЗАПИСИ, а не проверки — то есть на два шага дальше того места, где о форме имени вообще думают.

Обе стороны приводятся к форме имени поля контракта; точка разделяет вложенные поля и в приведении не участвует.

func Labels

func Labels(field string, labels map[string]string) error

Labels проверяет map labels: число пар, длину и regex ключа, длину значения.

func Name

func Name(field, value string) error

Name проверяет имя ресурса против единственной формы дерева (NameForm).

Пустая строка — НЕ имя. Она отвергается отдельным сообщением `<field> is required`, а не общим сообщением о форме: вызывающий, забывший поле, и вызывающий, приславший `My_Name`, ошиблись по-разному, и отказ обязан это различать.

На пути СОЗДАНИЯ пустая строка остаётся законным входом — там зовут не эту функцию, а пару NameOnCreate (форма) + NameOrDefault (что записать).

func NameOnCreate

func NameOnCreate(field, value string) error

NameOnCreate — форма имени на пути СОЗДАНИЯ: пустая строка законна, непустая обязана соответствовать NameForm.

Пустое здесь означает не «имя отсутствует», а «назови сам»: до записи оно заменяется умолчанием (NameOrDefault), и ресурс с пустым именем не возникает. Требовать имя на создании значило бы ломающее изменение у каждого создающего глагола ради величины косметической: адресуется ресурс по неизменяемому id (ban #15).

Отвечает на вопрос «допустим ли ввод», а НЕ «что будет записано» — второй вопрос задают в момент, когда id уже сгенерирован, и отвечает на него NameOrDefault. Два вопроса, два момента, две функции.

func NameOnUpdate

func NameOnUpdate(field string, mask []string, value string) (bool, error)

NameOnUpdate — ЕДИНСТВЕННОЕ решение «что делать с именем на правке».

Возвращает, следует ли записывать имя, и отказ, если присланное недопустимо. Пять исходов, и они выведены из того, что proto3 НЕ РАЗЛИЧАЕТ «поле не прислано» и «поле пусто»:

маска пуста, имя пусто          → (false, nil) — полная правка, имени не касались
маска пуста, имя непусто        → (true, форма)
маска не называет name          → (false, nil)
маска называет name, имя пусто  → (false, `<field> is required`)
маска называет name, непусто    → (true, форма)

Почему пустая маска НЕ отвергает пустое имя. Полная правка — законный способ изменить описание или метки, не трогая имя; отказ там сломал бы каждого, кто правит объект целиком. Почему она при этом не ЗАПИСЫВАЕТ пустое: записать значило бы вернуть безымянный ресурс той самой дверью, которую закрывает отказ строкой ниже, — и упереться в ограничение формы уже на стороне базы, отдав вызывающему внутреннюю ошибку вместо контрактного отказа.

Почему функция общая. Правило горизонтально — оно про форму запроса, а не про предмет сервиса, и рассыпанное по use-case'ам разойдётся ровно так же, как разошлись четыре регулярки. Первая его редакция и завелась копией в одном сервисе; здесь она сведена к одной (#715).

func NameOrDefault

func NameOrDefault(value, id string) string

NameOrDefault — имя, которое СЛЕДУЕТ ЗАПИСАТЬ: пустое заменяется умолчанием, производным от id.

Зовётся в use-case создания в точке, где id уже сгенерирован. Точка вызова у каждого сервиса своя, и это нормально; общей обязана быть функция — правило «пустое имя не доживает до записи», рассыпанное по сервисам как `if name == "" { name = … }`, разойдётся ровно так же, как разошлись четыре регулярки.

Гонки нет by construction: значение известно ДО вставки, вставка одна, конфликту взяться неоткуда. Форму value эта функция НЕ проверяет — её проверил NameOnCreate на входе.

func PageSize

func PageSize(field string, value int64) (int64, error)

PageSize проверяет границы page_size в List RPC.

Семантика контракта:

  • page_size == 0 → допустимо; клиент явно не задал, репозиторий применяет DefaultPageSize. Возвращает (DefaultPageSize, nil).
  • page_size < 0 или > MaxPageSize → InvalidArgument с FieldViolation; возвращает (0, err). Не silent fallback — это нарушение контракта.
  • 1..MaxPageSize → возвращает (value, nil).

Возвращаемое effective значение нужно использовать в LIMIT-выражении SQL. Каждый репозиторий-метод List должен вызывать PageSize первой строкой и пробрасывать err наружу через service.

func ResourceID

func ResourceID(resourceType, expectedPrefix, id string) error

ResourceID проверяет, что resource-id синтаксически валиден — начинается с известного prefix Kachō в ОДНОЙ из двух форм (B3, redesign-2026):

  • legacy слитная форма "<prefix><17-crockford-base32>" — первые 3 символа ∈ resourceIDPrefixes (`net…`, `epd…`, `acb…`);
  • going-forward hyphen-форма "<prefix>-<crockford-base32>" — сегмент ДО первого дефиса ∈ hyphenResourceIDPrefixes (`ins-…`, `ns-…`, `mt-…`).

Крокфорд-тело дефис не содержит, поэтому наличие дефиса — однозначный сигнал новой формы; классификация **строго аддитивна** — legacy-поведение не меняется (hyphen-приём только ДОБАВЛЯЕТ acceptance, а не отзывает). Сервисы мигрируют свой prefix по одному, поэтому router обязан принимать обе формы в переходный период. Пустой id — пропускается (required-проверка / transcoding-роутинг — отдельно).

Контракт: на malformed / нераспознанный resource-id мутирующие и read-RPC отдают sync `InvalidArgument` с flat-message `"invalid <resourceType> id '<id>'"` (НЕ `NotFound`). Семантика **family-agnostic**: prefix должен быть из известного набора, но НЕ обязан совпадать с типом ресурса (`enp`-id, переданный как subnet-id, проходит → дальше `repo.Get` → `NotFound`). Длину/алфавит тела внутри здесь не проверяем.

resourceType   — имя ресурса в нижнем регистре ("network", "subnet",
                 "security group", "folder", "gateway", "private endpoint", ...).
expectedPrefix — prefix семейства этого ресурса (ids.PrefixNetwork и т.п.).
                 По контракту проверка family-agnostic (см. выше), поэтому
                 значение осознанно НЕ сверяется с id — параметр документирует
                 в call-site'е, какое семейство ожидается, не навязывая strict-
                 сверку. Это не «зарезервировано на будущее»: family-agnostic —
                 конечный контракт (enp-id как subnet-id должен доходить до
                 repo.Get → NotFound, а не отбиваться InvalidArgument здесь).

Возвращаемая ошибка — готовый gRPC `status` с нужным flat-message (не field-violation builder — в этом случае контракт требует flat-message).

func UpdateMask

func UpdateMask(field string, mask []string, known map[string]struct{}) error

UpdateMask проверяет, что все field-ы в mask содержатся в known.

Используется в *.Update методах: каждый сервис указывает свой набор разрешенных для апдейта полей; все остальное — InvalidArgument.

func ZoneId

func ZoneId(field, value string) error

ZoneId — format/required-валидация: проверяет, что value не пустой.

Список валидных зон НЕ хардкодится. Existence-валидация (есть ли такая зона в БД) — ответственность сервиса, владеющего таблицей `zones` (kacho-vpc). Здесь только required-check — формируем единообразный FieldViolation для пустого zone_id.

Пустая строка → InvalidArgument c FieldViolation `<field> is required`. Непустое значение → nil (caller обязан выполнить existence-check).

Types

This section is empty.

Directories

Path Synopsis
Package nameform несёт ЕДИНСТВЕННУЮ форму имени ресурса Kachō и чистый предикат к ней — без транспорта.
Package nameform несёт ЕДИНСТВЕННУЮ форму имени ресурса Kachō и чистый предикат к ней — без транспорта.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL