interfaces

package
v0.0.0-...-b75b351 Latest Latest
Warning

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

Go to latest
Published: Aug 27, 2026 License: LGPL-2.1 Imports: 9 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ActionCreate      = "create"
	ActionUpdate      = "update"
	ActionDelete      = "delete"
	ActionSetArchived = "set_archived"
	ActionGetById     = "get_by_id"
	ActionGetByUnique = "get_by_unique"
	ActionSearch      = "search"
	ActionExists      = "exists"
	ActionGetSchema   = "get_schema"
	// ActionComputeField evaluates one function-kind computed field against an unsaved model.
	ActionComputeField = "compute_field"
)

Built-in action names. The action map is per-engine, so plain verbs cannot collide across resources. When a globally unique identity is needed, use "{resourceName}.{actionName}".

View Source
const (
	ActionTypeCreate        = ActionType("Create")
	ActionTypeDelete        = ActionType("Delete")
	ActionTypeRead          = ActionType("Read")
	ActionTypeUpdatePatch   = ActionType("UpdatePatch")
	ActionTypeUpdateReplace = ActionType("UpdateReplace")

	// ActionTypeGeneric is for actions whose semantics are none of the CRUD verbs —
	// an operation on a resource, such as "exists" or "send_invitation".
	// It maps to POST so the action may carry a request body.
	ActionTypeGeneric = ActionType("Generic")
)
View Source
const (
	PermissionCreate      = "create"
	PermissionRead        = "read"
	PermissionUpdate      = "update"
	PermissionDelete      = "delete"
	PermissionSetArchived = "set_archived"
)

Permission action codes, matching what the IAM application services assert.

View Source
const (
	CrudActionCreate      = CrudAction(ActionCreate)
	CrudActionUpdate      = CrudAction(ActionUpdate)
	CrudActionDelete      = CrudAction(ActionDelete)
	CrudActionSetArchived = CrudAction(ActionSetArchived)
	CrudActionGetById     = CrudAction(ActionGetById)
	CrudActionGetByUnique = CrudAction(ActionGetByUnique)
	CrudActionSearch      = CrudAction(ActionSearch)
	CrudActionExists      = CrudAction(ActionExists)
	CrudActionGetSchema   = CrudAction(ActionGetSchema)
)

Values match the ActionXxx action-name constants verbatim.

Variables

View Source
var RestPathRegex = regexp.MustCompile(`^:?[a-zA-Z0-9_]+(/:?[a-zA-Z0-9_]+)*$`)

RestPathRegex accepts slash-separated segments of [a-zA-Z0-9_], where a segment may be an Echo path param (":name"). Hyphens are deliberately excluded: the word separator is "_".

Functions

This section is empty.

Types

type ActionAfterValidationFn

type ActionAfterValidationFn = corecrud.AfterValidationSuccessFn[*DynamicEntity]

ActionAfterValidationFn runs after successful schema validation.

type ActionBeforeValidationFn

type ActionBeforeValidationFn = corecrud.BeforeValidationFn[*DynamicEntity]

ActionBeforeValidationFn may sanitize or enrich the model before schema validation.

type ActionResult

type ActionResult = dyn.OpResult[any]

ActionResult is the outcome of any action. The spec writes a non-generic "OpResult"; the real type is generic, so actions use OpResult[any] and callers type-assert Data to the shape the action documents:

  • dmodel.DynamicFields for single-record actions
  • dyn.PagedResultData[dmodel.DynamicFields] for search
  • dyn.ExistsResultData for exists
  • dyn.MutateResultData for delete/update/set_archived

type ActionType

type ActionType string

ActionType classifies an action for the REST engine, which maps it to an HTTP method. It is mandatory as soon as an action declares a RestPath, and ignored otherwise.

func (ActionType) HttpMethod

func (this ActionType) HttpMethod() string

HttpMethod maps the action type to the HTTP verb the REST engine registers it under. It returns an empty string for an invalid type; callers validate before registering.

func (ActionType) IsValid

func (this ActionType) IsValid() bool

IsValid reports whether the value is one of the six declared action types.

func (ActionType) String

func (this ActionType) String() string

type ActionValidateExtraFn

type ActionValidateExtraFn = corecrud.UpdateValidateExtraFn[*DynamicEntity]

ActionValidateExtraFn performs validation that the schema cannot express.

It is aliased to corecrud's *update* hook shape, the wider of the two, so that one type serves every action. On update and delete, foundModel is the stored record, fetched by the crud helper itself. On create there is no stored record and foundModel is nil, so a hook that reads it must nil-check first.

type ComputeFnRequest

type ComputeFnRequest struct {
	// SchemaName and FieldName identify what is being computed, so one function can serve several
	// fields or several schemas.
	SchemaName string
	FieldName  string

	// Models are the rows to compute over: a page of rows on a read, exactly one on a
	// meta/compute call, where it is the unsaved model the client posted.
	Models []dmodel.DynamicFields

	// Args carries the caller-supplied extras of a meta/compute call. Nil on a read.
	Args map[string]any
}

ComputeFnRequest is what a computed-field function is given.

type ComputedFieldFn

type ComputedFieldFn func(ctx corectx.Context, req ComputeFnRequest) ([]any, error)

ComputedFieldFn produces the value of a "function"-kind computed field.

It receives the whole page at once rather than one row at a time: a search returns up to a page of rows, and a per-row signature would turn any lookup the function performs into an N+1. It must return exactly one value per row in Models, in the same order; a length mismatch is an error, never a partial fill.

The function may resolve services from the dependency container. Resolve them once, when the module registers the function, and close over them — resolving per call walks the DI graph on every read.

type CrudAction

type CrudAction string

type DynamicActionDefinition

type DynamicActionDefinition struct {
	// ActionName is mandatory and unique within the engine.
	ActionName string

	// ActionType decides the HTTP method the REST engine registers this action under.
	// Mandatory as soon as RestPath is set, and ignored when the action is not exposed.
	ActionType ActionType

	// RestPath is the route path relative to the engine's RoutePath(), and may carry Echo
	// path params (":id"). An empty string means the resource base path itself.
	// Segments are [a-zA-Z0-9_]: the word separator is "_", hyphens are rejected.
	// Leave both this and ActionType unset to keep the action off the REST surface.
	RestPath string

	// RestHandler optionally replaces the generic handler. When set, it owns request
	// binding and response shaping, and the engine only registers the route for it.
	RestHandler echo.HandlerFunc

	// ParamSchema is optional. When provided, the pipeline validates params against it
	// before MainProcess runs. It does not gate the validator hooks below, which the crud
	// helper runs against the resource's own schema.
	ParamSchema func() *dmodel.ModelSchema

	// ValidateAsEdit runs the schema validation in "for edit" mode, which skips absent
	// fields and no-update fields. Set it for partial-update actions.
	ValidateAsEdit bool

	// KeysToFetch is optional. When provided, the engine fetches the identified record
	// and hands it to ValidateExtra as foundModel.
	KeysToFetch KeysToFetchFn

	// Permission is the action code to assert, e.g. "read", "create".
	// An empty string skips the permission check.
	Permission string

	// PermissionScope overrides the engine's default scope for this action only.
	PermissionScope *requestguard.ResourceScope

	// IsOrgScoped confines the action to one organization: the REST caller must supply
	// "?org_id=", the value must name an org the caller belongs to, and the action only ever
	// sees records of that org.
	//
	// A nil value means true. Org scoping is the default because the unsafe direction is the
	// silent one: an action that forgot to declare it would otherwise expose every org's rows
	// to anyone holding a grant. Opt out with util.ToPtr(false), and only for a resource that
	// genuinely has no owning org — schema metadata, or a resource with no org column at all.
	//
	// It has no effect on a resource whose schema declares no org_id field; such a resource
	// cannot be org-filtered and is left alone.
	IsOrgScoped *bool

	// PrimarySchema names the parent resource this action hangs off, and nests its REST route
	// under it:
	//
	//	/{PrimarySchema}/:{PrimaryRestIdParam}/{engine.RoutePath()}/{RestPath}
	//
	// so that the full path of a nested get-by-id reads
	// "/{primary-schema}/{primary-id}/{current-schema}/{current-id}".
	// Leave it nil for a top-level resource, which is the common case.
	PrimarySchema *string

	// PrimaryRestIdParam names the path parameter carrying the parent id, and is mandatory
	// whenever PrimarySchema is set. The value lands in the action params under this name.
	// Segments follow RestPathRegex: [a-zA-Z0-9_], the word separator is "_".
	PrimaryRestIdParam *string

	// The validator hooks. The definition is the single place they live: the service reads
	// them from here and passes them to the crud helper unchanged. There is no per-call way
	// to supply one, so what runs on a create is answerable by reading this definition alone.
	//
	// ModifyAction replaces these fields rather than chaining them, so a module attaching a
	// guard to an action that may already have one reads the existing hook first and calls it
	// from its own — see rejectArchivedOnCreate in inventory/dynamicengines.
	BeforeValidation       ActionBeforeValidationFn
	AfterValidationSuccess ActionAfterValidationFn
	ValidateExtra          ActionValidateExtraFn

	// MainProcess is mandatory.
	MainProcess DynamicActionProcessFn
}

DynamicActionDefinition declares one action on a resource engine.

func (DynamicActionDefinition) IsNested

func (this DynamicActionDefinition) IsNested() bool

IsNested reports whether this action's REST route hangs off a parent resource.

func (DynamicActionDefinition) OrgScoped

func (this DynamicActionDefinition) OrgScoped() bool

OrgScoped resolves IsOrgScoped, defaulting an unset field to true. Every call site asks through this method rather than reading the pointer, so the "nil means org-scoped" default cannot be forgotten in one place and honoured in another.

type DynamicActionDelta

type DynamicActionDelta struct {
	// ActionName is mandatory and must name an existing action.
	ActionName string

	// ActionType, RestPath and RestHandler are plain values: a zero value keeps the existing
	// one. Withdrawing an action from the REST surface is therefore not expressible through
	// a delta — routes are registered once at startup, so there is nothing to withdraw from.
	ActionType  ActionType
	RestPath    string
	RestHandler echo.HandlerFunc

	ParamSchema func() *dmodel.ModelSchema

	// ValidateAsEdit is a pointer so that overriding it back to false is expressible.
	ValidateAsEdit *bool

	KeysToFetch KeysToFetchFn

	// Permission is a pointer so that overriding it to "" (skip the check) is expressible.
	Permission *string

	PermissionScope *requestguard.ResourceScope

	// IsOrgScoped is a pointer for the same reason it is one on the definition: nil means
	// "keep what the action already declared", and util.ToPtr(false) withdraws org scoping.
	IsOrgScoped *bool

	PrimarySchema      *string
	PrimaryRestIdParam *string

	BeforeValidation       ActionBeforeValidationFn
	AfterValidationSuccess ActionAfterValidationFn
	ValidateExtra          ActionValidateExtraFn
	MainProcess            DynamicActionProcessFn
}

DynamicActionDelta overrides fields of an already defined action. Every field except ActionName is optional; a nil field keeps the existing value.

type DynamicActionProcessFn

type DynamicActionProcessFn func(ctx corectx.Context, input ProcessInput) (*ActionResult, error)

DynamicActionProcessFn is the main business processing function of an action.

type DynamicEntity

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

DynamicEntity is the schema-agnostic domain model of the dynamic resource engine. It carries nothing but the field map, which makes it usable as the TDomain type argument of the generic helpers in modules/core/dynamicmodel/crud and .../baserepo, no matter which schema the enclosing engine serves.

func NewDynamicEntity

func NewDynamicEntity() *DynamicEntity

func NewDynamicEntityFrom

func NewDynamicEntityFrom(fields dmodel.DynamicFields) *DynamicEntity

func (*DynamicEntity) GetFieldData

func (this *DynamicEntity) GetFieldData() dmodel.DynamicFields

GetFieldData implements dmodel.DynamicModelGetter.

func (*DynamicEntity) SetFieldData

func (this *DynamicEntity) SetFieldData(data dmodel.DynamicFields)

SetFieldData implements dmodel.DynamicModelSetter.

type DynamicResourceEngine

type DynamicResourceEngine interface {
	// ResourceName is the dynamic-model schema name this engine serves, e.g. "iam_user".
	// It doubles as the permission resource code and as the REST route path segment.
	ResourceName() string

	Schema() *dmodel.ModelSchema

	// RoutePath is the REST path segment, defaulting to ResourceName().
	RoutePath() string
	SetRoutePath(path string)

	// DefaultPermissionScope applies to actions that declare no PermissionScope.
	DefaultPermissionScope() requestguard.ResourceScope
	SetDefaultPermissionScope(scope requestguard.ResourceScope)

	RestApi() DynamicRestApi
	ResourceService() DynamicResourceService
	ResourceRepository() DynamicResourceRepository

	SetRestApi(restApi DynamicRestApi)
	SetResourceService(service DynamicResourceService)
	SetResourceRepository(repository DynamicResourceRepository)

	// DefineAction registers a new action. It fails when the action name is already taken,
	// or when a mandatory field of the definition is missing.
	DefineAction(definition DynamicActionDefinition) error

	// DefineComputedFieldFunction registers the Go implementation of a "function"-kind computed
	// field. The name must match the schema's computed declaration; a schema naming a function no
	// engine defines fails AssertComputedFunctionsDefined at boot rather than at first read.
	//
	// It fails when the name is empty or already registered on this engine.
	DefineComputedFieldFunction(name string, fn ComputedFieldFn) error

	// ComputedFieldFunction returns the registered implementation, if any.
	ComputedFieldFunction(name string) (ComputedFieldFn, bool)

	// AssertComputedFunctionsDefined reports every "function"-kind computed field of this
	// engine's schema whose function was never registered. Called once at startup, after modules
	// have had their chance to register — an engine is built before its module's Init body runs,
	// so this cannot be a construction-time check.
	AssertComputedFunctionsDefined() error

	// ModifyAction overrides fields of an already defined action.
	// It fails when the named action does not exist.
	ModifyAction(delta DynamicActionDelta) error

	// Action returns a copy of the named action definition.
	Action(actionName string) (DynamicActionDefinition, bool)

	// ActionNames lists every defined action name.
	ActionNames() []string

	// ExecuteAction runs the full pipeline of the named action: org scoping, permission
	// check, ParamSchema validation, key fetching, main process.
	//
	// The validator hooks are not part of it. They run inside the crud helper the resource
	// service delegates to, so they fire on a direct service call too.
	ExecuteAction(ctx corectx.Context, actionName string, params dmodel.DynamicFields) (*ActionResult, error)
}

DynamicResourceEngine is the generic CRUD machinery of one resource. A feature module creates one instance per resource in its Init(), defines extra actions on it, and registers it into the dependency container as "engine_{resource name}".

type DynamicResourceEngineRegistry

type DynamicResourceEngineRegistry interface {
	// NewEngine creates and registers an engine for the given dynamic-model schema name.
	// It fails when the schema is unknown or an engine for it already exists.
	// Only the first NewEngineOptions is used, the rest are ignored.
	NewEngine(schemaName string, options ...NewEngineOptions) (DynamicResourceEngine, error)

	// GetEngine returns the engine registered for the given schema name.
	GetEngine(schemaName string) (DynamicResourceEngine, bool)

	// MustGetEngine is GetEngine that panics when the engine is missing.
	MustGetEngine(schemaName string) DynamicResourceEngine

	// AllEngines returns every registered engine.
	AllEngines() []DynamicResourceEngine
}

DynamicResourceEngineRegistry owns every resource engine of the running application. It is a process-wide singleton, reached through dynamicresource.Registry().

Future work: part of the registry data (engine list and their action definitions) is meant to be loadable from the database, so that a resource can be declared without a code change. The seam is EngineFactory: a loader would call NewEngine followed by DefineAction for each persisted definition. That loading is deliberately not implemented yet.

type DynamicResourceRepository

type DynamicResourceRepository interface {
	// Embedding this interface lets the repository be passed directly to the generic
	// helpers of modules/core/dynamicmodel/crud, which expect a dyn.DynamicModelRepository.
	dyn.DynamicModelRepository

	BeginTransaction(ctx corectx.Context) (database.DbTransaction, error)

	Insert(ctx corectx.Context, data dmodel.DynamicFields) (*dyn.OpResult[int], error)
	Update(ctx corectx.Context, data dmodel.DynamicFields) (*dyn.OpResult[dyn.MutateResultData], error)
	DeleteOne(ctx corectx.Context, keys dmodel.DynamicFields) (*dyn.OpResult[dyn.MutateResultData], error)

	// FindByKeys fetches the single record identified by the given primary or unique keys.
	FindByKeys(ctx corectx.Context, keys dmodel.DynamicFields) (*dyn.OpResult[dmodel.DynamicFields], error)

	GetOne(ctx corectx.Context, param dyn.RepoGetOneParam) (*dyn.OpResult[dmodel.DynamicFields], error)
	Search(ctx corectx.Context, param dyn.RepoSearchParam) (*dyn.OpResult[dyn.PagedResultData[dmodel.DynamicFields]], error)
	Exists(ctx corectx.Context, keys []dmodel.DynamicFields) (*dyn.OpResult[dyn.RepoExistsResult], error)
}

DynamicResourceRepository writes and reads the resource records through the SQL query builder.

type DynamicResourceService

type DynamicResourceService interface {
	Create(ctx corectx.Context, params dmodel.DynamicFields) (*dyn.OpResult[dmodel.DynamicFields], error)
	Update(ctx corectx.Context, params dmodel.DynamicFields) (*dyn.OpResult[dyn.MutateResultData], error)
	Delete(ctx corectx.Context, params dmodel.DynamicFields) (*dyn.OpResult[dyn.MutateResultData], error)
	SetArchived(ctx corectx.Context, params dmodel.DynamicFields) (*dyn.OpResult[dyn.MutateResultData], error)

	// GetById fetches one record by primary key. Params carry "id" and optional "fields".
	GetById(ctx corectx.Context, params dmodel.DynamicFields) (*dyn.OpResult[dyn.SingleResultData[dmodel.DynamicFields]], error)

	// GetOne fetches one record by any unique key carried in params.
	GetOne(ctx corectx.Context, params dmodel.DynamicFields) (*dyn.OpResult[dyn.SingleResultData[dmodel.DynamicFields]], error)

	Search(ctx corectx.Context, params dmodel.DynamicFields) (*dyn.OpResult[dyn.PagedResultData[dmodel.DynamicFields]], error)
	Exists(ctx corectx.Context, params dmodel.DynamicFields) (*dyn.OpResult[dyn.ExistsResultData], error)

	// Schema is the dynamic-model schema this service operates on.
	Schema() *dmodel.ModelSchema
}

DynamicResourceService holds the business processing of a resource. It performs validation and orchestration, and invokes the repository when it needs to touch the database. It performs no permission check: that belongs to the engine pipeline, which sits above it.

A module extends it by embedding the default implementation into its own struct and installing that struct with Engine.SetResourceService.

type DynamicRestApi

type DynamicRestApi interface {
	// RegisterRoutes adds every endpoint of the resource to the given route group.
	RegisterRoutes(route *echo.Group, middlewares ...echo.MiddlewareFunc)
}

DynamicRestApi exposes a resource over HTTP.

type KeysToFetchFn

type KeysToFetchFn func(params dmodel.DynamicFields) dmodel.DynamicFields

KeysToFetchFn returns the primary or unique keys identifying the record the engine should auto-fetch, to be handed to MainProcess as ProcessInput.FoundModel.

It does not feed ValidateExtra: on update and delete the crud helper fetches the stored record itself. Declare it only for an action whose MainProcess reads the record.

type NewEngineOptions

type NewEngineOptions struct {
	// CrudActions selects which built-in CRUD actions this engine defines.
	// Nil or empty means all of them, so the zero value keeps the default behavior.
	//
	// An action left out is not registered as a REST route, and the resource service
	// refuses it when invoked directly.
	CrudActions []CrudAction

	// DefaultSearchFields is the field list a search returns when it specifies neither
	// fields nor a resolvable view. When empty, every column of the schema is returned.
	//
	// Primary key fields are always included by the query builder, so listing them here
	// is redundant.
	DefaultSearchFields []string
}

NewEngineOptions customizes an engine at creation time. The zero value is valid and reproduces the default engine behavior.

type ProcessInput

type ProcessInput struct {
	Params dmodel.DynamicFields

	// FoundModel is the record the action's KeysToFetch identified, already fetched by the
	// pipeline. It is nil when the action declares no KeysToFetch, and when the record does
	// not exist.
	//
	// It exists so that a MainProcess needing the record does not re-read a row the pipeline
	// has already read on its behalf.
	FoundModel *dmodel.DynamicFields

	// ResourceService is the engine's service subengine. A module that installed its own
	// extended service via Engine.SetResourceService type-asserts this to its own interface.
	ResourceService DynamicResourceService

	// ResourceRepository is the engine's repository subengine, for actions that need
	// direct database access without going through the service.
	ResourceRepository DynamicResourceRepository
}

ProcessInput is handed to the main processing function of an action.

Jump to

Keyboard shortcuts

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