package cabana import ( "context" "gorm.io/gorm" ) type txContextKey struct{} // withTx returns ctx carrying the transaction a write is running in. func withTx(ctx context.Context, tx *gorm.DB) context.Context { if tx == nil { return ctx } return context.WithValue(ctx, txContextKey{}, tx) } // TxFromContext returns the database transaction a write route is running in, // for the context handed to a lifecycle hook (pact.FormBeforeCreate and the // other Form and Relation hooks) or a scope (pact.FormExtendQuery) that runs // inside it. A hook that has to read the database should use this handle // instead of the application pool: it then sees the same snapshot as the write // and does not take a second connection while the transaction holds row locks. // The handle is valid only until the hook returns. The second result is false // on routes that do not run in a transaction, such as the list. func TxFromContext(ctx context.Context) (*gorm.DB, bool) { if ctx == nil { return nil, false } tx, ok := ctx.Value(txContextKey{}).(*gorm.DB) return tx, ok && tx != nil } type virtualFieldsKey struct{} // withVirtualFields returns ctx carrying the virtual field values of a save. func withVirtualFields(ctx context.Context, values map[string]any) context.Context { if values == nil { values = map[string]any{} } return context.WithValue(ctx, virtualFieldsKey{}, values) } // VirtualFieldsFromContext returns the values an administrator submitted for // the form's virtual fields (pact.FormVirtualFields), keyed by field name, for // the context handed to a Form hook (pact.FormBeforeCreate, // pact.FormAfterCreate, pact.FormBeforeUpdate, pact.FormAfterUpdate). Only // fields that were present in the request body and whose `context` allows the // operation are in the map, so a missing key means "not submitted". Values // are scalars as decoded from the JSON body: a string, a bool, a json.Number // or nil. The map is a copy. The second result is false outside a create or // update save. func VirtualFieldsFromContext(ctx context.Context) (map[string]any, bool) { if ctx == nil { return nil, false } values, ok := ctx.Value(virtualFieldsKey{}).(map[string]any) if !ok { return nil, false } out := make(map[string]any, len(values)) for name, value := range values { out[name] = value } return out, true } type fieldWritesKey struct{} // FieldWrite is one value a save wrote for a plugin field type // (pact.HasFormFieldTypes): Locale is the content locale of a translatable // type's value and empty otherwise, Default is true when the value went to // the host column, and Value is what the type's Decode returned. type FieldWrite struct { Locale string Default bool Value any } // withFieldWrites returns ctx carrying the plugin field values a save writes. func withFieldWrites(ctx context.Context, writes map[string][]FieldWrite) context.Context { if writes == nil { writes = map[string][]FieldWrite{} } return context.WithValue(ctx, fieldWritesKey{}, writes) } // WithFieldWrites returns ctx carrying writes as a save would, so a test can // call a Form hook that reads FieldWritesFromContext. The map is copied. func WithFieldWrites(ctx context.Context, writes map[string][]FieldWrite) context.Context { return withFieldWrites(ctx, copyFieldWrites(writes)) } // FieldWritesFromContext returns the values a create or update save // actually writes for the form's plugin field types, keyed by field name, for // the context handed to a Form hook (pact.FormBeforeCreate, // pact.FormAfterCreate, pact.FormBeforeUpdate, pact.FormAfterUpdate). A field // or locale whose submitted value equals the value the form was served is // not written and not listed; a translatable type lists one entry per // written locale. The map is a copy. The second result is false outside a // create or update save. func FieldWritesFromContext(ctx context.Context) (map[string][]FieldWrite, bool) { if ctx == nil { return nil, false } writes, ok := ctx.Value(fieldWritesKey{}).(map[string][]FieldWrite) if !ok { return nil, false } return copyFieldWrites(writes), true } func copyFieldWrites(in map[string][]FieldWrite) map[string][]FieldWrite { out := make(map[string][]FieldWrite, len(in)) for field, list := range in { out[field] = append([]FieldWrite(nil), list...) } return out }