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 ¶
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 ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
GetTenantID reports the tenant the row belongs to.