dynamicmodel

package
v0.0.0-...-1b2c9a0 Latest Latest
Warning

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

Go to latest
Published: Sep 19, 2026 License: LGPL-2.1 Imports: 16 Imported by: 0

Documentation

Index

Constants

View Source
const DefaultSearchName = "default"

Variables

This section is empty.

Functions

func DefineFieldIncludeArchived

func DefineFieldIncludeArchived() *dmodel.FieldBuilder

DefineFieldIncludeArchived defines the "include_archived" search query field.

It deliberately carries no Default(false): ModelField.Validate materializes a non-empty default into the *bool, which would erase the nil/false distinction before crud.Search ever sees it. The public-API default is applied in crud.Search instead, so that callers going straight to the repository keep the unfiltered legacy behaviour.

func DefineFieldSearchContext

func DefineFieldSearchContext() *dmodel.FieldBuilder

DefineFieldSearchContext defines the "context" search query field: the whitelisted request values SQL-computed fields may bind inside their filters. It is a free-form JSON object here; the per-definition whitelist and type conversion are enforced where the subquery compiles.

func DefineFieldSearchFields

func DefineFieldSearchFields() *dmodel.FieldBuilder

func DefineFieldSearchGraph

func DefineFieldSearchGraph() *dmodel.FieldBuilder

func DefineFieldSearchLanguage

func DefineFieldSearchLanguage() *dmodel.FieldBuilder

DefineFieldSearchLanguage defines the "language" search query field: which translation of a LangJson column to filter and sort on.

Declared here rather than left undeclared because an undeclared field survives validation only by accident -- ValidateStruct mutates the caller's own struct, and the mapper happens not to zero what the schema did not mention. A refactor to build a fresh target would silently drop it and un-localize every search, with no compile error and no failing test.

The rule is BCP47 shape rather than an enum of the supported locales: that set lives in the essential module, which this package must not import. A well-formed locale the application ships no translations for simply matches nothing, which is the same answer it would give anyway.

func DefineFieldSearchName

func DefineFieldSearchName() *dmodel.FieldBuilder

func DefineFieldSearchPage

func DefineFieldSearchPage() *dmodel.FieldBuilder

func DefineFieldSearchSize

func DefineFieldSearchSize() *dmodel.FieldBuilder

func DeleteOneQuerySchemaBuilder

func DeleteOneQuerySchemaBuilder() *dmodel.ModelSchemaBuilder

func ExistsQuerySchemaBuilder

func ExistsQuerySchemaBuilder() *dmodel.ModelSchemaBuilder

func GetOneQuerySchemaBuilder

func GetOneQuerySchemaBuilder() *dmodel.ModelSchemaBuilder

func InitSubModule

func InitSubModule(params InitParams) error

func ManageAssocsSchemaBuilder

func ManageAssocsSchemaBuilder() *dmodel.ModelSchemaBuilder

func ResolveLocale

func ResolveLocale(ctx corectx.Context) *model.LanguageCode

ResolveLocale returns the acting user's effective locale, memoized for the request.

Returns nil whenever the locale cannot be established -- no resolver installed, no acting user, a failed or panicking read. Every caller must treat nil as "do not localize" rather than as an error: a list that sorts by the raw column is worse than one sorted by the reader's language, but it is a great deal better than a 500.

The memo assumes a request context is used by one goroutine at a time, which is how corectx is used throughout and what every other WithValue caller already assumes.

func SearchQuerySchemaBuilder

func SearchQuerySchemaBuilder() *dmodel.ModelSchemaBuilder

func SetArchivedCommandSchemaBuilder

func SetArchivedCommandSchemaBuilder() *dmodel.ModelSchemaBuilder

func SetLocaleResolver

func SetLocaleResolver(fn LocaleResolver)

SetLocaleResolver installs the process-wide resolver.

Call it once, during module Init, before any request is served.

Types

type BaseDynamicRepository

type BaseDynamicRepository interface {
	Schema() *dmodel.ModelSchema
	BeginTransaction(ctx corectx.Context) (database.DbTransaction, error)
	ExtractClient(ctx corectx.Context) orm.DbClient
	ExecFunc(ctx corectx.Context, sqlFuncName string, sqlFuncArgs ...any) error
	QueryFunc(ctx corectx.Context, sqlFuncName string, sqlFuncArgs ...any) (*sql.Rows, error)

	CheckUniqueCollisions(ctx corectx.Context, data dmodel.DynamicFields) (*OpResult[[][]string], error)
	CountM2m(ctx corectx.Context, param RepoCountM2mParam) (*OpResult[int], error)
	DeleteOne(ctx corectx.Context, keys dmodel.DynamicFields) (*OpResult[int], error)
	Exists(ctx corectx.Context, keys []dmodel.DynamicFields) (*OpResult[RepoExistsResult], error)
	ExistsM2m(ctx corectx.Context, param RepoExistsM2mParam) (bool, error)
	InsertBulk(ctx corectx.Context, data []dmodel.DynamicFields) (*OpResult[int], error)
	GetOne(ctx corectx.Context, param RepoGetOneParam) (*OpResult[dmodel.DynamicFields], error)
	Insert(ctx corectx.Context, data dmodel.DynamicFields) (*OpResult[int], error)
	// ManageM2m inserts and/or deletes junction rows for a finalized many-to-many link to dest schema.
	// Source and destination are identified by id.
	ManageM2m(ctx corectx.Context, param RepoManageM2mParam) (*OpResult[int], error)
	Search(ctx corectx.Context, param RepoSearchParam) (*OpResult[PagedResultData[dmodel.DynamicFields]], error)
	Update(ctx corectx.Context, data dmodel.DynamicFields) (*OpResult[dmodel.DynamicFields], error)
}

type DeleteOneCommand

type DeleteOneCommand struct {
	Id model.Id `json:"id" param:"id"`
}

func (DeleteOneCommand) GetSchema

func (this DeleteOneCommand) GetSchema() *dmodel.ModelSchema

type DeleteOneCommandShape

type DeleteOneCommandShape interface {
	~struct {
		Id model.Id `json:"id" param:"id"`
	}
}

Helper type for generic type constraints.

type DynamicModelPtr

type DynamicModelPtr[T any] interface {
	*T
	dmodel.DynamicModel
}

type DynamicModelRepository

type DynamicModelRepository interface {
	BeginTransaction(ctx corectx.Context) (database.DbTransaction, error)
	GetBaseRepo() BaseDynamicRepository
}

type DynamicModelSetterPtr

type DynamicModelSetterPtr[T any] interface {
	*T
	dmodel.DynamicModelSetter
}

type ExistsQuery

type ExistsQuery struct {
	Ids []model.Id `json:"ids" query:"ids"`
}

type ExistsQueryShape

type ExistsQueryShape interface {
	~struct {
		Ids []model.Id `json:"ids" query:"ids"`
	}
}

Helper type for generic type constraints.

type ExistsResultData

type ExistsResultData struct {
	Existing    []model.Id `json:"existing"`
	NotExisting []model.Id `json:"not_existing"`
}

func (ExistsResultData) Exists

func (this ExistsResultData) Exists(id model.Id) bool

type GetOneQuery

type GetOneQuery struct {
	Id     model.Id `json:"id" param:"id"`
	Fields []string `json:"fields" query:"fields"`
}

type InitParams

type InitParams struct {
	dig.In
}

type LocaleResolver

type LocaleResolver func(ctx corectx.Context) *model.LanguageCode

LocaleResolver reports the effective display locale for the acting user, or nil when none can be determined.

It is a function type rather than an interface because the settings module -- the only thing that can answer this -- imports THIS package. An interface would have to name the settings types to be useful, which would close the loop into an import cycle and abort the build. A func value carries no package dependency on its provider, so the edge stays one-directional and the module that owns both ends pushes the implementation in at boot.

type MutateResultData

type MutateResultData struct {
	AffectedCount int                 `json:"affected_count"`
	AffectedAt    model.ModelDateTime `json:"affected_at"`
	Etag          model.Etag          `json:"etag,omitempty"`
}

type NewBaseDynamicRepositoryFn

type NewBaseDynamicRepositoryFn func(param NewBaseRepoParam) BaseDynamicRepository

type NewBaseRepoParam

type NewBaseRepoParam struct {
	Client       orm.DbClient
	ConfigSvc    config.ConfigService
	Logger       logging.LoggerService
	QueryBuilder orm.QueryBuilder
	Schema       *dmodel.ModelSchema
}

type OpResult

type OpResult[TData any] struct {
	// The result data when success. It is only meaningful if HasData is true and ClientErrors is nil.
	// Otherwise, it could be nil or an empty struct.
	Data TData

	// Contains validation errors, business errors...,
	// or nil if there is no violation.
	ClientErrors ft.ClientErrors

	// Indicates whether "Data" is present (non-zero value: non-empty struct, non-empty array, etc.).
	//
	// If ClientErrors is nil but HasData is false,
	// it means the query is successfull but no data is found.
	HasData bool
}

type PagedResultData

type PagedResultData[T any] struct {
	Items []T `json:"items"`
	Total int `json:"total"`
	Page  int `json:"page"`
	Size  int `json:"size"`

	// List of fields determined by `View` which are requested to read.
	DesiredFields []string `json:"desired_fields"`

	// Subset of `DesiredFields` that user doesn't have permission to read.
	// These fields will be returned as `nil` values. User can be aware of their existence
	// and ask for permission to read them.
	MaskedFields []string `json:"masked_fields"`

	// The etag of the schema used to generate the response.
	// This is used to check if the schema has changed since the last request.
	// If the schema has changed, the client should fetch the new schema and update its cache.
	SchemaEtag string `json:"schema_etag"`
}

type PagingOptions

type PagingOptions struct {
	Page int `json:"page" query:"page"`
	Size int `json:"size" query:"size"`
}

type RepoBeforeInsertM2mFn

type RepoBeforeInsertM2mFn func(ctx corectx.Context, dbRecords []dmodel.DynamicFields) error

type RepoCountM2mParam

type RepoCountM2mParam struct {
	M2mEdge string   `json:"m2m_edge"`
	SrcId   model.Id `json:"src_id"`
}

RepoCountM2mParam counts junction rows for one source record on an outgoing many-to-many edge.

type RepoExistsM2mParam

type RepoExistsM2mParam struct {
	M2mEdge string    `json:"m2m_edge"`
	SrcId   model.Id  `json:"src_id"`
	DestId  *model.Id `json:"dest_id"`
}

RepoExistsM2mParam checks the junction for an outgoing many-to-many edge on the repository schema. When dest_id is omitted, null, or empty, checks that SrcId has at least one junction row; otherwise checks the (SrcId, DestId) pair.

type RepoExistsResult

type RepoExistsResult struct {
	Existing    []dmodel.DynamicFields `json:"existing"`
	NotExisting []dmodel.DynamicFields `json:"not_existing"`
}

RepoExistsResult is the raw batch existence outcome per filter map (same order as input keys).

type RepoGetOneParam

type RepoGetOneParam struct {
	Filter dmodel.DynamicFields
	Fields []string
}

type RepoM2mAssociation

type RepoM2mAssociation struct {
	SrcKeys  dmodel.DynamicFields
	DestKeys dmodel.DynamicFields
}

RepoM2mAssociation is one row to insert into the M2M junction: source entity keys and peer entity keys.

type RepoManageM2mParam

type RepoManageM2mParam struct {
	DestSchemaName string
	SrcId          model.Id
	// Field name for the source ID used to include in the error message.
	SrcIdFieldForError string
	// M2M edge name on the source schema.
	SrcEdgeName      string
	AssociatedIds    datastructure.Set[model.Id]
	DisassociatedIds datastructure.Set[model.Id]
	BeforeInsert     RepoBeforeInsertM2mFn
}

type RepoSearchParam

type RepoSearchParam struct {
	Fields   []string
	Filter   []dmodel.DynamicFields
	Page     int
	Size     int
	Graph    *dmodel.SearchGraph
	Language *model.LanguageCode

	// ComputedContext carries the request's whitelisted context values for SQL-computed
	// fields; the query builder binds them into "${ctx.key}" filter references.
	ComputedContext map[string]any

	// IncludeArchived controls whether archived records take part in the search.
	// It is tri-state on purpose:
	//   - nil: no filtering at all. This is the legacy contract that every internal lookup
	//     relies on, e.g. models.FindTemplateVariants which needs archived rows by design.
	//     Leave it nil when reusing Search to fetch a single known record.
	//   - false: the repository prepends "is_archived = false" to the search graph.
	//     crud.Search sets this when the caller omitted the query parameter, so the public
	//     HTTP API hides archived records by default.
	//   - true: no filtering; archived records are returned alongside active ones.
	//
	// The condition is only prepended on schemas that actually carry the is_archived column
	// (it comes from the optional basemodel.ArchivableModelSchemaBuilder mixin).
	//
	// Note this filters the searched schema only. Nested edge columns are hydrated by
	// follow-up queries that are not filtered, so requesting fields=groups.name with
	// IncludeArchived=false excludes archived rows of this schema but still hydrates
	// archived rows of the edge.
	IncludeArchived *bool
}

type SearchQuery

type SearchQuery struct {
	Fields []string `json:"fields" query:"fields"`
	Page   int      `json:"page" query:"page"`
	Size   int      `json:"size" query:"size"`
	// Optional search graph for advanced search
	Graph *dmodel.SearchGraph `json:"graph" query:"graph"`
	// Optional language code to filter fields with LangJson type
	Language *model.LanguageCode `json:"language" query:"language"`

	// Optional flag to include archived records in the result.
	// When omitted, crud.Search treats it as false, so archived records are hidden by default.
	// Set it to true to search archived and active records together.
	IncludeArchived *bool `json:"include_archived" query:"include_archived"`

	// Determines the fields to be returned in the response.
	// If not specified, return all fields that user has permission on.
	// Otherwise, return fields specified by the search name.
	SearchName *string `json:"search_name" query:"search_name"`

	// Optional whitelisted context values for SQL-computed fields (aggregate/exists/lookup)
	// whose filter declares context dependencies. Each value binds a "${ctx.key}" reference;
	// a computed field whose declared key is absent here fails with a validation error.
	Context map[string]any `json:"context" query:"context"`
}

func (SearchQuery) GetSchema

func (SearchQuery) GetSchema() *dmodel.ModelSchema

type SetIsArchivedCommand

type SetIsArchivedCommand struct {
	Id         model.Id   `json:"id" param:"id"`
	Etag       model.Etag `json:"etag" param:"etag"`
	IsArchived *bool      `json:"is_archived" param:"is_archived"` // Pointer to trigger missing field error
}

type SingleMetaData

type SingleMetaData struct {

	// List of fields determined by `View` which are requested to read.
	DesiredFields []string `json:"desired_fields"`

	// Subset of `DesiredFields` that user doesn't have permission to read.
	// These fields will be returned as `nil` values. User can be aware of their existence
	// and ask for permission to read them.
	MaskedFields []string `json:"masked_fields"`

	// The etag of the schema used to generate the response.
	// This is used to check if the schema has changed since the last request.
	// If the schema has changed, the client should fetch the new schema and update its cache.
	SchemaEtag string `json:"schema_etag"`
}

type SingleResultData

type SingleResultData[T any] struct {
	Item T              `json:"item"`
	Meta SingleMetaData `json:"meta"`
}

type Validatable

type Validatable interface {
	Validate() ft.ClientErrors
}

type ValidationFlow

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

func StartValidationFlow

func StartValidationFlow(startWith ...Validatable) *ValidationFlow

func StartValidationFlowCopy

func StartValidationFlowCopy(initClientErrs *ft.ClientErrors, startWith ...Validatable) *ValidationFlow

func (*ValidationFlow) End

func (this *ValidationFlow) End() (ft.ClientErrors, error)

func (*ValidationFlow) Start

func (this *ValidationFlow) Start() *ValidationFlow

func (*ValidationFlow) StartCopy

func (this *ValidationFlow) StartCopy(initialErrs *ft.ClientErrors) *ValidationFlow

func (*ValidationFlow) Step

func (this *ValidationFlow) Step(fn func(vErrs *ft.ClientErrors) error, ignoreValidationError ...bool) (out *ValidationFlow)

func (*ValidationFlow) StepS

func (this *ValidationFlow) StepS(fn func(vErrs *ft.ClientErrors, stop func()) error, ignoreValidationError ...bool) (out *ValidationFlow)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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