tenant

package
v0.0.0-...-8c930d8 Latest Latest
Warning

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

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

Documentation

Overview

Package tenant implements the row scoping the public tenant package forwards: the column type whose clauses Gorm applies to every statement of a schema carrying it, the struct a model embeds to carry that column, and the context a statement takes its tenant from. The public package documents how a model opts in; framework code imports this one.

Index

Constants

View Source
const Default = "default"

Default is the tenant everything acts in when the context names none. A deployment in which nothing ever names a tenant runs entirely inside it, which is what makes single tenancy the case that needs no configuration at all.

Variables

View Source
var ErrTenantImmutable = errors.New("tenant: a row cannot be moved to another tenant")

ErrTenantImmutable reports an update carrying a tenant other than the one the caller is acting in.

The column is write-once, so the update could not have moved the row whatever it said. Refusing is what keeps that from being a silent no-op: a client that believed it moved a row and received a success would be wrong about where its data is, and nothing in the response would say so.

View Source
var ErrTenantRequired = errors.New("tenant: a cross-tenant insert must name the tenant the row belongs to")

ErrTenantRequired reports an insert made under a cross-tenant scope without naming the tenant the row belongs to.

A cross-tenant caller is the one caller the framework cannot stamp for: it is acting in every tenant, so there is no single one to write. Refusing is the only safe answer — defaulting would file the row under whichever tenant the framework picked, silently and in the one situation where the caller had the reach to mean any of them.

Functions

func Across

func Across(ctx context.Context) context.Context

Across returns a context spanning every tenant.

Reserved for work that is genuinely platform-wide: a subject whose authorization was not decided in any one tenant, or framework work that has to reach rows in all of them — recomputing what a global record implies for every tenant's roles, for instance.

Nothing about it is implicit. A context that was never given a scope acts in one tenant, so forgetting this call yields too little data rather than too much, and the mistake shows up as something missing rather than as something leaked.

func From

func From(ctx context.Context) (string, bool)

From reports the tenant a context acts in, and whether it acts in exactly one. A cross-tenant context acts in none, so ok is false and the caller has to get the tenant from somewhere it names explicitly.

It exists for the one thing the stamp cannot do for a model: derive something from the tenant before the row is written. A model computing its own key from the tenant has to use the tenant the row will end up carrying, and the value it arrived with is not that.

func In

func In(ctx context.Context, id string) context.Context

In returns a context acting inside one tenant.

It is how code running outside a request — a scheduled job, a startup seed, a command — supplies what a request gets from its authorization. It is not a way to widen a request: a request already carries the tenant it was authorized in, and calling In with another one inside a request hands that request data it was never authorized for.

Types

type ID

type ID string

ID is the tenant column's type, and the whole of the row scoping.

The clause methods below are what Gorm discovers on the field, so a model holding this type is scoped by having it. That is why the scoping cannot be switched off, shadowed by a method on the model, or missed by a code path nobody thought of: it is a property of the column, not of the caller.

The public tenant package does not forward it, so a project cannot name it and a model carries the column through Scope alone, whose tags keep it written once and never null; a field declared with the type by hand would be scoped without either.

func (ID) CreateClauses

func (ID) CreateClauses(f *gormschema.Field) []clause.Interface

func (ID) DeleteClauses

func (ID) DeleteClauses(f *gormschema.Field) []clause.Interface

func (ID) QueryClauses

func (ID) QueryClauses(f *gormschema.Field) []clause.Interface

func (ID) String

func (id ID) String() string

String reports the tenant as plain text, for callers comparing it against a value that came from somewhere without a type.

func (ID) UpdateClauses

func (ID) UpdateClauses(f *gormschema.Field) []clause.Interface

type Scope

type Scope struct {
	// TenantID is the tenant the record belongs to, set when the record is
	// created: the caller's tenant, whatever tenant the request names, unless
	// the caller acts across tenants, which may name the tenant itself.
	TenantID ID `json:"tenant_id,omitempty" gorm:"column:tenant_id;size:191;not null;default:default;<-:create" query:"tenant_id" url:"-"`
}

Scope is embedded by models whose rows each belong to one tenant.

The column is written once, on insert, and never again: the framework stamps it from the context and Gorm's create-only permission keeps every later write off it. A row therefore cannot change hands, and a client cannot move one by sending a different value.

The column is tenant_id on every model rather than a name the model chose. A chosen name would have to be resolved again by everything that consumes it, and three resolutions of one name are three chances to disagree; the framework fixes id, created_at and deleted_at on the same reasoning, and modelschema.ValidateFrameworkColumns refuses a model renaming any of them.

func (*Scope) GetTenantID

func (s *Scope) GetTenantID() string

GetTenantID reports the tenant the row belongs to.

Jump to

Keyboard shortcuts

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