shared

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 13, 2026 License: AGPL-3.0 Imports: 23 Imported by: 0

Documentation

Overview

Package shared — общие helpers для всех api-слайсов (parity с services/vpc/internal/apps/kacho/shared).

Содержит:

  • errors.go — sentinel → gRPC status mapping (MapRepoErr, MapValidationErr); заменяет per-resource `mapRepoErr`.
  • errdetails.go — InvalidArgument-builder с BadRequest FieldViolations.
  • ids.go — `ValidateResourceID(id, prefix, name)` (parity с YC-style ошибкой `"invalid <resource> id '<id>'"`).
  • proto.go — `TimestampProto` (truncate-to-seconds), `OperationToProto` (corelib.Operation → proto.Operation).

Package shared — errdetails.go: builder для InvalidArgument с BadRequest FieldViolations details (Kachō error-format convention).

Заменяет 6+ копий per-resource `invalidArg(field, desc)` (account/helpers.go, project/helpers.go, …) — все идентичны bit-for-bit.

Package shared — errors.go: единый sentinel → gRPC status mapping для всех api-слайсов (account / project / user / service_account / group / role / access_binding).

Заменяет 7+ копий per-resource `mapRepoErr` (account/helpers.go, project/helpers.go, …). Все вызывающие должны маппить sentinel-ошибки именно через эти функции — единственный authoritative point of translation между internal-sentinels и gRPC-кодами, чтобы (а) не дрейфил mapping per-package, (б) добавление нового sentinel'а требовало правки одного места.

Package shared — ids.go: resource-id format validator.

Заменяет 11+ копий per-resource `validate<Resource>ID(id)` и `validateAccountIDFor<Resource>(id)` функций (account/get.go, project/get.go, role/helpers.go, group/helpers.go, ServiceAccount, User, AccessBinding — все одинаковые: prefix + length check).

Package shared — list_operations.go: ListOperationsUseCase backs the per-resource ListOperations RPC of RoleService / GroupService / ProjectService / ServiceAccountService, plus MapOperationsListErr — the single classifier every IAM operations feed uses to decide whether a listing failure belongs to the caller or to the store.

All four resources list operations identically — filter the common `operations` table by the denormalized `resource_id` column with (created_at, id) cursor pagination (corelib operations.Repo). The query lives in the repo layer (corelib); this use-case is the thin reuse point so the four handlers stay transport-only (architecture.md clean-arch) and the no-op placeholders are replaced by one shared implementation.

Package shared — proto.go: общие helper'ы для конверсии domain ↔ proto.

Заменяет:

  • 7 копий `tsProto(t)` truncate-to-seconds timestamp helper'ов (account/handler.go, project/handler.go, user/handler.go, role/helpers.go, group/helpers.go, service_account/helpers.go, access_binding/helpers.go);
  • копии `operationToProto(op)` corelib.Operation → proto.Operation mapping функций (account, access_binding, group, sa_keys, internal_authorize, project, user, conditions, role, service_account).

Все копии — bit-for-bit идентичны; разные имена per-package — единственное отличие.

Package shared — txworker.go: generic Write-TX scaffolding для worker'ов async-операций (doCreate / doUpdate / doDelete / etc).

Заменяет ~12 LOC boilerplate'а begin→commit→rollback→committed flag, повторяющегося в 15+ worker'ах через все 7 ресурсных пакетов (account / project / user / sa / group / role / access_binding).

Package shared — updatemask.go: UpdateMask validation + predicate.

Заменяет повторяющиеся ~16 LOC (validation loop + apply closure) в каждом из 5 Update use-case'ов: account/project/service_account/group/role.

Per-resource maps `<resource>MutableFields` + `<resource>ImmutableFields` остаются package-level в каждом ресурсе (контекстные error-messages типа `"ownerUserId is immutable after Account.Create"` — имя поля в тексте всегда в wire-форме camelCase, см. immutable_message_camelcase_test.go) — они конфигурация, не дубликат.

Index

Constants

View Source
const (
	// ReconcileEventUpsert — an object appeared or changed (mirror upsert / iam-native
	// Create / label update). Drives a forward re-evaluation of matching bindings.
	ReconcileEventUpsert = "mirror.upsert"
	// ReconcileEventDelete — an object was removed (mirror delete). Drives eager-revoke
	// of any materialized member referencing it.
	ReconcileEventDelete = "mirror.delete"
)

reconcile_event.go — app-layer reconcile-event type literals.

The γ reconciler-worker drains kaname.resource_reconcile_outbox and re-evaluates the bindings that reference the changed object (ReconcileObject keys on object_type/object_id — it does NOT branch on the event type). So an IAM-OWN resource CHANGE (label-update OR a brand-new resource that must forward-materialize under an owner `*.*` binding, rbac-contract-a-fix) re-uses the SAME "mirror.upsert" literal the β mirror-change path uses — there is no separate "created" type.

These literals are kept in lockstep with repo/kaname/pg/reconcile_outbox.Event* (the drainer reads the same strings) but declared HERE so the use-case layer does not import the pg adapter package (Clean Architecture dependency rule — the same reason internal_iam.register_resource.go inlines them).

View Source
const (
	ScopeBindingRevokePageSize  = 1000
	ScopeBindingRevokeMaxPasses = 50
)

ScopeBindingRevokePageSize / ScopeBindingRevokeMaxPasses — границы дренажа. Размер страницы — платформенный максимум списка; потолок проходов обращает патологическую область в отказ, а не в тихую частичную работу.

View Source
const DefaultListPageSize = 50

DefaultListPageSize — the page a caller gets when he names no size. Same value the repository applies (pg.defaultListPageSize); stated here because the use-case now decides the length of the answer.

View Source
const MaxListPageSize int32 = 1000

MaxListPageSize — верхняя граница page_size, общая с репозиторием (pg.effectivePageSize) и с платформенной validate.PageSize. Значение вне диапазона ОТВЕРГАЕТСЯ, а не зажимается: молчаливое зажатие отдаёт не ту страницу, о которой просили, и вызывающий об этом не узнаёт.

View Source
const MaxNamedBlockingGrants = 5

MaxNamedBlockingGrants — сколько выдач называется поимённо.

Предел есть, и он не вкусовщина: число мешающих выдач НИЧЕМ не ограничено сверху, а сообщение об отказе читает человек. Отказ, вываливающий двести идентификаторов, перестают читать целиком — вместе с той частью, ради которой он написан. Полная величина при этом не теряется: она уезжает и прозой, и машинно.

View Source
const PostCommitTimeout = 2 * time.Minute

PostCommitTimeout bounds a detached post-commit materialization pass. Generous relative to a healthy pass (milliseconds to low seconds) but finite, so a pass wedged on a lock or an unresponsive peer cannot pin a goroutine for the process lifetime. Exceeding it is not data loss — the durable outbox/event backstop re-converges the object.

View Source
const UnavailableMessage = "service unavailable"

UnavailableMessage — текст, который получает вызывающий на признаке недоступности. ЕДИНСТВЕННЫЙ производитель этого текста в службе.

Почему константа, а не литерал у каждого переводчика

Переводчик отказа в этой службе не один, и предмет у них общий. Пока текст стоял литералом, копии разошлись с каноном молча: канон был переведён на фиксированный текст, копии остались на тексте цепочки, и решения об этом расхождении никто не принимал (задача #2464). Второе место об одном предмете расходится на первом же уточнении — здесь оно расходилось полтора месяца.

Почему текст НЕ называет подсистему

Признак недоступности ставит и база, и сосед, и гейт прав, поэтому «database unavailable» на проводе был бы собственной маленькой ложью в двух случаях из трёх. Вызывающему довольно кода и того, что повтор осмыслен.

Variables

This section is empty.

Functions

func DoWithWriteTx

func DoWithWriteTx[T any](
	ctx context.Context,
	repo kanamerepo.Repository,
	action func(ctx context.Context, w kanamerepo.Writer) (T, error),
) (T, error)

DoWithWriteTx оборачивает стандартный writer-tx паттерн вокруг одной мутации: Begin → action → Commit (если action вернул nil) или Rollback (если action вернул err / panic — defer-Rollback срабатывает на любом not-committed возврате).

Использование (account/create.go::doCreate):

created, err := shared.DoWithWriteTx(ctx, u.repo,
    func(ctx context.Context, w kanamerepo.Writer) (domain.Account, error) {
        return w.AccountsW().Insert(ctx, a)
    })
if err != nil { return nil, err }
// post-commit hooks here
return marshalAccount(created)

Generic T — domain-тип, возвращаемый action'ом. Go infer'ит его из closure.

Все ошибки из репо (action error + Writer/Commit) маппятся через MapRepoErr — caller получает уже gRPC-status.

func DoWithWriteTxVoid

func DoWithWriteTxVoid(
	ctx context.Context,
	repo kanamerepo.Repository,
	action func(ctx context.Context, w kanamerepo.Writer) error,
) error

DoWithWriteTxVoid — вариант для action'ов без возвращаемого domain-объекта (например, doDelete). Возвращает только error.

Использование (account/delete.go::doDelete):

if err := shared.DoWithWriteTxVoid(ctx, u.repo,
    func(ctx context.Context, w kanamerepo.Writer) error {
        return w.AccountsW().Delete(ctx, id)
    }); err != nil {
    return nil, err
}
return anypb.New(&emptypb.Empty{})

func EffectiveListPageSize

func EffectiveListPageSize(pageSize int32) int

EffectiveListPageSize resolves a requested page size to the number of visible rows a page holds. It mirrors the repository's own resolution (0 means the default) and is needed in the use-case because THAT is where the page is now filled — the repository no longer knows how large the answer is.

The value is assumed already validated by ValidateVisiblePagination; out of range it clamps to the default rather than inventing a limit, and the guard above is what makes that branch unreachable on the served path.

func EncodeVisiblePageToken

func EncodeVisiblePageToken(c VisibleCursor) string

EncodeVisiblePageToken renders the cursor. Nanosecond precision is kept because the keyset compares the stored timestamp, not the truncated one the resource message carries.

func GoPostCommit

func GoPostCommit(callerCtx context.Context, logger *slog.Logger, what string, fn func(context.Context))

GoPostCommit runs fn asynchronously, detached from callerCtx's cancellation and deadline but carrying its values, under PostCommitTimeout. `what` names the pass in the panic log. A nil fn is a no-op.

The caller MUST NOT rely on fn having completed when GoPostCommit returns — that is the entire point. Use it only for work whose durable record is already committed.

func InvalidArg

func InvalidArg(field, desc string) error

InvalidArg — InvalidArgument-error с одним BadRequest_FieldViolation. Если WithDetails не смог приклеить детали (proto-marshal-fail), возвращает голый status без них (best-effort — клиент получит code+message, отсутствие details не критично).

func IsMembershipCarriesRights

func IsMembershipCarriesRights(err error) bool

IsMembershipCarriesRights — отказ ли это полосы «членство несёт права».

Спрашивается ПОСЛЕ `MapRepoErr`, поэтому смотрит на машинный признак, а не на цепочку sentinel'ов: `MapRepoErr` пересобирает статус и цепочку не сохраняет. Это же делает распознавание НЕЗАВИСИМЫМ ОТ ЯЗЫКА прозы — ровно то свойство, ради которого признак и заводится.

func LabelsEqual

func LabelsEqual(a, b domain.Labels) bool

LabelsEqual reports whether two label maps are equal (same keys + values). Used by Update use-cases to decide whether `labels` is a real change for the audit `changed_fields` set (no-op label re-set must not count as a change).

func LogRepoErr

func LogRepoErr(ctx context.Context, logger *slog.Logger, op string, err error) error

LogRepoErr переводит ошибку хранилища в gRPC-статус и, если перевод СТИРАЕТ причину, называет её журналу сервера.

Какие исходы пишутся и почему не все

Пишутся ровно те, чьё сообщение на проводе причины НЕ несёт:

  • `INTERNAL` — текст фиксирован («internal error») by construction, иначе наружу уехал бы текст драйвера;
  • `UNAVAILABLE` — текст тоже фиксирован («service unavailable»), по той же причине: цепочка ведёт к драйверу. До этой правки он собирался из цепочки, и обёртка вызывающего уезжала на провод дословно. Подсистему текст не называет: признак ставит и база, и сосед, и гейт прав.

Остальные отказы называют свою причину САМИ и адресованы вызывающему: «Project %s not found», «Illegal argument …». Дублировать их в журнале значило бы писать строку на каждое обычное обращение арендатора — и та единственная, ради которой журнал и читают, утонула бы среди них. Заметность, введённая таким способом, уничтожается тем же средством, которым вводится.

Что пишется

Полоса (`op`), полная цепочка ошибки (там же код состояния, который мост SQLSTATE оставляет намеренно) и КОНКРЕТНЫЙ ТИП ошибки. Тип назван отдельно, потому что отказ, не несущий строки состояния вовсе (не дозвонились до базы), цепочкой не отличим от прочих, а типом — отличим сразу.

Журнал — серверный, на провод из него не идёт ничего: возвращаемый статус собирает `MapRepoErr`, и он остаётся единственным местом перевода.

func MapOperationsListErr

func MapOperationsListErr(err error) error

MapOperationsListErr classifies a failure of operations.Repo.List by WHAT THE STORE ANSWERED, never by what the caller happened to send.

The operations repo validates the caller's page format itself and reports it as a gRPC InvalidArgument naming the field — a page_token it could not decode, a page_size outside [0..1000] (corevalidate.PageSize). That classification is authoritative and passes through unchanged, field violations included. Everything else the repo returns is a store failure and gets the fixed INTERNAL text (never the pgx/SQL text).

The shape this replaces keyed on "was a page_token supplied", which is not a fact about the failure at all. It mislabelled in both directions: a database that was down was reported as a malformed cursor — sending the operator to fix a client that was fine — and a page_size out of range on the FIRST page (no token yet) was reported as an internal fault, filing a caller's mistake as an outage. Both are now decided by the store's own answer.

func MapRepoErr

func MapRepoErr(err error) error

MapRepoErr — sentinel → gRPC status. Возвращает nil на nil-input.

Полное покрытие 8 sentinel'ов (включая ErrPermissionDenied / ErrUnauthenticated, которых не было в per-resource копиях — leak'ало `codes.Internal` клиенту до этой консолидации).

Fallback'и:

  • если err уже несет gRPC status (не codes.Unknown) — пропускаем через;
  • если err-текст начинается с "Illegal argument" — YC-style InvalidArgument (parity с verbatim-формой error-сообщений);
  • иначе — Internal с переданным err-текстом (StripSentinel снимает sentinel-prefix чтобы клиент не увидел "not found: ...").

Порядок веток — сначала pass-through, потом sentinel-switch (форма kacho-nlb). Он несущий, а не косметический: pkg/validate кладёт имя поля ТОЛЬКО в google.rpc.BadRequest-details, сообщение остаётся общим «invalid argument». Пересборка статуса в sentinel-ветке (`status.Error(code, StripSentinel(err))`) детали теряет, поэтому ошибка, обёрнутая через `%w` на iamerr.Err*, обязана пройти pass-through ПЕРВОЙ. status с codes.Unknown под pass-through НЕ попадает (guard `!= Unknown`) — он падает в sentinel-switch и дальше в фиксированный INTERNAL, без leak'а.

func MapValidationErr

func MapValidationErr(err error) error

MapValidationErr — обертка для результатов `domain.<Type>.Validate()` (cumulative multierr). Все sync-handler'ы вызывают ее на validation-stage перед эмитом Operation, чтобы InvalidArgument имел единую форму.

func MaskAllows

func MaskAllows(mask []string, field string) bool

MaskAllows возвращает true, если поле `field` присутствует в mask, ИЛИ если mask пустой (full-PATCH semantics — все поля применяются).

Заменяет inlined-closure pattern:

apply := func(field string) bool {
    if len(mask) == 0 { return true }
    for _, m := range mask { if m == field { return true } }
    return false
}

func NameBlockingGrants

func NameBlockingGrants(err error, named []string, total int) error

NameBlockingGrants дополняет отказ полосы перечнем выдач, которые ДЕРЖАТ членство: тем же перечнем прозой и машинно.

`total` — сколько их всего, `named` — сколько из них названо. Пустой перечень возвращает отказ КАК ЕСТЬ: дочитать не удалось (выдачи успели отозвать между отказом и чтением, либо база не ответила), и выдумывать перечень не из чего. Отказ при этом не портится — он просто остаётся прежним, а не превращается в утверждение «мешающих выдач ноль», которого база не делала.

func OperationToProto

func OperationToProto(op *operations.Operation) *operationpb.Operation

OperationToProto — прослойка к общему слою: перевод строки операции в контракт объявлен в дереве ОДИН раз (`pkg/operations/operationspb`, задача #1369).

До сведения объявлений было двенадцать, а смысловых версий — пять; расходились они именем помощника усечения времени и охраной пустого значения, то есть там, где расхождение не ломает сборку и видно только тому, кто сравнит копии.

func ResolveLabelsUpdate

func ResolveLabelsUpdate(mask []string, body domain.Labels) (newLabels domain.Labels, apply bool)

ResolveLabelsUpdate решает, нужно ли применить labels в Update, и возвращает целевое значение метки.

proto3-map не несет presence: пустой `labels:{}` и отсутствующий labels в теле приходят в use-case как nil domain.Labels — неотличимо. Поэтому единственный надежный сигнал «очистить labels» — присутствие "labels" в update_mask:

  • mask содержит "labels" → apply, значение = тело (nil/пусто → очистка);
  • mask пустой (full-PATCH) и labels переданы в теле → apply, значение = тело;
  • иначе → labels не трогаем.

При apply=true newLabels всегда non-nil (пустой набор = очистка), чтобы writer записал labels='{}' и LabelsEqual корректно сравнил с текущим значением.

func RevokeBindingsInScope

func RevokeBindingsInScope(
	ctx context.Context,
	w kanamerepo.Writer,
	resourceType domain.ResourceType,
	resourceID string,
	displayNoun string,
) ([]outboxtypes.RelationTuple, int, error)

RevokeBindingsInScope сносит КАЖДУЮ выдачу, сделанную на область (resourceType, resourceID), внутри транзакции вызывающего: читает её персистентную ведомость выпущенных кортежей, удаляет строку выдачи (её ведомость уходит каскадом) и ВОЗВРАЩАЕТ собранный набор кортежей на снятие.

Эмитит собранное САМ ВЫЗЫВАЮЩИЙ — вместе со своими собственными кортежами жизненного цикла (указатель аккаунта на кластер, структурные указатели проекта), которые в ведомости выдач по построению не значатся.

displayNoun — существительное ресурса в тоне контракта («Account», «Project»); попадает в текст отказа, который читает оператор.

func TimestampProto

func TimestampProto(t time.Time) *timestamppb.Timestamp

TimestampProto конвертирует time.Time в *timestamppb.Timestamp с truncate'ом до секунд (конвенция Kachō: timestamp precision = seconds, не nanoseconds). Zero-time → nil (parity с handler-стиль омитом null timestamp полей).

func ValidatePageToken

func ValidatePageToken(field, token string) error

ValidatePageToken returns InvalidArgument when a non-empty token is not a well-formed keyset cursor (base64 of `<RFC3339Nano>|<id>`). Empty → OK (first page). The message is the stable contract form "Illegal argument <field>".

func ValidatePagination

func ValidatePagination(pageToken string, pageSize int32) error

ValidatePagination — sync-проверка формата пагинации целиком (page_size + page_token) для списочного use-case'а.

Зачем в use-case'е, а не только в хендлере. Списочный use-case решает, кто спрашивает, раньше, чем читает страницу: анонимный или неуправомоченный вызывающий получает пустую страницу и до репозитория не доходит. Пока формат проверял только репозиторий, один и тот же мусорный курсор получал разный ответ в зависимости от того, что вызывающему выдано, — то есть проверка ввода зависела от прав. Здесь она от них не зависит.

Репозиторий остаётся авторитетным: он повторяет обе проверки на служимом пути.

ВАЖНО про место вызова: сюда приходит уже суженное до int32 значение. Если вызывающий сузил его насыщающим преобразованием, отрицательный ввод превратился в 0 ещё ДО этой строки, а 0 в контракте значит «применить умолчание» — проверка стоит, верна и не может сработать. Формат СЫРОГО запроса судит ValidateRawPagination; эта функция остаётся проверкой уже разобранного фильтра.

func ValidateRawPagination

func ValidateRawPagination(pageToken string, pageSize int64) error

ValidateRawPagination — формат пагинации по СЫРЫМ значениям запроса, до любого преобразования типа и до любого решения о доступе.

Зачем отдельная функция и почему она принимает int64. `page_size` приезжает по проводу как int64, а внутренние фильтры iam — int32, поэтому транспорт сужал значение насыщающим преобразованием (safeconv.ClampNonNegInt32). Насыщение — не проверка: −1 становится нулём, ноль означает «умолчание», и вызывающий получает страницу, о которой не просил, ничего об этом не узнав. Судить надо то, что прислали, а сужать — уже проверенное (после этой строки значение лежит в [0..1000] и в int32 помещается by construction).

Порядок тоже её предмет. Вопрос «правильно ли составлен запрос» имеет ОДИН ответ для всех вызывающих, поэтому отвечать на него надо раньше, чем на вопрос «что этому вызывающему видно» (api-conventions.md: формат → authz → repo). Иначе один и тот же мусорный курсор даёт InvalidArgument тому, у кого грант есть, и отказ либо пустую страницу тому, у кого его нет.

Текст отказа — платформенный (`validate.PageSize`), тот же, что у vpc/geo/storage и что записан в требованиях к продукту: "page_size must be in [0..1000]".

func ValidateRawVisiblePagination

func ValidateRawVisiblePagination(pageToken string, pageSize int64) error

ValidateRawVisiblePagination is ValidateVisiblePagination over the RAW request values — the transport's int64 page_size before it is narrowed to int32. Narrowing saturates, and saturation is not validation: −1 becomes 0, and 0 means "apply the default", so a caller would receive a page it did not ask for and never learn of it.

func ValidateResourceID

func ValidateResourceID(id, prefix, resourceName string) error

ValidateResourceID проверяет соответствие id формату `<prefix><17-char-tail>` (общая для всех IAM-ресурсов длина — `domain.ShortIDLen`).

СТРОГОСТЬ ЗДЕСЬ — РЕШЕНИЕ, А НЕ УПУЩЕНИЕ, и она отличает эту проверку от платформенного маршрутизатора `corevalidate.ResourceID` по ТРЁМ осям, а не по одной: пустая строка (здесь отвергается, там проходит), чужой тип с известным префиксом (здесь отвергается, там проходит в полосу отсутствия) и длина тела (здесь фиксирована, там не проверяется).

Поэтому «привести к конвенции» заменой вызова — ослабление по трём осям сразу; ось пустой строки при этом завела бы отказ с вырезанным id (`"Account not found"`) во все места разом. Расхождение с `api-conventions.md` §«By-lane code-split», довод, три границы и ВНЕШНИЙ предикат пересмотра записаны решением: `docs/engineering/architecture/known-divergences.md`, §19.

ЗВАТЬ ТОЛЬКО ДЛЯ СВОЕГО ИДЕНТИФИКАТОРА. Тип чужого решает его владелец; строгая сверка префикса на чужой ссылке — нарушение конвенции, и она стережётся гейтом `TestStrictIDFormatCheckStaysOwnerScoped` (ids_owner_scope_test.go): префикс обязан быть константой собственного пакета `domain`.

На несоответствии возвращает InvalidArgument с сообщением в каноническом Kachō-формате: `"invalid <resource-name> id '<id>'"`. resourceName — для error-сообщения (например "account", "service account", "access binding"; **именно** в той форме, в какой Kachō показывает ошибку — с пробелами, не camelCase).

func ValidateUpdateMask

func ValidateUpdateMask(mask []string, mutable map[string]struct{}, immutable map[string]string) error

ValidateUpdateMask проверяет, что каждое поле в mask:

  • НЕ присутствует в `immutable` map (если присутствует → InvalidArg с per-field error message — по контракту error-format Kachō), И
  • присутствует в `mutable` set (если нет — Illegal-argument update_mask field — по контракту error-format Kachō).

Пустая mask проходит без ошибок (full-PATCH semantics).

`immutable` value-string — сообщение для конкретного field (`"<field> is immutable after <Resource>.Create"` — см. per-resource maps).

func ValidateVisiblePagination

func ValidateVisiblePagination(pageToken string, pageSize int32) error

ValidateVisiblePagination — the whole page-format question for a visible-page list, answered before anything about the caller is decided.

Types

type CreatedByLane

type CreatedByLane struct {
	// Principal — идентификатор аутентифицированного вызывающего.
	Principal string
	// CallerIsServiceAccount — вызывающий есть машина (служебная учётка). Её
	// идентификатор строкой users(id) не является, поэтому ответственным она
	// быть не может ни на одной полосе.
	CallerIsServiceAccount bool
	// MachineLaneKnowsRecord — знает ли КРАЙ, какого ответственного полоса
	// запишет вызывающему-машине. false → присланное сверить нечем.
	MachineLaneKnowsRecord bool
	// MachineLaneRecords — то самое значение. Читается только при
	// MachineLaneKnowsRecord.
	MachineLaneRecords string
	// MachineLaneRecordSource — как ответственный называется в отказе, который
	// читает вызывающий. Часть контракта сообщений.
	MachineLaneRecordSource string
}

CreatedByLane описывает, что полоса выдачи запишет в `created_by_user_id`.

Знание значения и само значение — РАЗНЫЕ поля намеренно: пустая строка в MachineLaneRecords означала бы одновременно «записывается пустое» и «край не знает», а это два разных состояния, и правило на них отвечает по-разному.

func CreatedByLaneForSAKey

func CreatedByLaneForSAKey(principal string, callerIsServiceAccount bool) CreatedByLane

CreatedByLaneForSAKey — полоса ключа служебной учётки: вызывающей машине ответственным записывается владелец аккаунта ЦЕЛЕВОЙ учётки, и край его не знает — резолв идёт в use-case, из репозитория.

func CreatedByLaneForUserToken

func CreatedByLaneForUserToken(principal string, callerIsServiceAccount bool, targetUserID string) CreatedByLane

CreatedByLaneForUserToken — полоса персонального токена: вызывающей машине ответственным записывается ЦЕЛЕВОЙ пользователь, и он же назван запросом.

func (CreatedByLane) ValidateRequested

func (l CreatedByLane) ValidateRequested(requested string) error

ValidateRequested судит присланного ответственного. nil — вход законен; InvalidArgument с именем поля — сервис его не запишет.

Тексты отказов — часть контракта и здесь дословно те, что полосы произносили до сведения: сведение правила не есть повод переписать сообщения.

type ListOperationsUseCase

type ListOperationsUseCase struct {
	// contains filtered or unexported fields
}

ListOperationsUseCase lists the operations recorded for a single resource id.

func NewListOperationsUseCase

func NewListOperationsUseCase(opsRepo operations.Repo) *ListOperationsUseCase

NewListOperationsUseCase wires the use-case to the operations repo.

func (*ListOperationsUseCase) Execute

func (u *ListOperationsUseCase) Execute(ctx context.Context, resourceID string, pageSize int64, pageToken string) ([]operations.Operation, string, error)

Execute returns the resource's operations (cursor-paginated) and the next_page_token. Failures are classified by MapOperationsListErr.

type ListScan

type ListScan struct {
	Rows   int
	Checks int
}

ListScan — накопитель стоимости одной страницы. Считает по мере догрузок и отдаётся регистратору один раз, на выходе.

func (*ListScan) AddBatch

func (s *ListScan) AddBatch(rows int)

AddBatch учитывает одну догрузку: её строки и одно обращение к модели.

func (*ListScan) Report

func (s *ListScan) Report(ctx context.Context, rec ListScanRecorder, resource string)

Report отдаёт накопленное регистратору. Безопасен на нулевом регистраторе: вызывающему не нужно знать, провязано ли наблюдение.

type ListScanRecorder

type ListScanRecorder interface {
	ObserveListScan(ctx context.Context, resource string, rows, checks int)
}

ListScanRecorder принимает стоимость одной отданной страницы.

resource — вид ресурса («account», «project», …), чтобы дорогая поверхность была отличима от дешёвой; rows — сколько строк рассмотрено всеми догрузками вместе; checks — сколько раз спрошена модель прав.

type NoopListScanRecorder

type NoopListScanRecorder struct{}

NoopListScanRecorder — умолчание для мест, где наблюдение не провязано (пробы, инструменты). Именованный тип, а не nil-проверка у каждого вызывающего: «не провязано» обязано быть решением, а не забытой ветвью.

func (NoopListScanRecorder) ObserveListScan

func (NoopListScanRecorder) ObserveListScan(context.Context, string, int, int)

ObserveListScan ничего не делает.

type VisibleCursor

type VisibleCursor struct {
	CreatedAt time.Time
	ID        string
}

VisibleCursor — the boundary of a visible page: the last row RETURNED, in keyset order.

func DecodeVisiblePageToken

func DecodeVisiblePageToken(field, token string) (*VisibleCursor, error)

DecodeVisiblePageToken parses a token of this form. An empty token is the first page and yields (nil, nil) — absence is representable apart from any value. Anything else that is not this form is INVALID_ARGUMENT with the contract tone, including a token of the previous form.

Jump to

Keyboard shortcuts

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