[{"data":1,"prerenderedAt":5884},["ShallowReactive",2],{"search-sections-cereal":3,"nav-cereal":1144,"content-tree-cereal":1197,"footer-resources":1221,"content-/v0.1.2/reference/errors":3251,"surround-/v0.1.2/reference/errors":5882},[4,10,14,20,25,30,35,41,46,51,56,61,66,69,74,79,84,89,94,99,104,109,112,117,121,126,131,136,141,146,151,156,161,166,171,176,181,186,191,196,201,205,209,213,217,222,227,232,237,242,247,252,257,262,267,270,275,280,285,289,294,299,304,308,312,317,322,327,332,337,342,347,352,357,361,366,371,376,381,386,391,396,401,406,411,416,421,426,431,436,440,445,450,455,460,465,470,475,480,485,490,495,499,504,509,514,519,524,529,534,539,544,549,553,557,562,567,572,577,582,587,592,597,602,607,612,616,621,626,631,636,641,646,651,656,660,664,669,673,678,683,688,693,698,702,706,711,716,719,724,729,734,739,744,748,753,757,763,768,773,778,783,788,793,798,803,808,812,817,822,827,832,837,842,846,851,855,860,865,870,875,880,884,889,894,899,904,909,914,919,924,929,933,938,943,948,951,955,959,963,967,971,976,981,985,989,994,999,1004,1009,1014,1019,1024,1028,1033,1038,1043,1047,1052,1057,1062,1066,1070,1075,1080,1085,1090,1095,1100,1105,1109,1114,1119,1124,1129,1134,1139],{"id":5,"title":6,"titles":7,"content":8,"level":9},"/v0.1.2/overview","Overview",[],"Boundary-aware serialization for Go",1,{"id":11,"title":6,"titles":12,"content":13,"level":9},"/v0.1.2/overview#overview",[],"Data crosses boundaries constantly. Each crossing demands different treatment. Cereal provides boundary-aware serialization: transform data differently depending on where it's going or where it came from. type User struct {\n    ID       string `json:\"id\"`\n    Email    string `json:\"email\" store.encrypt:\"aes\" load.decrypt:\"aes\" send.mask:\"email\"`\n    Password string `json:\"password\" receive.hash:\"argon2\"`\n    SSN      string `json:\"ssn\" send.mask:\"ssn\"`\n    Token    string `json:\"token\" send.redact:\"[REDACTED]\"`\n}\n\nfunc (u User) Clone() User { return u }\n\nfunc main() {\n    ctx := context.Background()\n\n    proc, _ := cereal.NewProcessor[User]()\n    enc, _ := cereal.AES(key)\n    proc.SetEncryptor(cereal.EncryptAES, enc)\n\n    // Receive: hash password from incoming request\n    user, _ := proc.Receive(ctx, incomingUser)\n\n    // Store: encrypt email before saving to database\n    stored, _ := proc.Store(ctx, user)\n\n    // Load: decrypt email when reading from database\n    loaded, _ := proc.Load(ctx, stored)\n\n    // Send: mask email and SSN, redact token for API response\n    sanitized, _ := proc.Send(ctx, loaded)\n} Four boundaries. Four operations. Tags declare which transforms apply where.",{"id":15,"title":16,"titles":17,"content":18,"level":19},"/v0.1.2/overview#the-four-boundaries","The Four Boundaries",[6],"BoundaryOperationDirectionUse CaseReceiveReceive()External → AppAPI requests, webhooks, eventsLoadLoad()Storage → AppDatabase reads, cache hitsStoreStore()App → StorageDatabase writes, cache setsSendSend()App → ExternalAPI responses, outbound events",2,{"id":21,"title":22,"titles":23,"content":24,"level":19},"/v0.1.2/overview#architecture","Architecture",[6],"┌─────────────────────────────────────────────────────────────────┐\n│                           Codec                                 │\n│                                                                 │\n│  ┌─────────────────────────────────────────────────────────┐   │\n│  │                   Processor[T]                           │   │\n│  │  ┌──────────────┐   ┌──────────────┐   ┌─────────────┐  │   │\n│  │  │ Transforms   │   │    Codec     │   │  Sentinel   │  │   │\n│  │  │              │   │  (optional)  │   │  Metadata   │  │   │\n│  │  │  Encryptors  │   │  Marshal()   │   │  (cached)   │  │   │\n│  │  │  Hashers     │   │  Unmarshal() │   │             │  │   │\n│  │  │  Maskers     │   │              │   │             │  │   │\n│  │  └──────────────┘   └──────────────┘   └─────────────┘  │   │\n│  └─────────────────────────────────────────────────────────┘   │\n│                                                                 │\n│  ┌─────────────────────────────────────────────────────────┐   │\n│  │                      Providers                           │   │\n│  │   JSON    XML    YAML    MessagePack    BSON            │   │\n│  └─────────────────────────────────────────────────────────┘   │\n└─────────────────────────────────────────────────────────────────┘ The Processor[T] provides boundary-aware transforms. A codec may optionally be set for byte-level serialization via the secondary API.",{"id":26,"title":27,"titles":28,"content":29,"level":19},"/v0.1.2/overview#capabilities","Capabilities",[6],"Encryption - AES-GCM, RSA-OAEP, envelope encryption. Applied on store, reversed on load. Hashing - SHA-256, SHA-512, Argon2, bcrypt. Applied on receive for incoming data. Masking - Content-aware partial masking. SSN becomes ***-**-6789. Email becomes a***@example.com. Eight built-in patterns. Redaction - Full replacement. send.redact:\"[HIDDEN]\" replaces the entire value. Override Interfaces - Implement Encryptable, Hashable, Maskable, or Redactable to bypass reflection.",{"id":31,"title":32,"titles":33,"content":34,"level":19},"/v0.1.2/overview#design-principles","Design Principles",[6],"",{"id":36,"title":37,"titles":38,"content":39,"level":40},"/v0.1.2/overview#boundary-awareness","Boundary Awareness",[6,32],"Different boundaries need different transforms: type Secret struct {\n    // Encrypt when storing, decrypt when loading\n    Value string `store.encrypt:\"aes\" load.decrypt:\"aes\"`\n\n    // Only mask when sending externally\n    Key string `send.mask:\"card\"`\n}",3,{"id":42,"title":43,"titles":44,"content":45,"level":40},"/v0.1.2/overview#type-safety","Type Safety",[6,32],"Processors are generic. A Processor[User] only accepts User values. proc, _ := cereal.NewProcessor[User]()\nproc.Store(ctx, user)         // OK\nproc.Store(ctx, otherType)    // Compile error",{"id":47,"title":48,"titles":49,"content":50,"level":40},"/v0.1.2/overview#non-destructive","Non-Destructive",[6,32],"Original values are never modified. The processor clones before transforming. original := User{Email: \"alice@example.com\"}\nsanitized, _ := proc.Send(ctx, original)\n\n// original.Email is still \"alice@example.com\"\n// sanitized contains the masked value",{"id":52,"title":53,"titles":54,"content":55,"level":40},"/v0.1.2/overview#provider-agnostic","Provider Agnostic",[6,32],"Swap codecs without changing your types. proc, _ := cereal.NewProcessor[User]()\n\nproc.SetCodec(json.New())\nproc.SetCodec(yaml.New())\nproc.SetCodec(bson.New())\n\n// Same type, same tags, different wire format",{"id":57,"title":58,"titles":59,"content":60,"level":40},"/v0.1.2/overview#observable","Observable",[6,32],"All operations emit signals via capitan for metrics and tracing integration. html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":62,"title":63,"titles":64,"content":65,"level":9},"/v0.1.2/learn/quickstart","Quickstart",[],"Get started with codec in minutes",{"id":67,"title":63,"titles":68,"content":34,"level":9},"/v0.1.2/learn/quickstart#quickstart",[],{"id":70,"title":71,"titles":72,"content":73,"level":19},"/v0.1.2/learn/quickstart#requirements","Requirements",[63],"Go 1.24 or later.",{"id":75,"title":76,"titles":77,"content":78,"level":19},"/v0.1.2/learn/quickstart#installation","Installation",[63],"go get github.com/zoobzio/cereal",{"id":80,"title":81,"titles":82,"content":83,"level":19},"/v0.1.2/learn/quickstart#basic-codec-usage","Basic Codec Usage",[63],"For simple marshaling without transforms, use a codec directly: package main\n\nimport (\n    \"fmt\"\n\n    \"github.com/zoobzio/cereal/json\"\n)\n\ntype Message struct {\n    ID   string `json:\"id\"`\n    Text string `json:\"text\"`\n}\n\nfunc main() {\n    codec := json.New()\n\n    msg := Message{ID: \"1\", Text: \"Hello\"}\n\n    // Marshal\n    data, err := codec.Marshal(msg)\n    if err != nil {\n        panic(err)\n    }\n    fmt.Println(string(data))\n    // {\"id\":\"1\",\"text\":\"Hello\"}\n\n    // Unmarshal\n    var result Message\n    err = codec.Unmarshal(data, &result)\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"%+v\\n\", result)\n    // {ID:1 Text:Hello}\n}",{"id":85,"title":86,"titles":87,"content":88,"level":19},"/v0.1.2/learn/quickstart#processor-with-boundary-transforms","Processor with Boundary Transforms",[63],"For boundary-aware serialization with automatic field transforms: package main\n\nimport (\n    \"context\"\n    \"fmt\"\n\n    \"github.com/zoobzio/cereal\"\n    \"github.com/zoobzio/cereal/json\"\n)\n\n// 1. Define your type with boundary tags\ntype User struct {\n    ID       string `json:\"id\"`\n    Email    string `json:\"email\" store.encrypt:\"aes\" load.decrypt:\"aes\" send.mask:\"email\"`\n    Password string `json:\"password\" receive.hash:\"sha256\"`\n    Token    string `json:\"token\" send.redact:\"[REDACTED]\"`\n}\n\n// 2. Implement Cloner[T]\nfunc (u User) Clone() User { return u }\n\nfunc main() {\n    ctx := context.Background()\n\n    // 3. Create processor\n    proc, err := cereal.NewProcessor[User]()\n    if err != nil {\n        panic(err)\n    }\n\n    // 4. Configure encryption\n    key := []byte(\"32-byte-key-for-aes-256-encrypt!\")\n    enc, err := cereal.AES(key)\n    if err != nil {\n        panic(err)\n    }\n    proc.SetEncryptor(cereal.EncryptAES, enc)\n\n    // 5. Validate configuration (optional - runs automatically on first operation)\n    if err := proc.Validate(); err != nil {\n        panic(err)\n    }\n\n    // 6. Primary API: T -> T transforms\n\n    // Receive: transform incoming data (hashes password)\n    incoming := User{ID: \"123\", Email: \"alice@example.com\", Password: \"secret\", Token: \"abc123\"}\n    user, err := proc.Receive(ctx, incoming)\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"Received - Password hashed: %s...\\n\", user.Password[:16])\n\n    // Store: transform for storage (encrypts email)\n    stored, err := proc.Store(ctx, user)\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"Stored - Email encrypted: %s\\n\", stored.Email)\n\n    // Load: transform from storage (decrypts email)\n    loaded, err := proc.Load(ctx, stored)\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"Loaded - Email decrypted: %s\\n\", loaded.Email)\n\n    // Send: transform for external output (masks email, redacts token)\n    sanitized, err := proc.Send(ctx, loaded)\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"Sent - Email: %s, Token: %s\\n\", sanitized.Email, sanitized.Token)\n\n    // 7. Secondary API: codec-aware byte serialization (optional)\n\n    proc.SetCodec(json.New())\n\n    // Decode: bytes -> *T with Load transforms\n    data := []byte(`{\"id\":\"123\",\"email\":\"encrypted-value\",\"password\":\"hashed\",\"token\":\"abc123\"}`)\n    decoded, err := proc.Read(ctx, data)\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"Decoded - Email: %s\\n\", decoded.Email)\n\n    // Encode: *T -> bytes with Send transforms\n    response, err := proc.Encode(ctx, &loaded)\n    if err != nil {\n        panic(err)\n    }\n    fmt.Printf(\"Encoded: %s\\n\", response)\n}",{"id":90,"title":91,"titles":92,"content":93,"level":19},"/v0.1.2/learn/quickstart#whats-happening","What's Happening",[63],"receive.hash:\"sha256\" - Hashes password when receiving external inputstore.encrypt:\"aes\" - Encrypts email before storing to databaseload.decrypt:\"aes\" - Decrypts email when loading from databasesend.mask:\"email\" - Masks email (a***@example.com) in API responsessend.redact:\"[REDACTED]\" - Replaces token entirely in API responsesClone() - Returns a copy so the original is never modifiedcontext.Context - Flows through for observability integration",{"id":95,"title":96,"titles":97,"content":98,"level":19},"/v0.1.2/learn/quickstart#performance-note","Performance Note",[63],"The processor uses reflection to apply transforms. For most use cases this is negligible (microseconds per operation). For performance-critical paths, implement override interfaces (Encryptable, Hashable, Maskable, Redactable) to bypass reflection entirely. See Escape Hatches.",{"id":100,"title":101,"titles":102,"content":103,"level":19},"/v0.1.2/learn/quickstart#next-steps","Next Steps",[63],"Concepts - Understand the boundary modelEncryption Guide - Configure encryptorsMasking Guide - Available mask typesTag Reference - Complete tag documentation html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":105,"title":106,"titles":107,"content":108,"level":9},"/v0.1.2/learn/concepts","Concepts",[],"Core concepts and design principles",{"id":110,"title":106,"titles":111,"content":34,"level":9},"/v0.1.2/learn/concepts#concepts",[],{"id":113,"title":114,"titles":115,"content":116,"level":19},"/v0.1.2/learn/concepts#codec-vs-processor","Codec vs Processor",[106],"Codec is a simple interface for marshaling and unmarshaling: type Codec interface {\n    ContentType() string\n    Marshal(v any) ([]byte, error)\n    Unmarshal(data []byte, v any) error\n} Use a codec directly when you need basic encoding without transforms. ProcessorT provides boundary-aware transforms. Optionally wraps a codec for serialization: type Processor[T Cloner[T]] struct {\n    // ...\n}\n\n// Primary API: T -> T\nfunc (p *Processor[T]) Receive(ctx context.Context, obj T) (T, error)\nfunc (p *Processor[T]) Load(ctx context.Context, obj T) (T, error)\nfunc (p *Processor[T]) Store(ctx context.Context, obj T) (T, error)\nfunc (p *Processor[T]) Send(ctx context.Context, obj T) (T, error)\n\n// Secondary codec-aware API\nfunc (p *Processor[T]) Decode(ctx context.Context, data []byte) (*T, error)\nfunc (p *Processor[T]) Read(ctx context.Context, data []byte) (*T, error)\nfunc (p *Processor[T]) Write(ctx context.Context, obj *T) ([]byte, error)\nfunc (p *Processor[T]) Encode(ctx context.Context, obj *T) ([]byte, error) Use a processor when data needs different treatment at different boundaries.",{"id":118,"title":16,"titles":119,"content":120,"level":19},"/v0.1.2/learn/concepts#the-four-boundaries",[106],"Data flows through four boundaries: External World              Your Application              Storage\n     │                            │                          │\n     │  ──── Receive ────>        │                          │\n     │                            │  ──── Store ────>        │\n     │                            │  \u003C──── Load ─────        │\n     │  \u003C──── Send ──────         │                          │\n     │                            │                          │ BoundaryOperationTransformsExampleReceiveReceive()receive.hashHash passwords from API requestsLoadLoad()load.decryptDecrypt fields from databaseStoreStore()store.encryptEncrypt fields for databaseSendSend()send.mask, send.redactMask PII in API responses",{"id":122,"title":123,"titles":124,"content":125,"level":19},"/v0.1.2/learn/concepts#clonert-constraint","ClonerT Constraint",[106],"All types used with Processor[T] must implement Cloner[T]: type Cloner[T any] interface {\n    Clone() T\n} This ensures the original value is never modified during transforms. All four operations clone before transforming.",{"id":127,"title":128,"titles":129,"content":130,"level":40},"/v0.1.2/learn/concepts#deep-copy-requirement","Deep Copy Requirement",[106,123],"WARNING: Clone must return a true deep copy. A shallow copy (return u) is only safe for types with no reference fields. Field TypeDeep Copy Required?string, int, bool, etc.No (copied by value)[]T (slice)Yesmap[K]VYes*T (pointer)YesStruct with any of the aboveYes",{"id":132,"title":133,"titles":134,"content":135,"level":40},"/v0.1.2/learn/concepts#simple-value-types","Simple Value Types",[106,123],"For types with only primitive fields: type User struct {\n    ID   string\n    Name string\n    Age  int\n}\n\nfunc (u User) Clone() User { return u }  // Safe: all fields are values",{"id":137,"title":138,"titles":139,"content":140,"level":40},"/v0.1.2/learn/concepts#types-with-reference-fields","Types with Reference Fields",[106,123],"For types with slices, maps, or pointers: type User struct {\n    ID       string\n    Tags     []string          // Slice\n    Settings map[string]string // Map\n    Profile  *Profile          // Pointer\n}\n\nfunc (u User) Clone() User {\n    clone := User{ID: u.ID}\n\n    // Deep copy slice\n    if u.Tags != nil {\n        clone.Tags = make([]string, len(u.Tags))\n        copy(clone.Tags, u.Tags)\n    }\n\n    // Deep copy map\n    if u.Settings != nil {\n        clone.Settings = make(map[string]string, len(u.Settings))\n        for k, v := range u.Settings {\n            clone.Settings[k] = v\n        }\n    }\n\n    // Deep copy pointer\n    if u.Profile != nil {\n        p := *u.Profile\n        clone.Profile = &p\n    }\n\n    return clone\n}",{"id":142,"title":143,"titles":144,"content":145,"level":40},"/v0.1.2/learn/concepts#verification","Verification",[106,123],"Test that modifying the clone does not affect the original: func TestClone(t *testing.T) {\n    original := User{Tags: []string{\"admin\"}}\n    clone := original.Clone()\n    clone.Tags[0] = \"user\"\n\n    if original.Tags[0] != \"admin\" {\n        t.Error(\"Clone modified original\")\n    }\n}",{"id":147,"title":148,"titles":149,"content":150,"level":19},"/v0.1.2/learn/concepts#boundary-tags","Boundary Tags",[106],"Struct tags declare which transforms apply at which boundaries: type User struct {\n    // Hash on receive (incoming data)\n    Password string `receive.hash:\"sha256\"`\n\n    // Encrypt on store, decrypt on load\n    Email string `store.encrypt:\"aes\" load.decrypt:\"aes\"`\n\n    // Mask on send (outgoing data)\n    SSN string `send.mask:\"ssn\"`\n\n    // Redact on send\n    Token string `send.redact:\"[HIDDEN]\"`\n}",{"id":152,"title":153,"titles":154,"content":155,"level":40},"/v0.1.2/learn/concepts#tag-format","Tag Format",[106,148],"boundary.operation:\"algorithm\" BoundaryOperationsreceivehashloaddecryptstoreencryptsendmask, redact",{"id":157,"title":158,"titles":159,"content":160,"level":40},"/v0.1.2/learn/concepts#tag-values","Tag Values",[106,148],"Tag values reference registered algorithms: // Tag value \"aes\" maps to EncryptAES\nEmail string `store.encrypt:\"aes\"`\n\n// Registration\nproc.SetEncryptor(cereal.EncryptAES, enc) Built-in algorithms: TypeConstantsEncryptionEncryptAES, EncryptRSA, EncryptEnvelopeHashingHashSHA256, HashSHA512, HashArgon2, HashBcryptMaskingMaskEmail, MaskSSN, MaskPhone, MaskCard, MaskIP, MaskUUID, MaskIBAN, MaskName",{"id":162,"title":163,"titles":164,"content":165,"level":19},"/v0.1.2/learn/concepts#configuration-via-setters","Configuration via Setters",[106],"Processors are configured with setter methods: proc, _ := cereal.NewProcessor[User]()\n\n// Configure encryption\nenc, _ := cereal.AES(key)\nproc.SetEncryptor(cereal.EncryptAES, enc)\n\n// Configure custom hasher\nproc.SetHasher(cereal.HashArgon2, customArgon2)\n\n// Configure custom masker\nproc.SetMasker(cereal.MaskEmail, customEmailMasker) Setters return the processor for chaining: proc.SetEncryptor(cereal.EncryptAES, aesEnc).\n    SetEncryptor(cereal.EncryptRSA, rsaEnc).\n    SetHasher(cereal.HashArgon2, argon2)",{"id":167,"title":168,"titles":169,"content":170,"level":19},"/v0.1.2/learn/concepts#validation","Validation",[106],"Validation runs automatically on the first operation. If a required handler is missing, the operation returns an error: proc, _ := cereal.NewProcessor[User]()\n// Forgot to configure encryptor...\n\nresult, err := proc.Store(ctx, user)\n// err: missing encryptor for algorithm \"aes\" (field Email) For fail-fast behavior at startup, call Validate() explicitly: proc, _ := cereal.NewProcessor[User]()\nproc.SetEncryptor(cereal.EncryptAES, enc)\n\nif err := proc.Validate(); err != nil {\n    log.Fatal(err) // Catch configuration errors immediately\n} Validation checks that all algorithms referenced in tags have registered handlers. Types implementing override interfaces (Encryptable, Decryptable, Hashable, Maskable) bypass validation for their respective transform types.",{"id":172,"title":173,"titles":174,"content":175,"level":19},"/v0.1.2/learn/concepts#sentinel-integration","Sentinel Integration",[106],"The processor uses sentinel for struct metadata. Field plans are built once per type and cached globally: // First call for User builds and caches field plans\nproc1, _ := cereal.NewProcessor[User]()\n\n// Subsequent calls reuse cached plans (no reflection overhead)\nproc2, _ := cereal.NewProcessor[User]()\n\n// Each processor has independent configuration\nproc1.SetEncryptor(cereal.EncryptAES, enc1)\nproc2.SetEncryptor(cereal.EncryptAES, enc2) This design provides: Shared plans: Reflection happens once per type, regardless of how many processors are createdIndependent state: Each processor maintains its own encryptors, hashers, and maskersSafe key rotation: Configure different keys per processor without affecting others",{"id":177,"title":178,"titles":179,"content":180,"level":40},"/v0.1.2/learn/concepts#test-isolation","Test Isolation",[106,173],"For tests that need to verify plan building, ResetPlansCache() clears the internal cache: func TestSomething(t *testing.T) {\n    cereal.ResetPlansCache()\n    defer cereal.ResetPlansCache()\n\n    proc, _ := cereal.NewProcessor[User]()\n    // ...\n} Most tests don't need this—processors with independent state can be created freely.",{"id":182,"title":183,"titles":184,"content":185,"level":19},"/v0.1.2/learn/concepts#override-interfaces","Override Interfaces",[106],"For performance-critical paths, implement these interfaces to bypass reflection: type Hashable interface {\n    Hash(hashers map[HashAlgo]Hasher) error\n}\n\ntype Encryptable interface {\n    Encrypt(encryptors map[EncryptAlgo]Encryptor) error\n}\n\ntype Decryptable interface {\n    Decrypt(encryptors map[EncryptAlgo]Encryptor) error\n}\n\ntype Maskable interface {\n    Mask(maskers map[MaskType]Masker) error\n}\n\ntype Redactable interface {\n    Redact() error\n} When implemented, the processor calls these methods instead of using reflection. See Override Interfaces for implementation guidance.",{"id":187,"title":188,"titles":189,"content":190,"level":19},"/v0.1.2/learn/concepts#context-propagation","Context Propagation",[106],"All operations accept context.Context: proc.Receive(ctx, obj)\nproc.Load(ctx, obj)\nproc.Store(ctx, obj)\nproc.Send(ctx, obj) Context flows through to signal emission for tracing integration. Pass your request context to correlate operations with traces.",{"id":192,"title":193,"titles":194,"content":195,"level":19},"/v0.1.2/learn/concepts#thread-safety","Thread Safety",[106],"Processors are safe for concurrent use. Multiple goroutines can call Receive, Load, Store, and Send simultaneously on the same processor instance. Configuration methods (SetEncryptor, SetHasher, SetMasker) use internal synchronization and can be called during operation, though typically configuration happens at startup. proc, _ := cereal.NewProcessor[User]()\nproc.SetEncryptor(cereal.EncryptAES, enc)\n\n// Safe: concurrent operations on same processor\ngo func() { proc.Store(ctx, user1) }()\ngo func() { proc.Store(ctx, user2) }()\ngo func() { proc.Load(ctx, user3) }()",{"id":197,"title":198,"titles":199,"content":200,"level":19},"/v0.1.2/learn/concepts#nested-struct-support","Nested Struct Support",[106],"Transforms apply to nested structs: type Address struct {\n    Street string `json:\"street\"`\n    City   string `json:\"city\" send.redact:\"[HIDDEN]\"`\n}\n\ntype User struct {\n    Name    string  `json:\"name\" send.mask:\"name\"`\n    Address Address `json:\"address\"` // City will be redacted on Send\n} Both direct fields and nested struct fields are processed. html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":202,"title":22,"titles":203,"content":204,"level":9},"/v0.1.2/learn/architecture",[],"System design and internal structure of cereal",{"id":206,"title":207,"titles":208,"content":34,"level":9},"/v0.1.2/learn/architecture#cereal-architecture","Cereal Architecture",[],{"id":210,"title":6,"titles":211,"content":212,"level":19},"/v0.1.2/learn/architecture#overview",[207],"Cereal is a boundary serialization library for Go. It sits at the edge of a system—the first thing data encounters on ingress, the last thing before egress. Data flows in from users, databases, and distributed event systems, and flows out to similar destinations. The library provides primitives for context-aware field transformation during serialization, enabling workflows required by security-conscious verticals like healthcare (HIPAA) and finance (PCI-DSS).",{"id":214,"title":215,"titles":216,"content":34,"level":19},"/v0.1.2/learn/architecture#mental-model","Mental Model",[207],{"id":218,"title":219,"titles":220,"content":221,"level":40},"/v0.1.2/learn/architecture#the-problem","The Problem",[207,215],"Traditional serialization is context-free: marshal a struct, get bytes. But real systems need context-aware transformation: A password should be hashed when received from a user, redacted when returnedAn SSN should be encrypted when stored, decrypted when loaded, masked when displayedAn email should be encrypted at rest, masked on output The same field requires different treatment depending on: Direction: Is data coming in (ingress) or going out (egress)?Destination: Is it storage, a user, or an external service?",{"id":223,"title":224,"titles":225,"content":226,"level":40},"/v0.1.2/learn/architecture#contexts","Contexts",[207,215],"Four contexts representing the boundaries data crosses: ContextDirectionDescriptionreceiveIngressAccepting from external source (API request, event, webhook)loadIngressReading from persistent storage (database, cache)storeEgressWriting to persistent storagesendEgressReturning to external destination (API response, event publish) ┌─────────────────────────┐\n                    │        System           │\n                    │                         │\n   receive          │                         │          send\n   ─────────────────►       (internal)       ─────────────────►\n   (users, events)  │                         │   (users, events)\n                    │                         │\n   load             │                         │          store\n   ─────────────────►                        ─────────────────►\n   (from database)  │                         │   (to database)\n                    │                         │\n                    └─────────────────────────┘",{"id":228,"title":229,"titles":230,"content":231,"level":40},"/v0.1.2/learn/architecture#actions","Actions",[207,215],"Five transformations applied to field values: ActionDirectionReversiblePurposehashIngress✗One-way transform (passwords)decryptIngress✓Restore encrypted dataencryptEgress✓Confidentiality at restmaskEgress✗Partial redaction preserving formatredactEgress✗Full replacement",{"id":233,"title":234,"titles":235,"content":236,"level":40},"/v0.1.2/learn/architecture#valid-context-action-combinations","Valid Context-Action Combinations",[207,215],"ContextValid Actionsreceivehashloaddecryptstoreencryptsendmask, redact",{"id":238,"title":239,"titles":240,"content":241,"level":40},"/v0.1.2/learn/architecture#tag-syntax","Tag Syntax",[207,215],"Field behavior is declared via struct tags: {context}.{action}:\"{value}\" For hash, encrypt, decrypt, mask: value is a capability constantFor redact: value is the replacement string Example: type Patient struct {\n    Password string `json:\"password\"\n                    receive.hash:\"argon2\"\n                    send.redact:\"***\"`\n\n    SSN string `json:\"ssn\"\n               store.encrypt:\"aes\"\n               load.decrypt:\"aes\"\n               send.mask:\"ssn\"`\n\n    Email string `json:\"email\"\n                 store.encrypt:\"aes\"\n                 load.decrypt:\"aes\"\n                 send.mask:\"email\"`\n}",{"id":243,"title":244,"titles":245,"content":246,"level":40},"/v0.1.2/learn/architecture#context-matrix","Context Matrix",[207,215],"For the Patient type above: FieldreceivestoreloadsendPasswordhash(argon2)——redactSSN—encrypt(aes)decrypt(aes)mask(ssn)Email—encrypt(aes)decrypt(aes)mask(email)",{"id":248,"title":249,"titles":250,"content":251,"level":19},"/v0.1.2/learn/architecture#capability-types","Capability Types",[207],"All capabilities are constrained to predefined constants—no arbitrary strings, no typo risk.",{"id":253,"title":254,"titles":255,"content":256,"level":40},"/v0.1.2/learn/architecture#encryption-algorithms","Encryption Algorithms",[207,249],"type EncryptAlgo string\nconst (\n    EncryptAES      EncryptAlgo = \"aes\"       // AES-GCM\n    EncryptRSA      EncryptAlgo = \"rsa\"       // RSA-OAEP\n    EncryptEnvelope EncryptAlgo = \"envelope\"  // Envelope encryption\n)",{"id":258,"title":259,"titles":260,"content":261,"level":40},"/v0.1.2/learn/architecture#hash-algorithms","Hash Algorithms",[207,249],"type HashAlgo string\nconst (\n    HashArgon2 HashAlgo = \"argon2\"  // Password hashing (salted)\n    HashBcrypt HashAlgo = \"bcrypt\"  // Password hashing (salted)\n    HashSHA256 HashAlgo = \"sha256\"  // Deterministic\n    HashSHA512 HashAlgo = \"sha512\"  // Deterministic\n)",{"id":263,"title":264,"titles":265,"content":266,"level":40},"/v0.1.2/learn/architecture#mask-types","Mask Types",[207,249],"type MaskType string\nconst (\n    MaskSSN   MaskType = \"ssn\"    // ***-**-6789\n    MaskEmail MaskType = \"email\"  // a***@example.com\n    MaskPhone MaskType = \"phone\"  // (***) ***-4567\n    MaskCard  MaskType = \"card\"   // ************1111\n    MaskIP    MaskType = \"ip\"     // 192.168.xxx.xxx\n    MaskUUID  MaskType = \"uuid\"   // 550e8400-****-****-****-************\n    MaskIBAN  MaskType = \"iban\"   // GB82**************5432\n    MaskName  MaskType = \"name\"   // J*** S****\n)",{"id":268,"title":22,"titles":269,"content":34,"level":19},"/v0.1.2/learn/architecture#architecture",[207],{"id":271,"title":272,"titles":273,"content":274,"level":40},"/v0.1.2/learn/architecture#core-components","Core Components",[207,22],"┌──────────────────────────────────────────────────────────────┐\n│                        Processor[T]                          │\n│                                                              │\n│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │\n│  │   Codec     │  │  Sentinel   │  │   Capabilities      │  │\n│  │ (encode/    │  │ (metadata   │  │ (encryptors,        │  │\n│  │  decode)    │  │  extraction)│  │  hashers, maskers)  │  │\n│  └─────────────┘  └─────────────┘  └─────────────────────┘  │\n│                                                              │\n│  ┌─────────────────────────────────────────────────────────┐│\n│  │              Per-Context Field Plans                    ││\n│  │  receive: [hash fields]                                 ││\n│  │  load:    [decrypt fields]                              ││\n│  │  store:   [encrypt fields]                              ││\n│  │  send:    [mask fields, redact fields]                  ││\n│  └─────────────────────────────────────────────────────────┘│\n└──────────────────────────────────────────────────────────────┘",{"id":276,"title":277,"titles":278,"content":279,"level":40},"/v0.1.2/learn/architecture#processing-flow","Processing Flow",[207,22],"Primary API (T → T): Receive: T → Clone → [Hash] → T Load: T → Clone → [Decrypt] → T Store: T → Clone → [Encrypt] → T Send: T → Clone → [Mask] → [Redact] → T Secondary codec-aware API adds unmarshal/marshal around the primary transforms: Decode: Bytes → Unmarshal → Clone → [Hash] → *TRead: Bytes → Unmarshal → Clone → [Decrypt] → *TWrite: *T → Clone → [Encrypt] → Marshal → BytesEncode: *T → Clone → [Mask] → [Redact] → Marshal → Bytes",{"id":281,"title":282,"titles":283,"content":284,"level":40},"/v0.1.2/learn/architecture#capability-interfaces","Capability Interfaces",[207,22],"// Encryptor provides symmetric or asymmetric encryption.\ntype Encryptor interface {\n    Encrypt(plaintext []byte) ([]byte, error)\n    Decrypt(ciphertext []byte) ([]byte, error)\n}\n\n// Hasher provides one-way hashing.\ntype Hasher interface {\n    Hash(plaintext []byte) (string, error)\n}\n\n// Masker provides format-preserving partial redaction.\ntype Masker interface {\n    Mask(value string) string\n}",{"id":286,"title":183,"titles":287,"content":288,"level":40},"/v0.1.2/learn/architecture#override-interfaces",[207,22],"Types can bypass reflection by implementing action-specific interfaces: // Encryptable bypasses reflection for store.encrypt actions.\ntype Encryptable interface {\n    Encrypt(encryptors map[EncryptAlgo]Encryptor) error\n}\n\n// Decryptable bypasses reflection for load.decrypt actions.\ntype Decryptable interface {\n    Decrypt(encryptors map[EncryptAlgo]Encryptor) error\n}\n\n// Hashable bypasses reflection for receive.hash actions.\ntype Hashable interface {\n    Hash(hashers map[HashAlgo]Hasher) error\n}\n\n// Maskable bypasses reflection for send.mask actions.\ntype Maskable interface {\n    Mask(maskers map[MaskType]Masker) error\n}\n\n// Redactable bypasses reflection for send.redact actions.\ntype Redactable interface {\n    Redact() error\n}",{"id":290,"title":291,"titles":292,"content":293,"level":40},"/v0.1.2/learn/architecture#auto-registration","Auto-Registration",[207,22],"CapabilityRegistrationUser ResponsibilityMaskersAutomaticNone—all mask types built-inHashersAutomaticNone—all hash algorithms built-inEncryptorsManualProvide key per algorithm used",{"id":295,"title":296,"titles":297,"content":298,"level":40},"/v0.1.2/learn/architecture#registration-api","Registration API",[207,22],"// Create processor\nproc, err := cereal.NewProcessor[Patient]()\n\n// Optionally set a codec for secondary API\nproc.SetCodec(json.New())\n\n// Configure encryption\nenc, _ := cereal.AES(aesKey)\nproc.SetEncryptor(cereal.EncryptAES, enc)\n\n// Escape hatch for custom implementations\nproc.SetEncryptor(cereal.EncryptAES, customEncryptor)\nproc.SetHasher(cereal.HashArgon2, customHasher)\nproc.SetMasker(cereal.MaskSSN, customMasker)",{"id":300,"title":301,"titles":302,"content":303,"level":40},"/v0.1.2/learn/architecture#context-specific-operations","Context-Specific Operations",[207,22],"// Primary API: T -> T\n// Receiving from API (applies receive.hash)\npatient, err := proc.Receive(ctx, incomingPatient)\n\n// Storing to database (applies store.encrypt)\nencrypted, err := proc.Store(ctx, patient)\n\n// Loading from database (applies load.decrypt)\ndecrypted, err := proc.Load(ctx, storedPatient)\n\n// Sending to API (applies send.mask, send.redact)\nsafe, err := proc.Send(ctx, patient)\n\n// Secondary codec-aware API (requires SetCodec)\n// Decode raw bytes into transformed *T\npatient, err := proc.Decode(ctx, requestBody)\n\n// Write *T to bytes with transforms\ndata, err := proc.Write(ctx, &patient)",{"id":305,"title":32,"titles":306,"content":307,"level":19},"/v0.1.2/learn/architecture#design-principles",[207],"Declarative: Behavior declared in struct tags, co-located with type definitionFail-Fast: Invalid tag values detected at processor creation, not runtimeNon-Destructive: Original objects never mutated; clone before transformContext-Aware: Same field, different treatment based on data flow directionConstrained: Capability values are predefined constants, not arbitrary stringsBatteries Included: Hashers and maskers auto-registered; only encryption keys requiredEscape Hatches: Override interfaces for custom logic or performance optimizationReflection Now, Codegen Later: Tag syntax supports both execution models",{"id":309,"title":310,"titles":311,"content":34,"level":19},"/v0.1.2/learn/architecture#use-cases","Use Cases",[207],{"id":313,"title":314,"titles":315,"content":316,"level":40},"/v0.1.2/learn/architecture#healthcare-hipaa","Healthcare (HIPAA)",[207,310],"type PatientRecord struct {\n    MRN string `json:\"mrn\"\n               store.encrypt:\"aes\"\n               load.decrypt:\"aes\"\n               send.mask:\"uuid\"`\n\n    Diagnosis string `json:\"diagnosis\"\n                     store.encrypt:\"aes\"\n                     load.decrypt:\"aes\"\n                     send.redact:\"[CLINICAL]\"`\n\n    SSN string `json:\"ssn\"\n               store.encrypt:\"aes\"\n               load.decrypt:\"aes\"\n               send.mask:\"ssn\"`\n}",{"id":318,"title":319,"titles":320,"content":321,"level":40},"/v0.1.2/learn/architecture#finance-pci-dss","Finance (PCI-DSS)",[207,310],"type PaymentMethod struct {\n    CardNumber string `json:\"card_number\"\n                      store.encrypt:\"aes\"\n                      load.decrypt:\"aes\"\n                      send.mask:\"card\"`\n\n    CVV string `json:\"cvv\"\n               store.redact:\"\"\n               send.redact:\"\"`\n\n    PIN string `json:\"pin\"\n               receive.hash:\"argon2\"\n               send.redact:\"***\"`\n}",{"id":323,"title":324,"titles":325,"content":326,"level":40},"/v0.1.2/learn/architecture#user-authentication","User Authentication",[207,310],"type User struct {\n    Email string `json:\"email\"\n                 store.encrypt:\"aes\"\n                 load.decrypt:\"aes\"\n                 send.mask:\"email\"`\n\n    Password string `json:\"password\"\n                    receive.hash:\"argon2\"\n                    send.redact:\"***\"`\n\n    APIKey string `json:\"api_key\"\n                  store.encrypt:\"aes\"\n                  load.decrypt:\"aes\"\n                  send.redact:\"[HIDDEN]\"`\n}",{"id":328,"title":329,"titles":330,"content":331,"level":19},"/v0.1.2/learn/architecture#collection-handling","Collection Handling",[207],"Actions apply to collection elements:",{"id":333,"title":334,"titles":335,"content":336,"level":40},"/v0.1.2/learn/architecture#slice-of-strings","Slice of Strings",[207,329],"type User struct {\n    Emails []string `send.mask:\"email\"`  // masks each element\n}",{"id":338,"title":339,"titles":340,"content":341,"level":40},"/v0.1.2/learn/architecture#slice-of-structs","Slice of Structs",[207,329],"type Contact struct {\n    Phone string `send.mask:\"phone\"`\n}\n\ntype User struct {\n    Contacts []Contact  // recurses into each element\n}",{"id":343,"title":344,"titles":345,"content":346,"level":40},"/v0.1.2/learn/architecture#map-values","Map Values",[207,329],"type User struct {\n    Phones map[string]string `send.mask:\"phone\"`  // masks each value\n}",{"id":348,"title":349,"titles":350,"content":351,"level":19},"/v0.1.2/learn/architecture#references","References",[207],"Sentinel - Struct metadata extractionCapitan - Event signaling html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sfm-E, html code.shiki .sfm-E{--shiki-default:var(--shiki-variable)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}",{"id":353,"title":354,"titles":355,"content":356,"level":9},"/v0.1.2/guides/encryption","Encryption",[],"Encrypt, decrypt, and hash sensitive fields",{"id":358,"title":354,"titles":359,"content":360,"level":9},"/v0.1.2/guides/encryption#encryption",[],"Cereal provides three built-in encryptors and four hashers.",{"id":362,"title":363,"titles":364,"content":365,"level":19},"/v0.1.2/guides/encryption#encryption-boundary-tags","Encryption Boundary Tags",[354],"Encryption uses two boundaries: type User struct {\n    // Encrypt when storing to database\n    Email string `store.encrypt:\"aes\"`\n\n    // Decrypt when loading from database\n    Email string `load.decrypt:\"aes\"`\n\n    // Usually paired together\n    Email string `store.encrypt:\"aes\" load.decrypt:\"aes\"`\n}",{"id":367,"title":368,"titles":369,"content":370,"level":19},"/v0.1.2/guides/encryption#aes-gcm","AES-GCM",[354],"Symmetric encryption using AES-256 in GCM mode: key := make([]byte, 32) // 256-bit key\nrand.Read(key)\n\nenc, err := cereal.AES(key)\nif err != nil {\n    // Key must be 16, 24, or 32 bytes\n}\n\nproc, _ := cereal.NewProcessor[User]()\nproc.SetEncryptor(cereal.EncryptAES, enc) Requires 16, 24, or 32 byte key (AES-128, AES-192, AES-256)Random nonce prepended to ciphertextAuthenticated encryption prevents tamperingBase64 encoded in output",{"id":372,"title":373,"titles":374,"content":375,"level":19},"/v0.1.2/guides/encryption#rsa-oaep","RSA-OAEP",[354],"Asymmetric encryption for scenarios where encrypt and decrypt happen in different contexts: // Generate key pair\npriv, _ := rsa.GenerateKey(rand.Reader, 2048)\npub := &priv.PublicKey\n\n// Full encryptor (can encrypt and decrypt)\nenc := cereal.RSA(pub, priv)\n\n// Encrypt-only encryptor (for client-side)\nencryptOnly := cereal.RSA(pub, nil)\n\nproc.SetEncryptor(cereal.EncryptRSA, enc) Uses SHA-256 for OAEP paddingPublic key encrypts, private key decryptsPass nil for private key if decrypt not needed",{"id":377,"title":378,"titles":379,"content":380,"level":19},"/v0.1.2/guides/encryption#envelope-encryption","Envelope Encryption",[354],"For encrypting large fields or when you need key rotation: masterKey := make([]byte, 32) // 16, 24, or 32 bytes\nrand.Read(masterKey)\n\nenc, err := cereal.Envelope(masterKey)\n\nproc.SetEncryptor(cereal.EncryptEnvelope, enc) How it works: Generate a random data encryption key (DEK) per operationEncrypt the field with the DEK (AES-GCM)Encrypt the DEK with the master keyPrepend encrypted DEK to ciphertext Benefits: Master key never touches plaintext directlyKey rotation: re-encrypt DEKs, data unchangedLarge fields don't stress the master key",{"id":382,"title":383,"titles":384,"content":385,"level":19},"/v0.1.2/guides/encryption#multiple-encryptors","Multiple Encryptors",[354],"Register different encryptors for different algorithms: aesEnc, _ := cereal.AES(aesKey)\nrsaEnc := cereal.RSA(pub, priv)\nenvEnc, _ := cereal.Envelope(masterKey)\n\nproc.SetEncryptor(cereal.EncryptAES, aesEnc)\nproc.SetEncryptor(cereal.EncryptRSA, rsaEnc)\nproc.SetEncryptor(cereal.EncryptEnvelope, envEnc)\n\ntype Record struct {\n    Email  string `store.encrypt:\"aes\" load.decrypt:\"aes\"`       // Uses AES\n    Secret string `store.encrypt:\"rsa\" load.decrypt:\"rsa\"`       // Uses RSA\n    Data   string `store.encrypt:\"envelope\" load.decrypt:\"envelope\"` // Uses Envelope\n}",{"id":387,"title":388,"titles":389,"content":390,"level":19},"/v0.1.2/guides/encryption#hashing","Hashing",[354],"One-way hashing for fields on the receive boundary: type User struct {\n    Password string `receive.hash:\"sha256\"`\n    Token    string `receive.hash:\"argon2\"`\n} Hashing applies when receiving external input. Hashed values cannot be recovered.",{"id":392,"title":393,"titles":394,"content":395,"level":40},"/v0.1.2/guides/encryption#built-in-hashers","Built-in Hashers",[354,388],"AlgorithmConstantOutputSHA-256HashSHA25664 hex charsSHA-512HashSHA512128 hex charsArgon2idHashArgon2PHC format stringbcryptHashBcryptbcrypt format string SHA-256 and SHA-512 are registered by default. Argon2 and bcrypt have configurable parameters.",{"id":397,"title":398,"titles":399,"content":400,"level":40},"/v0.1.2/guides/encryption#argon2-configuration","Argon2 Configuration",[354,388],"// Default parameters\nhasher := cereal.Argon2()\n\n// Custom parameters\nparams := cereal.Argon2Params{\n    Time:    3,        // iterations\n    Memory:  64 * 1024, // 64MB\n    Threads: 4,\n    KeyLen:  32,\n    SaltLen: 16,\n}\nhasher := cereal.Argon2WithParams(params)\n\nproc.SetHasher(cereal.HashArgon2, hasher)",{"id":402,"title":403,"titles":404,"content":405,"level":40},"/v0.1.2/guides/encryption#bcrypt-configuration","bcrypt Configuration",[354,388],"// Default cost (12, per OWASP recommendations)\nhasher := cereal.Bcrypt()\n\n// Custom cost\nhasher := cereal.BcryptWithCost(10)  // Lower cost for faster tests\n\nproc.SetHasher(cereal.HashBcrypt, hasher)",{"id":407,"title":408,"titles":409,"content":410,"level":40},"/v0.1.2/guides/encryption#when-to-hash-vs-encrypt","When to Hash vs Encrypt",[354,388],"Use CaseApproachNeed original value backstore.encrypt + load.decryptVerification onlyreceive.hashAudit trailreceive.hashPassword storagereceive.hash:\"argon2\" or receive.hash:\"bcrypt\"",{"id":412,"title":413,"titles":414,"content":415,"level":19},"/v0.1.2/guides/encryption#custom-encryptors","Custom Encryptors",[354],"Implement the Encryptor interface: type Encryptor interface {\n    Encrypt(plaintext []byte) ([]byte, error)\n    Decrypt(ciphertext []byte) ([]byte, error)\n} Example: type vaultEncryptor struct {\n    client *vault.Client\n    path   string\n}\n\nfunc (e *vaultEncryptor) Encrypt(plaintext []byte) ([]byte, error) {\n    return e.client.Transit.Encrypt(e.path, plaintext)\n}\n\nfunc (e *vaultEncryptor) Decrypt(ciphertext []byte) ([]byte, error) {\n    return e.client.Transit.Decrypt(e.path, ciphertext)\n}\n\nproc.SetEncryptor(cereal.EncryptAES, &vaultEncryptor{client, \"transit/keys/mykey\"})",{"id":417,"title":418,"titles":419,"content":420,"level":19},"/v0.1.2/guides/encryption#custom-hashers","Custom Hashers",[354],"Implement the Hasher interface: type Hasher interface {\n    Hash(data []byte) (string, error)\n} The return value is stored directly (typically hex or PHC format). type hmacHasher struct {\n    key []byte\n}\n\nfunc (h *hmacHasher) Hash(data []byte) (string, error) {\n    mac := hmac.New(sha256.New, h.key)\n    mac.Write(data)\n    return hex.EncodeToString(mac.Sum(nil)), nil\n}\n\nproc.SetHasher(cereal.HashSHA256, &hmacHasher{key})",{"id":422,"title":423,"titles":424,"content":425,"level":19},"/v0.1.2/guides/encryption#field-type-support","Field Type Support",[354],"Tagstring[]bytestore.encryptYesYesload.decryptYesYesreceive.hashYesYes",{"id":427,"title":428,"titles":429,"content":430,"level":40},"/v0.1.2/guides/encryption#string-vs-byte-slice-encoding","String vs Byte Slice Encoding",[354,423],"String fields are base64 encoded after encryption. This ensures compatibility with text-based codecs (JSON, XML, YAML) that cannot represent arbitrary binary data. Byte slice fields store raw ciphertext without encoding. Binary codecs (MessagePack, BSON) handle this natively. type Record struct {\n    // Base64 encoded in JSON: \"email\":\"SGVsbG8gV29ybGQ...\"\n    Email string `json:\"email\" store.encrypt:\"aes\" load.decrypt:\"aes\"`\n\n    // Raw bytes in MessagePack/BSON, base64 in JSON\n    Data []byte `json:\"data\" store.encrypt:\"aes\" load.decrypt:\"aes\"`\n} Tradeoff: Base64 encoding adds ~33% size overhead (4 bytes output per 3 bytes input). For performance-critical paths with large encrypted fields: Use []byte fields with binary codecs (MessagePack, BSON)Implement Encryptable/Decryptable interfaces with custom encodingStore encrypted data separately from the serialized struct html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}",{"id":432,"title":433,"titles":434,"content":435,"level":9},"/v0.1.2/guides/masking","Masking",[],"Content-aware partial masking for sensitive data",{"id":437,"title":433,"titles":438,"content":439,"level":9},"/v0.1.2/guides/masking#masking",[],"Masking partially obscures sensitive data while preserving format and some identifying information. Applied on the send boundary for outgoing data.",{"id":441,"title":442,"titles":443,"content":444,"level":19},"/v0.1.2/guides/masking#built-in-mask-types","Built-in Mask Types",[433],"Eight content-aware maskers are built in: TypeConstantExample InputExample OutputssnMaskSSN123-45-6789***-**-6789emailMaskEmailalice@example.coma***@example.comphoneMaskPhone(555) 123-4567(***) ***-4567cardMaskCard4111111111111111************1111ipMaskIP192.168.1.100192.168.xxx.xxxuuidMaskUUID550e8400-e29b-41d4-a716-446655440000550e8400-****-****-****-************ibanMaskIBANGB82WEST12345698765432GB82**************5432nameMaskNameJohn SmithJ*** S****",{"id":446,"title":447,"titles":448,"content":449,"level":19},"/v0.1.2/guides/masking#usage","Usage",[433],"Maskers are built in and require no registration: type Customer struct {\n    Email string `json:\"email\" send.mask:\"email\"`\n    SSN   string `json:\"ssn\" send.mask:\"ssn\"`\n    Phone string `json:\"phone\" send.mask:\"phone\"`\n    Card  string `json:\"card\" send.mask:\"card\"`\n}\n\nfunc (c Customer) Clone() Customer { return c }\n\nproc, _ := cereal.NewProcessor[Customer](json.New())\n\ncustomer := &Customer{\n    Email: \"alice@example.com\",\n    SSN:   \"123-45-6789\",\n    Phone: \"(555) 123-4567\",\n    Card:  \"4111111111111111\",\n}\n\n// Masking only applies on Send\ndata, _ := proc.Send(ctx, customer)\n// {\"email\":\"a***@example.com\",\"ssn\":\"***-**-6789\",\"phone\":\"(***) ***-4567\",\"card\":\"************1111\"}\n\n// Store doesn't mask\nstored, _ := proc.Store(ctx, customer)\n// {\"email\":\"alice@example.com\",\"ssn\":\"123-45-6789\",...} (original values)",{"id":451,"title":452,"titles":453,"content":454,"level":19},"/v0.1.2/guides/masking#redaction","Redaction",[433],"For complete replacement instead of partial masking, use send.redact: type Note struct {\n    Content string `json:\"content\" send.redact:\"[REDACTED]\"`\n    Secret  string `json:\"secret\" send.redact:\"***\"`\n    Token   string `json:\"token\" send.redact:\"\"`  // Empty string\n} The entire value is replaced with the tag value.",{"id":456,"title":457,"titles":458,"content":459,"level":19},"/v0.1.2/guides/masking#masking-vs-redaction","Masking vs Redaction",[433],"AspectMaskingRedactionPreserves formatYesNoShows partial dataYesNoHuman readableYesYesUseful for debuggingMoreLessTagsend.masksend.redact Choose masking when format or partial identification helps. Choose redaction when no information should leak.",{"id":461,"title":462,"titles":463,"content":464,"level":19},"/v0.1.2/guides/masking#boundary-specificity","Boundary Specificity",[433],"Masking and redaction only apply on the send boundary: type User struct {\n    Email string `send.mask:\"email\"`  // Only masked when sending\n}\n\n// Receive: email unchanged\nuser, _ := proc.Receive(ctx, data)\n// user.Email = \"alice@example.com\"\n\n// Store: email unchanged\nstored, _ := proc.Store(ctx, user)\n// stored contains \"alice@example.com\"\n\n// Load: email unchanged\nloaded, _ := proc.Load(ctx, stored)\n// loaded.Email = \"alice@example.com\"\n\n// Send: email masked\nresponse, _ := proc.Send(ctx, loaded)\n// response contains \"a***@example.com\" This lets you store full values but sanitize output.",{"id":466,"title":467,"titles":468,"content":469,"level":19},"/v0.1.2/guides/masking#validation-rules","Validation Rules",[433],"Maskers validate input format and return errors for invalid data: TypeValidationError WhenssnExactly 9 digitsNot 9 digits after extractionemailContains @ with local partNo @ or @ at position 0phoneAt least 7 digitsFewer than 7 digitscard13-19 digitsOutside valid card rangeipValid IPv4 or IPv6Invalid formatuuid8-4-4-4-12 segment lengthsWrong segment count or lengthsiban15-34 chars, starts with 2 lettersInvalid length or prefixnameNon-empty after trimEmpty or whitespace only user := &Customer{Email: \"not-an-email\"}\n_, err := proc.Send(ctx, user)\n// err: \"mask field Email: mask failed: invalid email format, missing or misplaced @\"\n\nif errors.Is(err, cereal.ErrMask) {\n    // Handle validation failure\n}",{"id":471,"title":472,"titles":473,"content":474,"level":19},"/v0.1.2/guides/masking#not-reversible","Not Reversible",[433],"Masked and redacted values cannot be restored: user := &User{Email: \"alice@example.com\"}\ndata, _ := proc.Send(ctx, user)\n\n// If you unmarshal this data, you get the masked value\nvar result User\njson.Unmarshal(data, &result)\n// result.Email == \"a***@example.com\" (masked, not original) For reversible transformations, use store.encrypt + load.decrypt.",{"id":476,"title":477,"titles":478,"content":479,"level":19},"/v0.1.2/guides/masking#custom-maskers","Custom Maskers",[433],"Implement the Masker interface: type Masker interface {\n    Mask(value string) (string, error)\n} Example custom masker: type lastFourMasker struct{}\n\nfunc (m *lastFourMasker) Mask(value string) (string, error) {\n    if len(value) \u003C 4 {\n        return \"\", fmt.Errorf(\"%w: value too short\", cereal.ErrMask)\n    }\n    return strings.Repeat(\"*\", len(value)-4) + value[len(value)-4:], nil\n}\n\nproc.SetMasker(cereal.MaskCard, &lastFourMasker{})",{"id":481,"title":482,"titles":483,"content":484,"level":19},"/v0.1.2/guides/masking#ipv6-support","IPv6 Support",[433],"The IP masker handles both IPv4 and IPv6: // IPv4\nsend.mask:\"ip\" on \"192.168.1.100\"  -> \"192.168.xxx.xxx\"\n\n// IPv6\nsend.mask:\"ip\" on \"2001:db8::1\"    -> \"2001:db8:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx\"",{"id":486,"title":487,"titles":488,"content":489,"level":19},"/v0.1.2/guides/masking#combining-masks-with-encryption","Combining Masks with Encryption",[433],"Common pattern: encrypt for storage, mask for external output: type User struct {\n    // Encrypted in database, masked in API responses\n    Email string `store.encrypt:\"aes\" load.decrypt:\"aes\" send.mask:\"email\"`\n\n    // Encrypted in database, redacted in API responses\n    SSN string `store.encrypt:\"aes\" load.decrypt:\"aes\" send.redact:\"[HIDDEN]\"`\n} This provides defense in depth: data is protected at rest and sanitized in transit. html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}",{"id":491,"title":492,"titles":493,"content":494,"level":9},"/v0.1.2/guides/providers","Providers",[],"Available codec providers and their characteristics",{"id":496,"title":492,"titles":497,"content":498,"level":9},"/v0.1.2/guides/providers#providers",[],"Five codec providers are included, each in its own submodule.",{"id":500,"title":501,"titles":502,"content":503,"level":19},"/v0.1.2/guides/providers#json","JSON",[492],"import \"github.com/zoobzio/cereal/json\"\n\ncodec := json.New() PropertyValueContent-Typeapplication/jsonHuman readableYesBinary supportBase64 encodingStruct tagjson Standard library encoding/json under the hood.",{"id":505,"title":506,"titles":507,"content":508,"level":19},"/v0.1.2/guides/providers#xml","XML",[492],"import \"github.com/zoobzio/cereal/xml\"\n\ncodec := xml.New() PropertyValueContent-Typeapplication/xmlHuman readableYesBinary supportBase64 encodingStruct tagxml Standard library encoding/xml under the hood. Note: XML uses different struct tags than JSON: // For JSON\ntype User struct {\n    ID string `json:\"id\"`\n}\n\n// For XML\ntype User struct {\n    ID string `xml:\"id\"`\n}\n\n// For both\ntype User struct {\n    ID string `json:\"id\" xml:\"id\"`\n}",{"id":510,"title":511,"titles":512,"content":513,"level":19},"/v0.1.2/guides/providers#yaml","YAML",[492],"import \"github.com/zoobzio/cereal/yaml\"\n\ncodec := yaml.New() PropertyValueContent-Typeapplication/yamlHuman readableYesBinary supportBase64 encodingStruct tagyaml Uses gopkg.in/yaml.v3.",{"id":515,"title":516,"titles":517,"content":518,"level":19},"/v0.1.2/guides/providers#messagepack","MessagePack",[492],"import \"github.com/zoobzio/cereal/msgpack\"\n\ncodec := msgpack.New() PropertyValueContent-Typeapplication/msgpackHuman readableNoBinary supportNativeStruct tagmsgpack or json Uses github.com/vmihailenco/msgpack/v5. Falls back to json tags if msgpack tags are not present.",{"id":520,"title":521,"titles":522,"content":523,"level":19},"/v0.1.2/guides/providers#bson","BSON",[492],"import \"github.com/zoobzio/cereal/bson\"\n\ncodec := bson.New() PropertyValueContent-Typeapplication/bsonHuman readableNoBinary supportNativeStruct tagbson or json Uses go.mongodb.org/mongo-driver/bson. Ideal for MongoDB integration.",{"id":525,"title":526,"titles":527,"content":528,"level":19},"/v0.1.2/guides/providers#choosing-a-provider","Choosing a Provider",[492],"Use CaseRecommendedREST APIsJSONConfiguration filesYAMLDocument interchangeXMLHigh-performance RPCMessagePackMongoDB integrationBSONDebuggingJSON or YAML",{"id":530,"title":531,"titles":532,"content":533,"level":19},"/v0.1.2/guides/providers#provider-independence","Provider Independence",[492],"The same type works with any provider: type User struct {\n    ID    string `json:\"id\" yaml:\"id\" xml:\"id\" msgpack:\"id\" bson:\"id\"`\n    Email string `json:\"email\" yaml:\"email\" xml:\"email\" send.mask:\"email\"`\n}\n\nfunc (u User) Clone() User { return u }\n\njsonProc, _ := cereal.NewProcessor[User]()\njsonProc.SetCodec(json.New())\nyamlProc, _ := cereal.NewProcessor[User]()\nyamlProc.SetCodec(yaml.New())\nbsonProc, _ := cereal.NewProcessor[User]()\nbsonProc.SetCodec(bson.New())\n\n// Same transforms, different wire format Boundary tags (store.encrypt, send.mask, etc.) are provider-agnostic.",{"id":535,"title":536,"titles":537,"content":538,"level":19},"/v0.1.2/guides/providers#submodule-installation","Submodule Installation",[492],"Each provider is a separate Go module for dependency isolation: # Install only what you need\ngo get github.com/zoobzio/cereal/json\ngo get github.com/zoobzio/cereal/yaml\ngo get github.com/zoobzio/cereal/xml\ngo get github.com/zoobzio/cereal/msgpack\ngo get github.com/zoobzio/cereal/bson This avoids pulling in dependencies you don't use (e.g., MongoDB driver for BSON).",{"id":540,"title":541,"titles":542,"content":543,"level":19},"/v0.1.2/guides/providers#custom-providers","Custom Providers",[492],"Implement the Codec interface: type Codec interface {\n    ContentType() string\n    Marshal(v any) ([]byte, error)\n    Unmarshal(data []byte, v any) error\n} Example for a hypothetical TOML provider: package toml\n\nimport (\n    \"github.com/zoobzio/cereal\"\n    \"github.com/pelletier/go-toml/v2\"\n)\n\ntype tomlCodec struct{}\n\nfunc New() cereal.Codec { return &tomlCodec{} }\n\nfunc (c *tomlCodec) ContentType() string { return \"application/toml\" }\n\nfunc (c *tomlCodec) Marshal(v any) ([]byte, error) {\n    return toml.Marshal(v)\n}\n\nfunc (c *tomlCodec) Unmarshal(data []byte, v any) error {\n    return toml.Unmarshal(data, v)\n} Then use it: proc, _ := cereal.NewProcessor[Config]()\nproc.SetCodec(toml.New())",{"id":545,"title":546,"titles":547,"content":548,"level":19},"/v0.1.2/guides/providers#content-type-access","Content-Type Access",[492],"Get the underlying codec's content type: proc, _ := cereal.NewProcessor[User]()\nproc.SetCodec(json.New())\nfmt.Println(proc.ContentType()) // \"application/json\" Useful for setting HTTP headers: func handler(w http.ResponseWriter, r *http.Request) {\n    data, _ := proc.Encode(ctx, &user)\n    w.Header().Set(\"Content-Type\", proc.ContentType())\n    w.Write(data)\n} html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}",{"id":550,"title":183,"titles":551,"content":552,"level":9},"/v0.1.2/cookbook/escape-hatches",[],"Bypass reflection for performance-critical paths",{"id":554,"title":183,"titles":555,"content":556,"level":9},"/v0.1.2/cookbook/escape-hatches#override-interfaces",[],"The processor uses reflection to apply transforms based on struct tags. For performance-critical paths, implement override interfaces to bypass reflection entirely.",{"id":558,"title":559,"titles":560,"content":561,"level":19},"/v0.1.2/cookbook/escape-hatches#available-interfaces","Available Interfaces",[183],"Five interfaces correspond to the five transform types: // Called during Receive instead of reflection-based hashing\ntype Hashable interface {\n    Hash(hashers map[HashAlgo]Hasher) error\n}\n\n// Called during Store instead of reflection-based encryption\ntype Encryptable interface {\n    Encrypt(encryptors map[EncryptAlgo]Encryptor) error\n}\n\n// Called during Load instead of reflection-based decryption\ntype Decryptable interface {\n    Decrypt(encryptors map[EncryptAlgo]Encryptor) error\n}\n\n// Called during Send instead of reflection-based masking\ntype Maskable interface {\n    Mask(maskers map[MaskType]Masker) error\n}\n\n// Called during Send instead of reflection-based redaction\ntype Redactable interface {\n    Redact() error\n}",{"id":563,"title":564,"titles":565,"content":566,"level":19},"/v0.1.2/cookbook/escape-hatches#implementation-example","Implementation Example",[183],"type User struct {\n    ID       string `json:\"id\"`\n    Email    string `json:\"email\"`\n    Password string `json:\"password\"`\n    SSN      string `json:\"ssn\"`\n    Token    string `json:\"token\"`\n}\n\nfunc (u User) Clone() User { return u }\n\n// Bypass reflection for hashing on Receive\nfunc (u *User) Hash(hashers map[cereal.HashAlgo]cereal.Hasher) error {\n    if u.Password != \"\" {\n        hasher, ok := hashers[cereal.HashSHA256]\n        if !ok {\n            return fmt.Errorf(\"missing sha256 hasher\")\n        }\n        hashed, err := hasher.Hash([]byte(u.Password))\n        if err != nil {\n            return err\n        }\n        u.Password = hashed\n    }\n    return nil\n}\n\n// Bypass reflection for encryption on Store\nfunc (u *User) Encrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n    if u.Email != \"\" {\n        enc, ok := encryptors[cereal.EncryptAES]\n        if !ok {\n            return fmt.Errorf(\"missing AES encryptor\")\n        }\n        ciphertext, err := enc.Encrypt([]byte(u.Email))\n        if err != nil {\n            return err\n        }\n        u.Email = base64.StdEncoding.EncodeToString(ciphertext)\n    }\n    return nil\n}\n\n// Bypass reflection for decryption on Load\nfunc (u *User) Decrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n    if u.Email != \"\" {\n        enc, ok := encryptors[cereal.EncryptAES]\n        if !ok {\n            return fmt.Errorf(\"missing AES encryptor\")\n        }\n        ciphertext, err := base64.StdEncoding.DecodeString(u.Email)\n        if err != nil {\n            return err\n        }\n        plaintext, err := enc.Decrypt(ciphertext)\n        if err != nil {\n            return err\n        }\n        u.Email = string(plaintext)\n    }\n    return nil\n}\n\n// Bypass reflection for masking on Send\nfunc (u *User) Mask(maskers map[cereal.MaskType]cereal.Masker) error {\n    if u.Email != \"\" {\n        masker, ok := maskers[cereal.MaskEmail]\n        if !ok {\n            return fmt.Errorf(\"missing email masker\")\n        }\n        u.Email = masker.Mask(u.Email)\n    }\n    if u.SSN != \"\" {\n        masker, ok := maskers[cereal.MaskSSN]\n        if !ok {\n            return fmt.Errorf(\"missing SSN masker\")\n        }\n        u.SSN = masker.Mask(u.SSN)\n    }\n    return nil\n}\n\n// Bypass reflection for redaction on Send\nfunc (u *User) Redact() error {\n    u.Token = \"[REDACTED]\"\n    return nil\n}",{"id":568,"title":569,"titles":570,"content":571,"level":19},"/v0.1.2/cookbook/escape-hatches#when-to-use-override-interfaces","When to Use Override Interfaces",[183],"Use override interfaces when: Hot paths - Transforms happen in tight loops or high-frequency handlersComplex logic - Transform logic that doesn't fit the tag modelConditional transforms - Different behavior based on runtime stateExternal dependencies - Transforms require services not available through setters",{"id":573,"title":574,"titles":575,"content":576,"level":19},"/v0.1.2/cookbook/escape-hatches#performance-comparison","Performance Comparison",[183],"// Reflection-based (default)\nBenchmarkProcessor_Store-8    500000    3200 ns/op    1024 B/op    12 allocs/op\n\n// With Encryptable interface\nBenchmarkProcessor_Store-8    800000    1800 ns/op     512 B/op     6 allocs/op Actual gains depend on type complexity and number of transformed fields.",{"id":578,"title":579,"titles":580,"content":581,"level":19},"/v0.1.2/cookbook/escape-hatches#partial-implementation","Partial Implementation",[183],"You can implement any subset of interfaces: // Only bypass encryption, use reflection for everything else\nfunc (u *User) Encrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n    // Custom encryption logic\n}\n\n// Only bypass masking\nfunc (u *User) Mask(maskers map[cereal.MaskType]cereal.Masker) error {\n    // Custom masking logic\n} Unimplemented interfaces fall back to reflection.",{"id":583,"title":584,"titles":585,"content":586,"level":19},"/v0.1.2/cookbook/escape-hatches#interface-detection","Interface Detection",[183],"The processor checks for interfaces at operation time on the cloned object: // Store checks for Encryptable\nclone := obj.Clone()\nif enc, ok := any(&clone).(Encryptable); ok {\n    enc.Encrypt(p.encryptors) // Uses interface\n} else {\n    p.applyEncrypt(&clone)    // Uses reflection\n}",{"id":588,"title":589,"titles":590,"content":591,"level":19},"/v0.1.2/cookbook/escape-hatches#combining-with-tags","Combining with Tags",[183],"Override interfaces take precedence over tags. If you implement Encryptable, the store.encrypt tags are ignored: type User struct {\n    // This tag is ignored if Encryptable is implemented\n    Email string `store.encrypt:\"aes\"`\n}\n\nfunc (u *User) Encrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n    // Your implementation handles Email (and any other fields)\n} This is all-or-nothing per interface. You can't mix reflection and interface for different fields of the same transform type.",{"id":593,"title":594,"titles":595,"content":596,"level":19},"/v0.1.2/cookbook/escape-hatches#validation-bypass","Validation Bypass",[183],"When using override interfaces, Validate() doesn't check tag requirements for that transform type: type User struct {\n    Email string `store.encrypt:\"custom\"` // \"custom\" not registered\n}\n\nfunc (u *User) Encrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n    // Handles encryption manually\n}\n\nproc, _ := cereal.NewProcessor[User](json.New())\nerr := proc.Validate() // No error - Encryptable bypasses encrypt tag validation",{"id":598,"title":599,"titles":600,"content":601,"level":19},"/v0.1.2/cookbook/escape-hatches#caveats","Caveats",[183],"Consistency - Your implementation must produce equivalent results to reflectionMaintenance - Adding fields requires updating the methodTesting - Test both paths to ensure equivalent behaviorAll-or-nothing - Can't mix reflection and interface for same transform type Consider keeping reflection-based transforms for development and switching to override interfaces only after profiling confirms the need.",{"id":603,"title":604,"titles":605,"content":606,"level":19},"/v0.1.2/cookbook/escape-hatches#example-conditional-encryption","Example: Conditional Encryption",[183],"Override interfaces enable runtime decisions: func (u *User) Encrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n    // Only encrypt if email is not already encrypted\n    if !strings.HasPrefix(u.Email, \"ENC:\") {\n        enc := encryptors[cereal.EncryptAES]\n        ciphertext, _ := enc.Encrypt([]byte(u.Email))\n        u.Email = \"ENC:\" + base64.StdEncoding.EncodeToString(ciphertext)\n    }\n    return nil\n} This isn't possible with tags alone. html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":608,"title":609,"titles":610,"content":611,"level":9},"/v0.1.2/cookbook/key-rotation","Key Rotation",[],"Patterns for rotating encryption keys",{"id":613,"title":609,"titles":614,"content":615,"level":9},"/v0.1.2/cookbook/key-rotation#key-rotation",[],"Cereal's SetEncryptor method allows runtime key changes, but doesn't track which key encrypted which data. This guide covers patterns for managing key rotation.",{"id":617,"title":618,"titles":619,"content":620,"level":19},"/v0.1.2/cookbook/key-rotation#the-challenge","The Challenge",[609],"When you rotate keys, existing encrypted data becomes unreadable: // Original key\nenc1, _ := cereal.AES(key1)\nproc.SetEncryptor(cereal.EncryptAES, enc1)\n\nstored, _ := proc.Write(ctx, &user) // Encrypted with key1\n\n// Rotate key\nenc2, _ := cereal.AES(key2)\nproc.SetEncryptor(cereal.EncryptAES, enc2)\n\nloaded, _ := proc.Read(ctx, stored) // Fails: can't decrypt with key2",{"id":622,"title":623,"titles":624,"content":625,"level":19},"/v0.1.2/cookbook/key-rotation#pattern-1-key-versioning-wrapper","Pattern 1: Key Versioning Wrapper",[609],"Embed key version in ciphertext. Decrypt tries keys in order. type versionedEncryptor struct {\n    current    cereal.Encryptor\n    currentVer byte\n    previous   map[byte]cereal.Encryptor\n}\n\nfunc (e *versionedEncryptor) Encrypt(plaintext []byte) ([]byte, error) {\n    ciphertext, err := e.current.Encrypt(plaintext)\n    if err != nil {\n        return nil, err\n    }\n    // Prepend version byte\n    return append([]byte{e.currentVer}, ciphertext...), nil\n}\n\nfunc (e *versionedEncryptor) Decrypt(ciphertext []byte) ([]byte, error) {\n    if len(ciphertext) \u003C 1 {\n        return nil, fmt.Errorf(\"ciphertext too short\")\n    }\n\n    version := ciphertext[0]\n    data := ciphertext[1:]\n\n    // Try versioned key\n    if version == e.currentVer {\n        return e.current.Decrypt(data)\n    }\n    if enc, ok := e.previous[version]; ok {\n        return enc.Decrypt(data)\n    }\n\n    return nil, fmt.Errorf(\"unknown key version: %d\", version)\n}\n\n// Usage\nfunc NewVersionedEncryptor(currentKey []byte, currentVer byte) *versionedEncryptor {\n    enc, _ := cereal.AES(currentKey)\n    return &versionedEncryptor{\n        current:    enc,\n        currentVer: currentVer,\n        previous:   make(map[byte]cereal.Encryptor),\n    }\n}\n\nfunc (e *versionedEncryptor) AddPreviousKey(key []byte, version byte) error {\n    enc, err := cereal.AES(key)\n    if err != nil {\n        return err\n    }\n    e.previous[version] = enc\n    return nil\n} Register and rotate: venc := NewVersionedEncryptor(currentKey, 2)\nvenc.AddPreviousKey(oldKey, 1)\n\nproc.SetEncryptor(cereal.EncryptAES, venc)\n\n// All data decrypts regardless of which key version encrypted it\n// New encryptions use version 2",{"id":627,"title":628,"titles":629,"content":630,"level":19},"/v0.1.2/cookbook/key-rotation#pattern-2-envelope-encryption","Pattern 2: Envelope Encryption",[609],"Use cereal.Envelope for built-in key separation. The master key encrypts data keys, not data directly. masterKey := loadMasterKey() // From KMS, vault, etc.\nenc, _ := cereal.Envelope(masterKey)\nproc.SetEncryptor(cereal.EncryptEnvelope, enc) Rotation strategy: Each record has its own data encryption key (DEK)DEK is encrypted by master key and stored with the ciphertextTo rotate: re-encrypt DEKs with new master key (data unchanged) // Re-encryption function for envelope rotation\nfunc ReencryptEnvelope(oldMaster, newMaster []byte, ciphertext []byte) ([]byte, error) {\n    oldEnc, _ := cereal.Envelope(oldMaster)\n    newEnc, _ := cereal.Envelope(newMaster)\n\n    // Decrypt with old master (recovers DEK and plaintext)\n    plaintext, err := oldEnc.Decrypt(ciphertext)\n    if err != nil {\n        return nil, err\n    }\n\n    // Re-encrypt with new master (new DEK)\n    return newEnc.Encrypt(plaintext)\n}",{"id":632,"title":633,"titles":634,"content":635,"level":19},"/v0.1.2/cookbook/key-rotation#pattern-3-external-key-management","Pattern 3: External Key Management",[609],"Delegate to a KMS that handles versioning internally. type kmsEncryptor struct {\n    client *kms.Client\n    keyID  string\n}\n\nfunc (e *kmsEncryptor) Encrypt(plaintext []byte) ([]byte, error) {\n    // KMS tracks key versions internally\n    return e.client.Encrypt(e.keyID, plaintext)\n}\n\nfunc (e *kmsEncryptor) Decrypt(ciphertext []byte) ([]byte, error) {\n    // KMS routes to correct key version based on ciphertext metadata\n    return e.client.Decrypt(e.keyID, ciphertext)\n} Works with: AWS KMSGoogle Cloud KMSAzure Key VaultHashiCorp Vault Transit",{"id":637,"title":638,"titles":639,"content":640,"level":19},"/v0.1.2/cookbook/key-rotation#pattern-4-decryptable-interface","Pattern 4: Decryptable Interface",[609],"For complex rotation logic, implement Decryptable: func (u *User) Decrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n    // Try current key\n    enc := encryptors[cereal.EncryptAES]\n    ciphertext, _ := base64.StdEncoding.DecodeString(u.Email)\n\n    plaintext, err := enc.Decrypt(ciphertext)\n    if err == nil {\n        u.Email = string(plaintext)\n        return nil\n    }\n\n    // Fall back to legacy key (stored separately)\n    legacyEnc := encryptors[cereal.EncryptAlgo(\"aes-legacy\")]\n    plaintext, err = legacyEnc.Decrypt(ciphertext)\n    if err != nil {\n        return fmt.Errorf(\"decrypt failed with all keys: %w\", err)\n    }\n\n    u.Email = string(plaintext)\n    return nil\n} Register both: proc.SetEncryptor(cereal.EncryptAES, currentEnc)\nproc.SetEncryptor(cereal.EncryptAlgo(\"aes-legacy\"), legacyEnc)",{"id":642,"title":643,"titles":644,"content":645,"level":19},"/v0.1.2/cookbook/key-rotation#rotation-workflow","Rotation Workflow",[609],"Regardless of pattern, the workflow is: Prepare - Load new key, keep old key availableDual-read - Configure decryption to try both keysRe-encrypt - Background job re-encrypts with new keyVerify - Confirm all data uses new keyRetire - Remove old key from configuration // Step 1-2: Dual-read configuration\nvenc := NewVersionedEncryptor(newKey, 2)\nvenc.AddPreviousKey(oldKey, 1)\nproc.SetEncryptor(cereal.EncryptAES, venc)\n\n// Step 3: Re-encryption job\nfor record := range records {\n    loaded, _ := proc.Read(ctx, record.Data)\n    updated, _ := proc.Write(ctx, loaded) // Re-encrypts with new key\n    record.Data = updated\n    save(record)\n}\n\n// Step 5: Remove old key\nvenc.previous = nil // Or create new encryptor without legacy",{"id":647,"title":648,"titles":649,"content":650,"level":19},"/v0.1.2/cookbook/key-rotation#considerations","Considerations",[609],"Storage overhead: Version bytes add 1 byte per field. Envelope encryption adds ~60 bytes (encrypted DEK + nonce). Performance: KMS calls add latency. Consider caching or local key unwrapping for high-throughput scenarios. Audit: Log key rotation events. Track which key version encrypted each record if compliance requires it. Backup keys: Encrypted backups become unreadable if you lose the key. Maintain secure key backups or use KMS with key history. html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}",{"id":652,"title":653,"titles":654,"content":655,"level":9},"/v0.1.2/cookbook/code-generation","Code Generation",[],"Generate override interfaces to bypass reflection",{"id":657,"title":653,"titles":658,"content":659,"level":9},"/v0.1.2/cookbook/code-generation#code-generation",[],"Override interfaces bypass reflection but require manual implementation. Code generation automates this, keeping implementations in sync with struct tags.",{"id":661,"title":219,"titles":662,"content":663,"level":19},"/v0.1.2/cookbook/code-generation#the-problem",[653],"Manual override implementations drift from struct definitions: type User struct {\n    Email    string `json:\"email\" store.encrypt:\"aes\" load.decrypt:\"aes\"`\n    Password string `json:\"password\" receive.hash:\"argon2\"`\n    SSN      string `json:\"ssn\" send.mask:\"ssn\"`\n    // Add new field...\n    Phone    string `json:\"phone\" send.mask:\"phone\"`\n}\n\n// Oops - forgot to update Mask() for Phone field\nfunc (u *User) Mask(maskers map[cereal.MaskType]cereal.Masker) error {\n    u.Email = maskers[cereal.MaskEmail].Mask(u.Email)\n    u.SSN = maskers[cereal.MaskSSN].Mask(u.SSN)\n    // Phone not masked!\n    return nil\n} Code generation solves this by reading struct tags and generating implementations automatically.",{"id":665,"title":666,"titles":667,"content":668,"level":19},"/v0.1.2/cookbook/code-generation#simple-generator","Simple Generator",[653],"A minimal generator using go/ast and go/parser: //go:build ignore\n\npackage main\n\nimport (\n    \"fmt\"\n    \"go/ast\"\n    \"go/parser\"\n    \"go/token\"\n    \"io\"\n    \"os\"\n    \"reflect\"\n    \"strings\"\n    \"text/template\"\n)\n\nfunc main() {\n    if len(os.Args) \u003C 3 {\n        fmt.Fprintln(os.Stderr, \"usage: go run gen.go \u003Cfile.go> \u003CTypeName>\")\n        os.Exit(1)\n    }\n\n    filename := os.Args[1]\n    typeName := os.Args[2]\n\n    fset := token.NewFileSet()\n    f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)\n    if err != nil {\n        fmt.Fprintln(os.Stderr, err)\n        os.Exit(1)\n    }\n\n    spec := findType(f, typeName)\n    if spec == nil {\n        fmt.Fprintf(os.Stderr, \"type %s not found\\n\", typeName)\n        os.Exit(1)\n    }\n\n    fields := extractFields(spec)\n    if err := generate(os.Stdout, typeName, fields); err != nil {\n        fmt.Fprintln(os.Stderr, err)\n        os.Exit(1)\n    }\n}\n\ntype Field struct {\n    Name       string\n    EncryptAlg string // store.encrypt value\n    DecryptAlg string // load.decrypt value\n    HashAlg    string // receive.hash value\n    MaskType   string // send.mask value\n    RedactVal  string // send.redact value\n}\n\nfunc findType(f *ast.File, name string) *ast.TypeSpec {\n    for _, decl := range f.Decls {\n        gen, ok := decl.(*ast.GenDecl)\n        if !ok || gen.Tok != token.TYPE {\n            continue\n        }\n        for _, spec := range gen.Specs {\n            ts := spec.(*ast.TypeSpec)\n            if ts.Name.Name == name {\n                return ts\n            }\n        }\n    }\n    return nil\n}\n\nfunc extractFields(spec *ast.TypeSpec) []Field {\n    st, ok := spec.Type.(*ast.StructType)\n    if !ok {\n        return nil\n    }\n\n    var fields []Field\n    for _, f := range st.Fields.List {\n        if len(f.Names) == 0 || f.Tag == nil {\n            continue\n        }\n\n        tag := reflect.StructTag(strings.Trim(f.Tag.Value, \"`\"))\n        field := Field{Name: f.Names[0].Name}\n\n        if v, ok := tag.Lookup(\"store.encrypt\"); ok {\n            field.EncryptAlg = v\n        }\n        if v, ok := tag.Lookup(\"load.decrypt\"); ok {\n            field.DecryptAlg = v\n        }\n        if v, ok := tag.Lookup(\"receive.hash\"); ok {\n            field.HashAlg = v\n        }\n        if v, ok := tag.Lookup(\"send.mask\"); ok {\n            field.MaskType = v\n        }\n        if v, ok := tag.Lookup(\"send.redact\"); ok {\n            field.RedactVal = v\n        }\n\n        if field.EncryptAlg != \"\" || field.DecryptAlg != \"\" ||\n            field.HashAlg != \"\" || field.MaskType != \"\" || field.RedactVal != \"\" {\n            fields = append(fields, field)\n        }\n    }\n    return fields\n}\n\nfunc generate(w io.Writer, typeName string, fields []Field) error {\n    return tmpl.Execute(w, map[string]any{\n        \"Type\":   typeName,\n        \"Fields\": fields,\n    })\n}\n\nvar tmpl = template.Must(template.New(\"\").Funcs(template.FuncMap{\n    \"hasEncrypt\": func(fields []Field) bool {\n        for _, f := range fields {\n            if f.EncryptAlg != \"\" {\n                return true\n            }\n        }\n        return false\n    },\n    \"hasDecrypt\": func(fields []Field) bool {\n        for _, f := range fields {\n            if f.DecryptAlg != \"\" {\n                return true\n            }\n        }\n        return false\n    },\n    \"hasHash\": func(fields []Field) bool {\n        for _, f := range fields {\n            if f.HashAlg != \"\" {\n                return true\n            }\n        }\n        return false\n    },\n    \"hasMask\": func(fields []Field) bool {\n        for _, f := range fields {\n            if f.MaskType != \"\" {\n                return true\n            }\n        }\n        return false\n    },\n    \"hasRedact\": func(fields []Field) bool {\n        for _, f := range fields {\n            if f.RedactVal != \"\" {\n                return true\n            }\n        }\n        return false\n    },\n}).Parse(`\n// Code generated by gen.go. DO NOT EDIT.\n\npackage main\n\nimport (\n    \"encoding/base64\"\n    \"github.com/zoobzio/cereal\"\n)\n\n{{- $type := .Type }}\n\n{{ if hasEncrypt .Fields -}}\nfunc (x *{{ $type }}) Encrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n{{- range .Fields }}\n{{- if .EncryptAlg }}\n    if x.{{ .Name }} != \"\" {\n        enc := encryptors[cereal.EncryptAlgo(\"{{ .EncryptAlg }}\")]\n        ciphertext, err := enc.Encrypt([]byte(x.{{ .Name }}))\n        if err != nil {\n            return err\n        }\n        x.{{ .Name }} = base64.StdEncoding.EncodeToString(ciphertext)\n    }\n{{- end }}\n{{- end }}\n    return nil\n}\n{{ end }}\n\n{{ if hasDecrypt .Fields -}}\nfunc (x *{{ $type }}) Decrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n{{- range .Fields }}\n{{- if .DecryptAlg }}\n    if x.{{ .Name }} != \"\" {\n        enc := encryptors[cereal.EncryptAlgo(\"{{ .DecryptAlg }}\")]\n        ciphertext, err := base64.StdEncoding.DecodeString(x.{{ .Name }})\n        if err != nil {\n            return err\n        }\n        plaintext, err := enc.Decrypt(ciphertext)\n        if err != nil {\n            return err\n        }\n        x.{{ .Name }} = string(plaintext)\n    }\n{{- end }}\n{{- end }}\n    return nil\n}\n{{ end }}\n\n{{ if hasHash .Fields -}}\nfunc (x *{{ $type }}) Hash(hashers map[cereal.HashAlgo]cereal.Hasher) error {\n{{- range .Fields }}\n{{- if .HashAlg }}\n    if x.{{ .Name }} != \"\" {\n        hasher := hashers[cereal.HashAlgo(\"{{ .HashAlg }}\")]\n        hashed, err := hasher.Hash([]byte(x.{{ .Name }}))\n        if err != nil {\n            return err\n        }\n        x.{{ .Name }} = hashed\n    }\n{{- end }}\n{{- end }}\n    return nil\n}\n{{ end }}\n\n{{ if hasMask .Fields -}}\nfunc (x *{{ $type }}) Mask(maskers map[cereal.MaskType]cereal.Masker) error {\n{{- range .Fields }}\n{{- if .MaskType }}\n    if x.{{ .Name }} != \"\" {\n        x.{{ .Name }} = maskers[cereal.MaskType(\"{{ .MaskType }}\")].Mask(x.{{ .Name }})\n    }\n{{- end }}\n{{- end }}\n    return nil\n}\n{{ end }}\n\n{{ if hasRedact .Fields -}}\nfunc (x *{{ $type }}) Redact() error {\n{{- range .Fields }}\n{{- if .RedactVal }}\n    x.{{ .Name }} = \"{{ .RedactVal }}\"\n{{- end }}\n{{- end }}\n    return nil\n}\n{{ end }}\n`))",{"id":670,"title":447,"titles":671,"content":672,"level":19},"/v0.1.2/cookbook/code-generation#usage",[653],"Add a go:generate directive to your type file: //go:generate go run gen.go user.go User\n\ntype User struct {\n    ID       string `json:\"id\"`\n    Email    string `json:\"email\" store.encrypt:\"aes\" load.decrypt:\"aes\" send.mask:\"email\"`\n    Password string `json:\"password\" receive.hash:\"argon2\"`\n    SSN      string `json:\"ssn\" send.mask:\"ssn\"`\n    Token    string `json:\"token\" send.redact:\"[REDACTED]\"`\n}\n\nfunc (u User) Clone() User { return u } Run generation: go generate ./... Output (user_cereal.go): // Code generated by gen.go. DO NOT EDIT.\n\npackage main\n\nimport (\n    \"encoding/base64\"\n    \"github.com/zoobzio/cereal\"\n)\n\nfunc (x *User) Encrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n    if x.Email != \"\" {\n        enc := encryptors[cereal.EncryptAlgo(\"aes\")]\n        ciphertext, err := enc.Encrypt([]byte(x.Email))\n        if err != nil {\n            return err\n        }\n        x.Email = base64.StdEncoding.EncodeToString(ciphertext)\n    }\n    return nil\n}\n\nfunc (x *User) Decrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n    if x.Email != \"\" {\n        enc := encryptors[cereal.EncryptAlgo(\"aes\")]\n        ciphertext, err := base64.StdEncoding.DecodeString(x.Email)\n        if err != nil {\n            return err\n        }\n        plaintext, err := enc.Decrypt(ciphertext)\n        if err != nil {\n            return err\n        }\n        x.Email = string(plaintext)\n    }\n    return nil\n}\n\nfunc (x *User) Hash(hashers map[cereal.HashAlgo]cereal.Hasher) error {\n    if x.Password != \"\" {\n        hasher := hashers[cereal.HashAlgo(\"argon2\")]\n        hashed, err := hasher.Hash([]byte(x.Password))\n        if err != nil {\n            return err\n        }\n        x.Password = hashed\n    }\n    return nil\n}\n\nfunc (x *User) Mask(maskers map[cereal.MaskType]cereal.Masker) error {\n    if x.Email != \"\" {\n        x.Email = maskers[cereal.MaskType(\"email\")].Mask(x.Email)\n    }\n    if x.SSN != \"\" {\n        x.SSN = maskers[cereal.MaskType(\"ssn\")].Mask(x.SSN)\n    }\n    return nil\n}\n\nfunc (x *User) Redact() error {\n    x.Token = \"[REDACTED]\"\n    return nil\n}",{"id":674,"title":675,"titles":676,"content":677,"level":19},"/v0.1.2/cookbook/code-generation#integration-with-ci","Integration with CI",[653],"Add generation check to CI: - name: Check generated code\n  run: |\n    go generate ./...\n    git diff --exit-code || (echo \"Generated code out of date\" && exit 1) This fails the build if someone modifies struct tags without regenerating.",{"id":679,"title":680,"titles":681,"content":682,"level":19},"/v0.1.2/cookbook/code-generation#extending-the-generator","Extending the Generator",[653],"The basic generator handles string fields. Extensions to consider: Nested structs: type Address struct {\n    City string `json:\"city\" send.redact:\"[HIDDEN]\"`\n}\n\ntype User struct {\n    Address Address `json:\"address\"`\n} Recursively process embedded/nested types. Slice and map fields: type User struct {\n    Emails []string `json:\"emails\" send.mask:\"email\"`\n} Generate loops over elements. Conditional transforms: type User struct {\n    Email string `json:\"email\" store.encrypt:\"aes,if=Sensitive\"`\n} Parse tag options and generate conditionals. byte fields: type Record struct {\n    Data []byte `json:\"data\" store.encrypt:\"aes\"`\n} Skip base64 encoding for byte slices.",{"id":684,"title":685,"titles":686,"content":687,"level":19},"/v0.1.2/cookbook/code-generation#alternative-reflection-at-init","Alternative: Reflection at Init",[653],"If code generation feels heavy, cache reflection work at init time: var userFields struct {\n    email    reflect.StructField\n    password reflect.StructField\n}\n\nfunc init() {\n    t := reflect.TypeOf(User{})\n    userFields.email, _ = t.FieldByName(\"Email\")\n    userFields.password, _ = t.FieldByName(\"Password\")\n}\n\nfunc (u *User) Encrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n    // Use cached field info\n    // Still faster than full reflection each call\n} This trades startup cost for runtime performance without full code generation.",{"id":689,"title":690,"titles":691,"content":692,"level":19},"/v0.1.2/cookbook/code-generation#when-to-generate","When to Generate",[653],"Use code generation when: Many types — Manual implementations don't scaleFrequent changes — Tags change often, implementations driftStrict correctness — Can't afford missed fieldsTeam scale — Multiple developers modifying types Skip code generation when: Few types — Manual is manageableStable schema — Types rarely changeCustom logic — Transforms need runtime decisionsSimple needs — Reflection overhead acceptable html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}",{"id":694,"title":695,"titles":696,"content":697,"level":9},"/v0.1.2/reference/api","API Reference",[],"Complete API reference for the codec package",{"id":699,"title":695,"titles":700,"content":701,"level":9},"/v0.1.2/reference/api#api-reference",[],"Complete API reference for github.com/zoobzio/cereal.",{"id":703,"title":704,"titles":705,"content":34,"level":19},"/v0.1.2/reference/api#core-interfaces","Core Interfaces",[695],{"id":707,"title":708,"titles":709,"content":710,"level":40},"/v0.1.2/reference/api#codec","Codec",[695,704],"type Codec interface {\n    ContentType() string\n    Marshal(v any) ([]byte, error)\n    Unmarshal(data []byte, v any) error\n} Content-type aware marshaling. Implemented by providers in json/, xml/, yaml/, msgpack/, bson/.",{"id":712,"title":713,"titles":714,"content":715,"level":40},"/v0.1.2/reference/api#clonert","ClonerT",[695,704],"type Cloner[T any] interface {\n    Clone() T\n} Required constraint for types used with Processor[T]. Returns a copy to avoid mutating the original during transforms.",{"id":717,"title":183,"titles":718,"content":34,"level":19},"/v0.1.2/reference/api#override-interfaces",[695],{"id":720,"title":721,"titles":722,"content":723,"level":40},"/v0.1.2/reference/api#hashable","Hashable",[695,183],"type Hashable interface {\n    Hash(hashers map[HashAlgo]Hasher) error\n} Bypasses reflection-based hashing during Receive.",{"id":725,"title":726,"titles":727,"content":728,"level":40},"/v0.1.2/reference/api#encryptable","Encryptable",[695,183],"type Encryptable interface {\n    Encrypt(encryptors map[EncryptAlgo]Encryptor) error\n} Bypasses reflection-based encryption during Store.",{"id":730,"title":731,"titles":732,"content":733,"level":40},"/v0.1.2/reference/api#decryptable","Decryptable",[695,183],"type Decryptable interface {\n    Decrypt(encryptors map[EncryptAlgo]Encryptor) error\n} Bypasses reflection-based decryption during Load.",{"id":735,"title":736,"titles":737,"content":738,"level":40},"/v0.1.2/reference/api#maskable","Maskable",[695,183],"type Maskable interface {\n    Mask(maskers map[MaskType]Masker) error\n} Bypasses reflection-based masking during Send.",{"id":740,"title":741,"titles":742,"content":743,"level":40},"/v0.1.2/reference/api#redactable","Redactable",[695,183],"type Redactable interface {\n    Redact() error\n} Bypasses reflection-based redaction during Send.",{"id":745,"title":746,"titles":747,"content":34,"level":19},"/v0.1.2/reference/api#processor","Processor",[695],{"id":749,"title":750,"titles":751,"content":752,"level":40},"/v0.1.2/reference/api#newprocessor","NewProcessor",[695,746],"func NewProcessor[T Cloner[T]]() (*Processor[T], error) Creates a typed processor with boundary-aware transforms. Scans the type for tags and builds field plans. A codec is not required for the primary API but can be set via SetCodec for codec-aware methods.",{"id":754,"title":755,"titles":756,"content":34,"level":40},"/v0.1.2/reference/api#methods","Methods",[695,746],{"id":758,"title":759,"titles":760,"content":761,"level":762},"/v0.1.2/reference/api#primary-api-t-t","Primary API (T -> T)",[695,746,755],"These methods operate on typed values directly. No codec required.",4,{"id":764,"title":765,"titles":766,"content":767,"level":762},"/v0.1.2/reference/api#receive","Receive",[695,746,755],"func (p *Processor[T]) Receive(ctx context.Context, obj T) (T, error) Clones and applies receive.hash transforms. Use for incoming external data.",{"id":769,"title":770,"titles":771,"content":772,"level":762},"/v0.1.2/reference/api#load","Load",[695,746,755],"func (p *Processor[T]) Load(ctx context.Context, obj T) (T, error) Clones and applies load.decrypt transforms. Use for data from storage.",{"id":774,"title":775,"titles":776,"content":777,"level":762},"/v0.1.2/reference/api#store","Store",[695,746,755],"func (p *Processor[T]) Store(ctx context.Context, obj T) (T, error) Clones and applies store.encrypt transforms. Use for data going to storage.",{"id":779,"title":780,"titles":781,"content":782,"level":762},"/v0.1.2/reference/api#send","Send",[695,746,755],"func (p *Processor[T]) Send(ctx context.Context, obj T) (T, error) Clones and applies send.mask and send.redact transforms. Use for outgoing external data.",{"id":784,"title":785,"titles":786,"content":787,"level":762},"/v0.1.2/reference/api#codec-aware-api-bytes","Codec-Aware API (bytes)",[695,746,755],"These methods require a codec to be set via SetCodec. They handle marshaling/unmarshaling in addition to transforms.",{"id":789,"title":790,"titles":791,"content":792,"level":762},"/v0.1.2/reference/api#decode","Decode",[695,746,755],"func (p *Processor[T]) Decode(ctx context.Context, data []byte) (*T, error) Unmarshals bytes and applies receive.hash transforms. Use for incoming external data in byte form.",{"id":794,"title":795,"titles":796,"content":797,"level":762},"/v0.1.2/reference/api#read","Read",[695,746,755],"func (p *Processor[T]) Read(ctx context.Context, data []byte) (*T, error) Unmarshals bytes and applies load.decrypt transforms. Use for reading data from storage.",{"id":799,"title":800,"titles":801,"content":802,"level":762},"/v0.1.2/reference/api#write","Write",[695,746,755],"func (p *Processor[T]) Write(ctx context.Context, obj *T) ([]byte, error) Clones, applies store.encrypt transforms, and marshals to bytes. Use for writing data to storage.",{"id":804,"title":805,"titles":806,"content":807,"level":762},"/v0.1.2/reference/api#encode","Encode",[695,746,755],"func (p *Processor[T]) Encode(ctx context.Context, obj *T) ([]byte, error) Clones, applies send.mask and send.redact transforms, and marshals to bytes. Use for outgoing external data in byte form.",{"id":809,"title":810,"titles":811,"content":34,"level":762},"/v0.1.2/reference/api#configuration","Configuration",[695,746,755],{"id":813,"title":814,"titles":815,"content":816,"level":762},"/v0.1.2/reference/api#setcodec","SetCodec",[695,746,755],"func (p *Processor[T]) SetCodec(codec Codec) *Processor[T] Sets the codec for codec-aware methods (Decode, Read, Write, Encode). Optional; not needed for the primary T -> T API. Returns the processor for chaining.",{"id":818,"title":819,"titles":820,"content":821,"level":762},"/v0.1.2/reference/api#setencryptor","SetEncryptor",[695,746,755],"func (p *Processor[T]) SetEncryptor(algo EncryptAlgo, enc Encryptor) *Processor[T] Registers an encryptor for an algorithm. Returns the processor for chaining. Thread-safe.",{"id":823,"title":824,"titles":825,"content":826,"level":762},"/v0.1.2/reference/api#sethasher","SetHasher",[695,746,755],"func (p *Processor[T]) SetHasher(algo HashAlgo, h Hasher) *Processor[T] Registers a hasher for an algorithm. Returns the processor for chaining. Thread-safe.",{"id":828,"title":829,"titles":830,"content":831,"level":762},"/v0.1.2/reference/api#setmasker","SetMasker",[695,746,755],"func (p *Processor[T]) SetMasker(mt MaskType, m Masker) *Processor[T] Registers a masker for a mask type. Returns the processor for chaining. Thread-safe.",{"id":833,"title":834,"titles":835,"content":836,"level":762},"/v0.1.2/reference/api#validate","Validate",[695,746,755],"func (p *Processor[T]) Validate() error Checks that all algorithms referenced in tags have registered handlers. Call before using the processor.",{"id":838,"title":839,"titles":840,"content":841,"level":762},"/v0.1.2/reference/api#contenttype","ContentType",[695,746,755],"func (p *Processor[T]) ContentType() string Returns the underlying codec's content type. Requires a codec to be set via SetCodec.",{"id":843,"title":844,"titles":845,"content":34,"level":19},"/v0.1.2/reference/api#plans-cache","Plans Cache",[695],{"id":847,"title":848,"titles":849,"content":850,"level":40},"/v0.1.2/reference/api#resetplanscache","ResetPlansCache",[695,844],"func ResetPlansCache() Clears the internal field plans cache. Primarily useful for test isolation. Most tests don't need this since each processor has independent mutable state.",{"id":852,"title":853,"titles":854,"content":34,"level":19},"/v0.1.2/reference/api#encryptors","Encryptors",[695],{"id":856,"title":857,"titles":858,"content":859,"level":40},"/v0.1.2/reference/api#encryptor-interface","Encryptor Interface",[695,853],"type Encryptor interface {\n    Encrypt(plaintext []byte) ([]byte, error)\n    Decrypt(ciphertext []byte) ([]byte, error)\n}",{"id":861,"title":862,"titles":863,"content":864,"level":40},"/v0.1.2/reference/api#encryptalgo","EncryptAlgo",[695,853],"type EncryptAlgo string\n\nconst (\n    EncryptAES      EncryptAlgo = \"aes\"\n    EncryptRSA      EncryptAlgo = \"rsa\"\n    EncryptEnvelope EncryptAlgo = \"envelope\"\n)",{"id":866,"title":867,"titles":868,"content":869,"level":40},"/v0.1.2/reference/api#aes","AES",[695,853],"func AES(key []byte) (Encryptor, error) AES-GCM encryptor. Key must be 16, 24, or 32 bytes (AES-128, AES-192, AES-256).",{"id":871,"title":872,"titles":873,"content":874,"level":40},"/v0.1.2/reference/api#rsa","RSA",[695,853],"func RSA(pub *rsa.PublicKey, priv *rsa.PrivateKey) Encryptor RSA-OAEP encryptor. Pass nil for priv to create encrypt-only.",{"id":876,"title":877,"titles":878,"content":879,"level":40},"/v0.1.2/reference/api#envelope","Envelope",[695,853],"func Envelope(masterKey []byte) (Encryptor, error) Envelope encryptor using per-message data keys. Master key must be 16, 24, or 32 bytes.",{"id":881,"title":882,"titles":883,"content":34,"level":19},"/v0.1.2/reference/api#hashers","Hashers",[695],{"id":885,"title":886,"titles":887,"content":888,"level":40},"/v0.1.2/reference/api#hasher-interface","Hasher Interface",[695,882],"type Hasher interface {\n    Hash(data []byte) (string, error)\n} Returns the hash as a string (typically hex-encoded or PHC format).",{"id":890,"title":891,"titles":892,"content":893,"level":40},"/v0.1.2/reference/api#hashalgo","HashAlgo",[695,882],"type HashAlgo string\n\nconst (\n    HashSHA256 HashAlgo = \"sha256\"\n    HashSHA512 HashAlgo = \"sha512\"\n    HashArgon2 HashAlgo = \"argon2\"\n    HashBcrypt HashAlgo = \"bcrypt\"\n)",{"id":895,"title":896,"titles":897,"content":898,"level":40},"/v0.1.2/reference/api#sha256hasher","SHA256Hasher",[695,882],"func SHA256Hasher() Hasher SHA-256 hasher. Returns 64 hex characters.",{"id":900,"title":901,"titles":902,"content":903,"level":40},"/v0.1.2/reference/api#sha512hasher","SHA512Hasher",[695,882],"func SHA512Hasher() Hasher SHA-512 hasher. Returns 128 hex characters.",{"id":905,"title":906,"titles":907,"content":908,"level":40},"/v0.1.2/reference/api#argon2","Argon2",[695,882],"func Argon2() Hasher Argon2id hasher with default parameters.",{"id":910,"title":911,"titles":912,"content":913,"level":40},"/v0.1.2/reference/api#argon2withparams","Argon2WithParams",[695,882],"func Argon2WithParams(params Argon2Params) Hasher Argon2id hasher with custom parameters.",{"id":915,"title":916,"titles":917,"content":918,"level":40},"/v0.1.2/reference/api#argon2params","Argon2Params",[695,882],"type Argon2Params struct {\n    Time    uint32 // iterations\n    Memory  uint32 // memory in KB\n    Threads uint8\n    KeyLen  uint32\n    SaltLen uint32\n}\n\nfunc DefaultArgon2Params() Argon2Params",{"id":920,"title":921,"titles":922,"content":923,"level":40},"/v0.1.2/reference/api#bcrypt","Bcrypt",[695,882],"func Bcrypt() Hasher bcrypt hasher with default cost (12, per OWASP recommendations).",{"id":925,"title":926,"titles":927,"content":928,"level":40},"/v0.1.2/reference/api#bcryptwithcost","BcryptWithCost",[695,882],"func BcryptWithCost(cost int) Hasher bcrypt hasher with custom cost.",{"id":930,"title":931,"titles":932,"content":34,"level":19},"/v0.1.2/reference/api#maskers","Maskers",[695],{"id":934,"title":935,"titles":936,"content":937,"level":40},"/v0.1.2/reference/api#masker-interface","Masker Interface",[695,931],"type Masker interface {\n    Mask(value string) string\n} Content-aware partial masking.",{"id":939,"title":940,"titles":941,"content":942,"level":40},"/v0.1.2/reference/api#masktype","MaskType",[695,931],"type MaskType string\n\nconst (\n    MaskSSN   MaskType = \"ssn\"\n    MaskEmail MaskType = \"email\"\n    MaskPhone MaskType = \"phone\"\n    MaskCard  MaskType = \"card\"\n    MaskIP    MaskType = \"ip\"\n    MaskUUID  MaskType = \"uuid\"\n    MaskIBAN  MaskType = \"iban\"\n    MaskName  MaskType = \"name\"\n)",{"id":944,"title":945,"titles":946,"content":947,"level":40},"/v0.1.2/reference/api#validation-functions","Validation Functions",[695,931],"func IsValidEncryptAlgo(algo EncryptAlgo) bool\nfunc IsValidHashAlgo(algo HashAlgo) bool\nfunc IsValidMaskType(mt MaskType) bool",{"id":949,"title":492,"titles":950,"content":34,"level":19},"/v0.1.2/reference/api#providers",[695],{"id":952,"title":501,"titles":953,"content":954,"level":40},"/v0.1.2/reference/api#json",[695,492],"import \"github.com/zoobzio/cereal/json\"\n\nfunc New() cereal.Codec Content-Type: application/json",{"id":956,"title":506,"titles":957,"content":958,"level":40},"/v0.1.2/reference/api#xml",[695,492],"import \"github.com/zoobzio/cereal/xml\"\n\nfunc New() cereal.Codec Content-Type: application/xml",{"id":960,"title":511,"titles":961,"content":962,"level":40},"/v0.1.2/reference/api#yaml",[695,492],"import \"github.com/zoobzio/cereal/yaml\"\n\nfunc New() cereal.Codec Content-Type: application/yaml",{"id":964,"title":516,"titles":965,"content":966,"level":40},"/v0.1.2/reference/api#messagepack",[695,492],"import \"github.com/zoobzio/cereal/msgpack\"\n\nfunc New() cereal.Codec Content-Type: application/msgpack",{"id":968,"title":521,"titles":969,"content":970,"level":40},"/v0.1.2/reference/api#bson",[695,492],"import \"github.com/zoobzio/cereal/bson\"\n\nfunc New() cereal.Codec Content-Type: application/bson",{"id":972,"title":973,"titles":974,"content":975,"level":19},"/v0.1.2/reference/api#signals","Signals",[695],"Codec emits signals via capitan for observability: var (\n    SignalProcessorCreated = capitan.NewSignal(\"cereal.processor.created\", \"...\")\n    SignalReceiveStart     = capitan.NewSignal(\"cereal.receive.start\", \"...\")\n    SignalReceiveComplete  = capitan.NewSignal(\"cereal.receive.complete\", \"...\")\n    SignalLoadStart        = capitan.NewSignal(\"cereal.load.start\", \"...\")\n    SignalLoadComplete     = capitan.NewSignal(\"cereal.load.complete\", \"...\")\n    SignalStoreStart       = capitan.NewSignal(\"cereal.store.start\", \"...\")\n    SignalStoreComplete    = capitan.NewSignal(\"cereal.store.complete\", \"...\")\n    SignalSendStart        = capitan.NewSignal(\"cereal.send.start\", \"...\")\n    SignalSendComplete     = capitan.NewSignal(\"cereal.send.complete\", \"...\")\n) Context flows through for trace correlation. html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sfm-E, html code.shiki .sfm-E{--shiki-default:var(--shiki-variable)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}",{"id":977,"title":978,"titles":979,"content":980,"level":9},"/v0.1.2/reference/tags","Tag Reference",[],"Complete reference for boundary struct tags",{"id":982,"title":978,"titles":983,"content":984,"level":9},"/v0.1.2/reference/tags#tag-reference",[],"Transforms are configured through boundary-specific struct tags.",{"id":986,"title":153,"titles":987,"content":988,"level":19},"/v0.1.2/reference/tags#tag-format",[978],"boundary.operation:\"algorithm\" boundary - When the transform applies (receive, load, store, send)operation - What transform to apply (hash, decrypt, encrypt, mask, redact)algorithm - Which registered handler to use",{"id":990,"title":991,"titles":992,"content":993,"level":19},"/v0.1.2/reference/tags#boundaries-and-operations","Boundaries and Operations",[978],"BoundaryDirectionAvailable OperationsreceiveExternal → ApphashloadStorage → AppdecryptstoreApp → StorageencryptsendApp → Externalmask, redact",{"id":995,"title":996,"titles":997,"content":998,"level":19},"/v0.1.2/reference/tags#receivehash","receive.hash",[978],"Hashes the field when receiving external input. One-way, not reversible. type User struct {\n    Password string `receive.hash:\"sha256\"`\n    Token    string `receive.hash:\"argon2\"`\n} Tag values: ValueConstantHandlersha256HashSHA256Built-insha512HashSHA512Built-inargon2HashArgon2Built-inbcryptHashBcryptBuilt-in Behavior: Hash field valueStore result (hex or PHC format)Original value lost",{"id":1000,"title":1001,"titles":1002,"content":1003,"level":19},"/v0.1.2/reference/tags#storeencrypt","store.encrypt",[978],"Encrypts the field when storing to database. Reversible with load.decrypt. type User struct {\n    Email string `store.encrypt:\"aes\"`\n    SSN   string `store.encrypt:\"rsa\"`\n} Tag values: ValueConstantHandleraesEncryptAESRequires SetEncryptorrsaEncryptRSARequires SetEncryptorenvelopeEncryptEnvelopeRequires SetEncryptor Behavior: Encrypt field valueBase64 encode for string fieldsStore ciphertext Registration required: enc, _ := cereal.AES(key)\nproc.SetEncryptor(cereal.EncryptAES, enc)",{"id":1005,"title":1006,"titles":1007,"content":1008,"level":19},"/v0.1.2/reference/tags#loaddecrypt","load.decrypt",[978],"Decrypts the field when loading from storage. Reverses store.encrypt. type User struct {\n    Email string `load.decrypt:\"aes\"`\n} Tag values: Same as store.encrypt. Behavior: Base64 decode for string fieldsDecrypt ciphertextRestore plaintext Common pattern: Pair with store.encrypt: Email string `store.encrypt:\"aes\" load.decrypt:\"aes\"`",{"id":1010,"title":1011,"titles":1012,"content":1013,"level":19},"/v0.1.2/reference/tags#sendmask","send.mask",[978],"Partially masks the field when sending to external destinations. type User struct {\n    Email string `send.mask:\"email\"`\n    SSN   string `send.mask:\"ssn\"`\n    Phone string `send.mask:\"phone\"`\n} Tag values: ValueConstantExample OutputssnMaskSSN***-**-6789emailMaskEmaila***@example.comphoneMaskPhone(***) ***-4567cardMaskCard************1111ipMaskIP192.168.xxx.xxxuuidMaskUUID550e8400-****-****-...ibanMaskIBANGB82**************5432nameMaskNameJ*** S**** Behavior: Apply content-aware partial maskingOriginal value preserved (not reversible)Built-in maskers require no registration",{"id":1015,"title":1016,"titles":1017,"content":1018,"level":19},"/v0.1.2/reference/tags#sendredact","send.redact",[978],"Replaces the entire field value when sending to external destinations. type User struct {\n    Password string `send.redact:\"***\"`\n    Token    string `send.redact:\"[REDACTED]\"`\n    Secret   string `send.redact:\"\"`\n} Tag value: The replacement string (can be empty). Behavior: Replace entire value with tag valueOriginal value lostNo registration required",{"id":1020,"title":1021,"titles":1022,"content":1023,"level":19},"/v0.1.2/reference/tags#multiple-tags","Multiple Tags",[978],"Combine tags for different boundaries: type User struct {\n    // Full lifecycle: encrypt for storage, decrypt on load, mask for API\n    Email string `store.encrypt:\"aes\" load.decrypt:\"aes\" send.mask:\"email\"`\n\n    // Hash on receive, redact on send\n    Password string `receive.hash:\"argon2\" send.redact:\"***\"`\n} Each tag applies at its specific boundary. They don't conflict.",{"id":1025,"title":423,"titles":1026,"content":1027,"level":19},"/v0.1.2/reference/tags#field-type-support",[978],"Tagstring[]bytereceive.hashYesYesstore.encryptYesYesload.decryptYesYessend.maskYesNosend.redactYesNo For string fields, encrypted values are base64 encoded.",{"id":1029,"title":1030,"titles":1031,"content":1032,"level":19},"/v0.1.2/reference/tags#nested-structs","Nested Structs",[978],"Tags apply to nested struct fields: type Address struct {\n    Street string `json:\"street\"`\n    City   string `json:\"city\" send.redact:\"[HIDDEN]\"`\n}\n\ntype User struct {\n    Name    string  `json:\"name\" send.mask:\"name\"`\n    Address Address `json:\"address\"` // City will be redacted\n} Both direct fields and nested fields are processed.",{"id":1034,"title":1035,"titles":1036,"content":1037,"level":19},"/v0.1.2/reference/tags#pointer-fields","Pointer Fields",[978],"Tags work on pointer fields: type User struct {\n    Email *string `send.mask:\"email\"`\n} Nil pointers are skipped.",{"id":1039,"title":1040,"titles":1041,"content":1042,"level":19},"/v0.1.2/reference/tags#slice-and-map-fields","Slice and Map Fields",[978],"Tags apply to elements within slices and maps: type User struct {\n    Emails []string          `send.mask:\"email\"`\n    Tags   map[string]string `send.redact:\"***\"`\n} Each element is transformed individually.",{"id":1044,"title":168,"titles":1045,"content":1046,"level":19},"/v0.1.2/reference/tags#validation",[978],"Call Validate() to check all tags have registered handlers: type User struct {\n    Email string `store.encrypt:\"custom\"` // Unknown algorithm\n}\n\nproc, _ := cereal.NewProcessor[User](json.New())\nerr := proc.Validate()\n// err: missing encryptor for algorithm \"custom\" (field Email) Validation checks: store.encrypt and load.decrypt values have registered encryptorsreceive.hash values have registered hashers (sha256/sha512 built-in)send.mask values are valid built-in typessend.redact can be any string (no validation)",{"id":1048,"title":1049,"titles":1050,"content":1051,"level":19},"/v0.1.2/reference/tags#override-interface-bypass","Override Interface Bypass",[978],"When override interfaces are implemented, corresponding tags are ignored: type User struct {\n    Email string `store.encrypt:\"aes\"` // Ignored if Encryptable implemented\n}\n\nfunc (u *User) Encrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n    // Your implementation\n} The interface takes full responsibility for that transform type.",{"id":1053,"title":1054,"titles":1055,"content":1056,"level":19},"/v0.1.2/reference/tags#invalid-tag-handling","Invalid Tag Handling",[978],"Unknown boundaries or operations are ignored: type User struct {\n    Email string `unknown.operation:\"value\"` // Ignored\n    Name  string `send.unknown:\"value\"`      // Ignored\n} Only valid boundary.operation combinations are processed. html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}",{"id":1058,"title":1059,"titles":1060,"content":1061,"level":9},"/v0.1.2/reference/errors","Errors",[],"Error handling reference",{"id":1063,"title":1059,"titles":1064,"content":1065,"level":9},"/v0.1.2/reference/errors#errors",[],"Cereal returns wrapped errors with context. This reference covers error types, causes, and handling patterns.",{"id":1067,"title":1068,"titles":1069,"content":34,"level":19},"/v0.1.2/reference/errors#error-categories","Error Categories",[1059],{"id":1071,"title":1072,"titles":1073,"content":1074,"level":40},"/v0.1.2/reference/errors#construction-errors","Construction Errors",[1059,1068],"Returned by NewProcessor: ErrorCauseinvalid tag formatMalformed struct tag (e.g., store.encrypt: without value)unknown boundaryUnrecognized boundary prefix (not receive/load/store/send)unknown operationInvalid operation for boundary (e.g., receive.encrypt) proc, err := cereal.NewProcessor[User](json.New())\nif err != nil {\n    // Tag parsing failed - fix struct tags\n    log.Fatalf(\"processor creation failed: %v\", err)\n}",{"id":1076,"title":1077,"titles":1078,"content":1079,"level":40},"/v0.1.2/reference/errors#validation-errors","Validation Errors",[1059,1068],"Returned by Validate: ErrorCausemissing encryptor for algorithm \"X\"Field uses store.encrypt:\"X\" but no encryptor registeredmissing hasher for algorithm \"X\"Field uses receive.hash:\"X\" but no hasher registeredmissing masker for type \"X\"Field uses send.mask:\"X\" but no masker registered err := proc.Validate()\nif err != nil {\n    // Configuration incomplete - register missing handlers\n    log.Fatalf(\"validation failed: %v\", err)\n} Note: SHA-256, SHA-512, and all mask types are registered by default. Only encryption and Argon2/bcrypt require explicit registration.",{"id":1081,"title":1082,"titles":1083,"content":1084,"level":40},"/v0.1.2/reference/errors#operation-errors","Operation Errors",[1059,1068],"Returned by Receive, Load, Store, Send: ErrorCauseunmarshal: ...Codec failed to parse input bytesmarshal: ...Codec failed to serialize outputencrypt field X: ...Encryption failed for fielddecrypt field X: ...Decryption failed for fieldhash field X: ...Hashing failed for fieldmask field X: ...Masking failed for field (invalid format) user, err := proc.Receive(ctx, data)\nif err != nil {\n    // Check error type for specific handling\n    var unmarshalErr *json.SyntaxError\n    if errors.As(err, &unmarshalErr) {\n        // Invalid JSON input\n    }\n}",{"id":1086,"title":1087,"titles":1088,"content":1089,"level":40},"/v0.1.2/reference/errors#encryptor-errors","Encryptor Errors",[1059,1068],"From built-in encryptors: ErrorCauseinvalid key sizeAES key not 16, 24, or 32 bytesciphertext too shortDecryption input shorter than nonceauthentication failedGCM tag verification failed (wrong key or corrupted data)message too longRSA plaintext exceeds key size limit enc, err := cereal.AES(key)\nif err != nil {\n    // Key size invalid\n}\n\n// During operation\n_, err = proc.Load(ctx, corruptedData)\n// err: \"decrypt field Email: authentication failed\"",{"id":1091,"title":1092,"titles":1093,"content":1094,"level":40},"/v0.1.2/reference/errors#hasher-errors","Hasher Errors",[1059,1068],"From built-in hashers: ErrorCausebcrypt: cost out of rangeCost \u003C 4 or > 31argon2: invalid parametersZero values for required params Hashers rarely fail during operation. SHA hashers never return errors.",{"id":1096,"title":1097,"titles":1098,"content":1099,"level":40},"/v0.1.2/reference/errors#masker-errors","Masker Errors",[1059,1068],"From built-in maskers (ErrMask): TypeValidation FailuressnNot exactly 9 digitsemailMissing @ or @ at position 0phoneFewer than 7 digitscardOutside 13-19 digit rangeipInvalid IPv4 or IPv6 formatuuidWrong segment count or lengths (must be 8-4-4-4-12)ibanOutside 15-34 chars or doesn't start with 2 lettersnameEmpty or whitespace only user := &User{Email: \"invalid\"}\n_, err := proc.Send(ctx, user)\n// err: \"mask field Email: mask failed: invalid email format, missing or misplaced @\"\n\nif errors.Is(err, cereal.ErrMask) {\n    // Input data doesn't match expected format\n}",{"id":1101,"title":1102,"titles":1103,"content":1104,"level":19},"/v0.1.2/reference/errors#error-wrapping","Error Wrapping",[1059],"Errors include context via fmt.Errorf with %w: // Original error\nerr := enc.Decrypt(ciphertext)\n// \"authentication failed\"\n\n// Wrapped by processor\n// \"decrypt field User.Email: authentication failed\" Use errors.Is and errors.As to inspect: if errors.Is(err, someSpecificError) {\n    // Handle specific case\n}\n\nvar targetErr *SomeErrorType\nif errors.As(err, &targetErr) {\n    // Access error details\n}",{"id":1106,"title":1107,"titles":1108,"content":34,"level":19},"/v0.1.2/reference/errors#handling-patterns","Handling Patterns",[1059],{"id":1110,"title":1111,"titles":1112,"content":1113,"level":40},"/v0.1.2/reference/errors#fail-fast","Fail Fast",[1059,1107],"For startup configuration: proc, err := cereal.NewProcessor[User](json.New())\nif err != nil {\n    log.Fatal(err)\n}\n\nenc, err := cereal.AES(key)\nif err != nil {\n    log.Fatal(err)\n}\nproc.SetEncryptor(cereal.EncryptAES, enc)\n\nif err := proc.Validate(); err != nil {\n    log.Fatal(err)\n}",{"id":1115,"title":1116,"titles":1117,"content":1118,"level":40},"/v0.1.2/reference/errors#graceful-degradation","Graceful Degradation",[1059,1107],"For runtime operations: func handleRequest(ctx context.Context, body []byte) (*User, error) {\n    user, err := proc.Receive(ctx, body)\n    if err != nil {\n        // Log full error for debugging\n        log.Printf(\"receive failed: %v\", err)\n\n        // Return safe error to caller\n        return nil, fmt.Errorf(\"invalid request format\")\n    }\n    return user, nil\n}",{"id":1120,"title":1121,"titles":1122,"content":1123,"level":40},"/v0.1.2/reference/errors#retry-with-backoff","Retry with Backoff",[1059,1107],"For transient failures (e.g., KMS errors): func loadWithRetry(ctx context.Context, data []byte) (*User, error) {\n    var user *User\n    var err error\n\n    for attempt := 0; attempt \u003C 3; attempt++ {\n        user, err = proc.Load(ctx, data)\n        if err == nil {\n            return user, nil\n        }\n\n        // Only retry on transient errors\n        if !isTransient(err) {\n            return nil, err\n        }\n\n        time.Sleep(time.Duration(attempt*100) * time.Millisecond)\n    }\n\n    return nil, fmt.Errorf(\"load failed after retries: %w\", err)\n}\n\nfunc isTransient(err error) bool {\n    // KMS timeout, network error, etc.\n    return strings.Contains(err.Error(), \"timeout\") ||\n           strings.Contains(err.Error(), \"connection\")\n}",{"id":1125,"title":1126,"titles":1127,"content":1128,"level":40},"/v0.1.2/reference/errors#field-level-recovery","Field-Level Recovery",[1059,1107],"Using override interfaces for partial success: func (u *User) Decrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n    enc := encryptors[cereal.EncryptAES]\n\n    // Try to decrypt email, leave as-is if failed\n    if u.Email != \"\" {\n        ciphertext, err := base64.StdEncoding.DecodeString(u.Email)\n        if err == nil {\n            plaintext, err := enc.Decrypt(ciphertext)\n            if err == nil {\n                u.Email = string(plaintext)\n            }\n            // Silently skip on decrypt failure (maybe already plaintext)\n        }\n    }\n\n    return nil // Never fail\n}",{"id":1130,"title":1131,"titles":1132,"content":1133,"level":19},"/v0.1.2/reference/errors#nil-pointer-behavior","Nil Pointer Behavior",[1059],"Fields in nil nested structs are silently skipped: type User struct {\n    Profile *Profile `json:\"profile\"`\n}\n\ntype Profile struct {\n    Email string `json:\"email\" store.encrypt:\"aes\"`\n}\n\nuser := &User{Profile: nil}\ndata, err := proc.Store(ctx, user)\n// No error - Profile.Email skipped because Profile is nil This is by design. Nil checks happen during field traversal; nil pointers short-circuit without error.",{"id":1135,"title":1136,"titles":1137,"content":1138,"level":19},"/v0.1.2/reference/errors#validation-timing","Validation Timing",[1059],"Validation runs automatically on the first operation. Missing handlers are caught immediately: proc, _ := cereal.NewProcessor[User](json.New())\n// Forgot SetEncryptor...\n\ndata, err := proc.Store(ctx, &user)\n// err: \"missing encryptor for algorithm \\\"aes\\\" (field Email)\" For earlier detection, call Validate() explicitly at startup: proc, _ := cereal.NewProcessor[User](json.New())\nproc.SetEncryptor(cereal.EncryptAES, enc)\n\nif err := proc.Validate(); err != nil {\n    log.Fatal(err) // Fail fast at startup\n} Both approaches produce the same error—explicit validation just moves it earlier in the lifecycle.",{"id":1140,"title":1141,"titles":1142,"content":1143,"level":19},"/v0.1.2/reference/errors#override-interface-errors","Override Interface Errors",[1059],"When implementing override interfaces, return errors to stop the operation: func (u *User) Encrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n    enc, ok := encryptors[cereal.EncryptAES]\n    if !ok {\n        return fmt.Errorf(\"AES encryptor not configured\")\n    }\n\n    ciphertext, err := enc.Encrypt([]byte(u.Email))\n    if err != nil {\n        return fmt.Errorf(\"encrypt email: %w\", err)\n    }\n\n    u.Email = base64.StdEncoding.EncodeToString(ciphertext)\n    return nil\n} The processor wraps your error and returns it from the operation. html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",[1145],{"title":1146,"path":1147,"stem":1148,"children":1149,"page":1163},"V012","/v0.1.2","v0.1.2",[1150,1152,1164,1175,1186],{"title":6,"path":5,"stem":1151,"description":8},"v0.1.2/1.overview",{"title":1153,"path":1154,"stem":1155,"children":1156,"page":1163},"Learn","/v0.1.2/learn","v0.1.2/2.learn",[1157,1159,1161],{"title":63,"path":62,"stem":1158,"description":65},"v0.1.2/2.learn/1.quickstart",{"title":106,"path":105,"stem":1160,"description":108},"v0.1.2/2.learn/2.concepts",{"title":22,"path":202,"stem":1162,"description":204},"v0.1.2/2.learn/3.architecture",false,{"title":1165,"path":1166,"stem":1167,"children":1168,"page":1163},"Guides","/v0.1.2/guides","v0.1.2/3.guides",[1169,1171,1173],{"title":354,"path":353,"stem":1170,"description":356},"v0.1.2/3.guides/1.encryption",{"title":433,"path":432,"stem":1172,"description":435},"v0.1.2/3.guides/2.masking",{"title":492,"path":491,"stem":1174,"description":494},"v0.1.2/3.guides/3.providers",{"title":1176,"path":1177,"stem":1178,"children":1179,"page":1163},"Cookbook","/v0.1.2/cookbook","v0.1.2/4.cookbook",[1180,1182,1184],{"title":183,"path":550,"stem":1181,"description":552},"v0.1.2/4.cookbook/1.escape-hatches",{"title":609,"path":608,"stem":1183,"description":611},"v0.1.2/4.cookbook/2.key-rotation",{"title":653,"path":652,"stem":1185,"description":655},"v0.1.2/4.cookbook/3.code-generation",{"title":1187,"path":1188,"stem":1189,"children":1190,"page":1163},"Reference","/v0.1.2/reference","v0.1.2/5.reference",[1191,1193,1195],{"title":695,"path":694,"stem":1192,"description":697},"v0.1.2/5.reference/1.api",{"title":978,"path":977,"stem":1194,"description":980},"v0.1.2/5.reference/2.tags",{"title":1059,"path":1058,"stem":1196,"description":1061},"v0.1.2/5.reference/3.errors",[1198],{"title":1146,"path":1147,"stem":1148,"children":1199,"page":1163},[1200,1201,1206,1211,1216],{"title":6,"path":5,"stem":1151},{"title":1153,"path":1154,"stem":1155,"children":1202,"page":1163},[1203,1204,1205],{"title":63,"path":62,"stem":1158},{"title":106,"path":105,"stem":1160},{"title":22,"path":202,"stem":1162},{"title":1165,"path":1166,"stem":1167,"children":1207,"page":1163},[1208,1209,1210],{"title":354,"path":353,"stem":1170},{"title":433,"path":432,"stem":1172},{"title":492,"path":491,"stem":1174},{"title":1176,"path":1177,"stem":1178,"children":1212,"page":1163},[1213,1214,1215],{"title":183,"path":550,"stem":1181},{"title":609,"path":608,"stem":1183},{"title":653,"path":652,"stem":1185},{"title":1187,"path":1188,"stem":1189,"children":1217,"page":1163},[1218,1219,1220],{"title":695,"path":694,"stem":1192},{"title":978,"path":977,"stem":1194},{"title":1059,"path":1058,"stem":1196},[1222,2833,2961],{"id":1223,"title":1224,"body":1225,"description":34,"extension":2826,"icon":2827,"meta":2828,"navigation":1501,"path":2829,"seo":2830,"stem":2831,"__hash__":2832},"resources/readme.md","README",{"type":1226,"value":1227,"toc":2810},"minimark",[1228,1233,1302,1305,1310,1313,1405,1430,1433,1657,1661,1678,1681,1685,2321,2324,2409,2413,2464,2468,2474,2477,2677,2680,2684,2692,2696,2716,2719,2737,2740,2761,2764,2785,2789,2797,2800,2806],[1229,1230,1232],"h1",{"id":1231},"cereal","Cereal",[1234,1235,1236,1247,1255,1263,1271,1279,1286,1294],"p",{},[1237,1238,1242],"a",{"href":1239,"rel":1240},"https://github.com/zoobz-io/cereal/actions/workflows/ci.yml",[1241],"nofollow",[1243,1244],"img",{"alt":1245,"src":1246},"CI","https://github.com/zoobz-io/cereal/actions/workflows/ci.yml/badge.svg",[1237,1248,1251],{"href":1249,"rel":1250},"https://codecov.io/gh/zoobz-io/cereal",[1241],[1243,1252],{"alt":1253,"src":1254},"codecov","https://codecov.io/gh/zoobz-io/cereal/branch/main/graph/badge.svg",[1237,1256,1259],{"href":1257,"rel":1258},"https://goreportcard.com/report/github.com/zoobz-io/cereal",[1241],[1243,1260],{"alt":1261,"src":1262},"Go Report Card","https://goreportcard.com/badge/github.com/zoobz-io/cereal",[1237,1264,1267],{"href":1265,"rel":1266},"https://github.com/zoobz-io/cereal/actions/workflows/codeql.yml",[1241],[1243,1268],{"alt":1269,"src":1270},"CodeQL","https://github.com/zoobz-io/cereal/actions/workflows/codeql.yml/badge.svg",[1237,1272,1275],{"href":1273,"rel":1274},"https://pkg.go.dev/github.com/zoobz-io/cereal",[1241],[1243,1276],{"alt":1277,"src":1278},"Go Reference","https://pkg.go.dev/badge/github.com/zoobz-io/cereal.svg",[1237,1280,1282],{"href":1281},"LICENSE",[1243,1283],{"alt":1284,"src":1285},"License","https://img.shields.io/github/license/zoobz-io/cereal",[1237,1287,1290],{"href":1288,"rel":1289},"https://github.com/zoobz-io/cereal",[1241],[1243,1291],{"alt":1292,"src":1293},"Go Version","https://img.shields.io/github/go-mod/go-version/zoobz-io/cereal",[1237,1295,1298],{"href":1296,"rel":1297},"https://github.com/zoobz-io/cereal/releases",[1241],[1243,1299],{"alt":1300,"src":1301},"Release","https://img.shields.io/github/v/release/zoobz-io/cereal",[1234,1303,1304],{},"Boundary-aware serialization for Go. Transform data differently as it crosses system boundaries—encrypt for storage, mask for APIs, hash on receive.",[1306,1307,1309],"h2",{"id":1308},"four-boundaries-one-processor","Four Boundaries, One Processor",[1234,1311,1312],{},"Data crosses boundaries constantly. Each crossing demands different treatment:",[1314,1315,1319],"pre",{"className":1316,"code":1317,"language":1318,"meta":34,"style":34},"language-go shiki shiki-themes","type User struct {\n    ID       string `json:\"id\"`\n    Email    string `json:\"email\" store.encrypt:\"aes\" load.decrypt:\"aes\" send.mask:\"email\"`\n    Password string `json:\"password\" receive.hash:\"argon2\"`\n    SSN      string `json:\"ssn\" send.mask:\"ssn\"`\n    Token    string `json:\"token\" send.redact:\"[REDACTED]\"`\n}\n","go",[1320,1321,1322,1341,1354,1365,1376,1388,1399],"code",{"__ignoreMap":34},[1323,1324,1326,1330,1334,1337],"span",{"class":1325,"line":9},"line",[1323,1327,1329],{"class":1328},"sUt3r","type",[1323,1331,1333],{"class":1332},"sYBwO"," User",[1323,1335,1336],{"class":1328}," struct",[1323,1338,1340],{"class":1339},"sq5bi"," {\n",[1323,1342,1343,1347,1350],{"class":1325,"line":19},[1323,1344,1346],{"class":1345},"sBGCq","    ID",[1323,1348,1349],{"class":1332},"       string",[1323,1351,1353],{"class":1352},"sxAnc"," `json:\"id\"`\n",[1323,1355,1356,1359,1362],{"class":1325,"line":40},[1323,1357,1358],{"class":1345},"    Email",[1323,1360,1361],{"class":1332},"    string",[1323,1363,1364],{"class":1352}," `json:\"email\" store.encrypt:\"aes\" load.decrypt:\"aes\" send.mask:\"email\"`\n",[1323,1366,1367,1370,1373],{"class":1325,"line":762},[1323,1368,1369],{"class":1345},"    Password",[1323,1371,1372],{"class":1332}," string",[1323,1374,1375],{"class":1352}," `json:\"password\" receive.hash:\"argon2\"`\n",[1323,1377,1379,1382,1385],{"class":1325,"line":1378},5,[1323,1380,1381],{"class":1345},"    SSN",[1323,1383,1384],{"class":1332},"      string",[1323,1386,1387],{"class":1352}," `json:\"ssn\" send.mask:\"ssn\"`\n",[1323,1389,1391,1394,1396],{"class":1325,"line":1390},6,[1323,1392,1393],{"class":1345},"    Token",[1323,1395,1361],{"class":1332},[1323,1397,1398],{"class":1352}," `json:\"token\" send.redact:\"[REDACTED]\"`\n",[1323,1400,1402],{"class":1325,"line":1401},7,[1323,1403,1404],{"class":1339},"}\n",[1406,1407,1408,1415,1420,1425],"ul",{},[1409,1410,1411,1414],"li",{},[1412,1413,765],"strong",{}," — Data arriving from external sources. Hash passwords, normalize inputs.",[1409,1416,1417,1419],{},[1412,1418,770],{}," — Data coming from storage. Decrypt sensitive fields.",[1409,1421,1422,1424],{},[1412,1423,775],{}," — Data going to storage. Encrypt before persisting.",[1409,1426,1427,1429],{},[1412,1428,780],{}," — Data going to external destinations. Mask PII, redact secrets.",[1234,1431,1432],{},"The struct declares intent. The processor handles the rest:",[1314,1434,1436],{"className":1316,"code":1435,"language":1318,"meta":34,"style":34},"proc, _ := cereal.NewProcessor[User]()\nproc.SetEncryptor(cereal.EncryptAES, encryptor)\n\n// Receive: hash password\nreceived, _ := proc.Receive(ctx, user)\n\n// Store: encrypt email\nstored, _ := proc.Store(ctx, received)\n\n// Load: decrypt email\nloaded, _ := proc.Load(ctx, stored)\n\n// Send: mask email, redact token\nsent, _ := proc.Send(ctx, loaded)\n",[1320,1437,1438,1471,1497,1503,1509,1539,1543,1548,1577,1582,1588,1617,1622,1628],{"__ignoreMap":34},[1323,1439,1440,1444,1447,1450,1453,1456,1459,1462,1465,1468],{"class":1325,"line":9},[1323,1441,1443],{"class":1442},"sh8_p","proc",[1323,1445,1446],{"class":1339},",",[1323,1448,1449],{"class":1442}," _",[1323,1451,1452],{"class":1442}," :=",[1323,1454,1455],{"class":1442}," cereal",[1323,1457,1458],{"class":1339},".",[1323,1460,750],{"class":1461},"s5klm",[1323,1463,1464],{"class":1339},"[",[1323,1466,1467],{"class":1332},"User",[1323,1469,1470],{"class":1339},"]()\n",[1323,1472,1473,1475,1477,1479,1482,1484,1486,1489,1491,1494],{"class":1325,"line":19},[1323,1474,1443],{"class":1442},[1323,1476,1458],{"class":1339},[1323,1478,819],{"class":1461},[1323,1480,1481],{"class":1339},"(",[1323,1483,1231],{"class":1442},[1323,1485,1458],{"class":1339},[1323,1487,1488],{"class":1442},"EncryptAES",[1323,1490,1446],{"class":1339},[1323,1492,1493],{"class":1442}," encryptor",[1323,1495,1496],{"class":1339},")\n",[1323,1498,1499],{"class":1325,"line":40},[1323,1500,1502],{"emptyLinePlaceholder":1501},true,"\n",[1323,1504,1505],{"class":1325,"line":762},[1323,1506,1508],{"class":1507},"sLkEo","// Receive: hash password\n",[1323,1510,1511,1514,1516,1518,1520,1523,1525,1527,1529,1532,1534,1537],{"class":1325,"line":1378},[1323,1512,1513],{"class":1442},"received",[1323,1515,1446],{"class":1339},[1323,1517,1449],{"class":1442},[1323,1519,1452],{"class":1442},[1323,1521,1522],{"class":1442}," proc",[1323,1524,1458],{"class":1339},[1323,1526,765],{"class":1461},[1323,1528,1481],{"class":1339},[1323,1530,1531],{"class":1442},"ctx",[1323,1533,1446],{"class":1339},[1323,1535,1536],{"class":1442}," user",[1323,1538,1496],{"class":1339},[1323,1540,1541],{"class":1325,"line":1390},[1323,1542,1502],{"emptyLinePlaceholder":1501},[1323,1544,1545],{"class":1325,"line":1401},[1323,1546,1547],{"class":1507},"// Store: encrypt email\n",[1323,1549,1551,1554,1556,1558,1560,1562,1564,1566,1568,1570,1572,1575],{"class":1325,"line":1550},8,[1323,1552,1553],{"class":1442},"stored",[1323,1555,1446],{"class":1339},[1323,1557,1449],{"class":1442},[1323,1559,1452],{"class":1442},[1323,1561,1522],{"class":1442},[1323,1563,1458],{"class":1339},[1323,1565,775],{"class":1461},[1323,1567,1481],{"class":1339},[1323,1569,1531],{"class":1442},[1323,1571,1446],{"class":1339},[1323,1573,1574],{"class":1442}," received",[1323,1576,1496],{"class":1339},[1323,1578,1580],{"class":1325,"line":1579},9,[1323,1581,1502],{"emptyLinePlaceholder":1501},[1323,1583,1585],{"class":1325,"line":1584},10,[1323,1586,1587],{"class":1507},"// Load: decrypt email\n",[1323,1589,1591,1594,1596,1598,1600,1602,1604,1606,1608,1610,1612,1615],{"class":1325,"line":1590},11,[1323,1592,1593],{"class":1442},"loaded",[1323,1595,1446],{"class":1339},[1323,1597,1449],{"class":1442},[1323,1599,1452],{"class":1442},[1323,1601,1522],{"class":1442},[1323,1603,1458],{"class":1339},[1323,1605,770],{"class":1461},[1323,1607,1481],{"class":1339},[1323,1609,1531],{"class":1442},[1323,1611,1446],{"class":1339},[1323,1613,1614],{"class":1442}," stored",[1323,1616,1496],{"class":1339},[1323,1618,1620],{"class":1325,"line":1619},12,[1323,1621,1502],{"emptyLinePlaceholder":1501},[1323,1623,1625],{"class":1325,"line":1624},13,[1323,1626,1627],{"class":1507},"// Send: mask email, redact token\n",[1323,1629,1631,1634,1636,1638,1640,1642,1644,1646,1648,1650,1652,1655],{"class":1325,"line":1630},14,[1323,1632,1633],{"class":1442},"sent",[1323,1635,1446],{"class":1339},[1323,1637,1449],{"class":1442},[1323,1639,1452],{"class":1442},[1323,1641,1522],{"class":1442},[1323,1643,1458],{"class":1339},[1323,1645,780],{"class":1461},[1323,1647,1481],{"class":1339},[1323,1649,1531],{"class":1442},[1323,1651,1446],{"class":1339},[1323,1653,1654],{"class":1442}," loaded",[1323,1656,1496],{"class":1339},[1306,1658,1660],{"id":1659},"install","Install",[1314,1662,1666],{"className":1663,"code":1664,"language":1665,"meta":34,"style":34},"language-bash shiki shiki-themes","go get github.com/zoobz-io/cereal\n","bash",[1320,1667,1668],{"__ignoreMap":34},[1323,1669,1670,1672,1675],{"class":1325,"line":9},[1323,1671,1318],{"class":1461},[1323,1673,1674],{"class":1352}," get",[1323,1676,1677],{"class":1352}," github.com/zoobz-io/cereal\n",[1234,1679,1680],{},"Requires Go 1.24+",[1306,1682,1684],{"id":1683},"quick-start","Quick Start",[1314,1686,1688],{"className":1316,"code":1687,"language":1318,"meta":34,"style":34},"package main\n\nimport (\n    \"context\"\n    \"fmt\"\n\n    \"github.com/zoobz-io/cereal\"\n    \"github.com/zoobz-io/cereal/json\"\n)\n\ntype User struct {\n    ID       string `json:\"id\"`\n    Email    string `json:\"email\" store.encrypt:\"aes\" load.decrypt:\"aes\" send.mask:\"email\"`\n    Password string `json:\"password\" receive.hash:\"argon2\"`\n}\n\nfunc (u User) Clone() User { return u }\n\nfunc main() {\n    ctx := context.Background()\n\n    // Create processor\n    proc, _ := cereal.NewProcessor[User]()\n\n    // Configure encryption\n    enc, _ := cereal.AES([]byte(\"32-byte-key-for-aes-256-encrypt!\"))\n    proc.SetEncryptor(cereal.EncryptAES, enc)\n\n    user := User{\n        ID:       \"123\",\n        Email:    \"alice@example.com\",\n        Password: \"secret\",\n    }\n\n    // Store: encrypts email before persisting\n    stored, _ := proc.Store(ctx, user)\n    fmt.Println(stored.Email)\n    // \u003Cencrypted>\n\n    // Load: decrypts email from storage\n    loaded, _ := proc.Load(ctx, stored)\n    fmt.Println(loaded.Email)\n    // alice@example.com\n\n    // Send: masks email for API response\n    sent, _ := proc.Send(ctx, user)\n    fmt.Println(sent.Email)\n    // a***@example.com\n\n    // Optional: codec-aware API for marshaling\n    proc.SetCodec(json.New())\n    sentBytes, _ := proc.Encode(ctx, &user)\n    fmt.Println(string(sentBytes))\n    // {\"id\":\"123\",\"email\":\"a***@example.com\",\"password\":\"secret\"}\n}\n",[1320,1689,1690,1698,1702,1711,1716,1721,1725,1730,1735,1739,1743,1753,1761,1769,1777,1782,1787,1826,1831,1843,1862,1867,1873,1897,1902,1908,1940,1964,1969,1982,1997,2010,2023,2029,2034,2040,2068,2090,2096,2101,2107,2135,2154,2160,2165,2171,2199,2218,2224,2229,2235,2257,2289,2310,2316],{"__ignoreMap":34},[1323,1691,1692,1695],{"class":1325,"line":9},[1323,1693,1694],{"class":1328},"package",[1323,1696,1697],{"class":1332}," main\n",[1323,1699,1700],{"class":1325,"line":19},[1323,1701,1502],{"emptyLinePlaceholder":1501},[1323,1703,1704,1707],{"class":1325,"line":40},[1323,1705,1706],{"class":1328},"import",[1323,1708,1710],{"class":1709},"soy-K"," (\n",[1323,1712,1713],{"class":1325,"line":762},[1323,1714,1715],{"class":1352},"    \"context\"\n",[1323,1717,1718],{"class":1325,"line":1378},[1323,1719,1720],{"class":1352},"    \"fmt\"\n",[1323,1722,1723],{"class":1325,"line":1390},[1323,1724,1502],{"emptyLinePlaceholder":1501},[1323,1726,1727],{"class":1325,"line":1401},[1323,1728,1729],{"class":1352},"    \"github.com/zoobz-io/cereal\"\n",[1323,1731,1732],{"class":1325,"line":1550},[1323,1733,1734],{"class":1352},"    \"github.com/zoobz-io/cereal/json\"\n",[1323,1736,1737],{"class":1325,"line":1579},[1323,1738,1496],{"class":1709},[1323,1740,1741],{"class":1325,"line":1584},[1323,1742,1502],{"emptyLinePlaceholder":1501},[1323,1744,1745,1747,1749,1751],{"class":1325,"line":1590},[1323,1746,1329],{"class":1328},[1323,1748,1333],{"class":1332},[1323,1750,1336],{"class":1328},[1323,1752,1340],{"class":1339},[1323,1754,1755,1757,1759],{"class":1325,"line":1619},[1323,1756,1346],{"class":1345},[1323,1758,1349],{"class":1332},[1323,1760,1353],{"class":1352},[1323,1762,1763,1765,1767],{"class":1325,"line":1624},[1323,1764,1358],{"class":1345},[1323,1766,1361],{"class":1332},[1323,1768,1364],{"class":1352},[1323,1770,1771,1773,1775],{"class":1325,"line":1630},[1323,1772,1369],{"class":1345},[1323,1774,1372],{"class":1332},[1323,1776,1375],{"class":1352},[1323,1778,1780],{"class":1325,"line":1779},15,[1323,1781,1404],{"class":1339},[1323,1783,1785],{"class":1325,"line":1784},16,[1323,1786,1502],{"emptyLinePlaceholder":1501},[1323,1788,1790,1793,1796,1800,1802,1805,1808,1811,1813,1816,1820,1823],{"class":1325,"line":1789},17,[1323,1791,1792],{"class":1328},"func",[1323,1794,1795],{"class":1339}," (",[1323,1797,1799],{"class":1798},"sSYET","u ",[1323,1801,1467],{"class":1332},[1323,1803,1804],{"class":1339},")",[1323,1806,1807],{"class":1461}," Clone",[1323,1809,1810],{"class":1339},"()",[1323,1812,1333],{"class":1332},[1323,1814,1815],{"class":1339}," {",[1323,1817,1819],{"class":1818},"sW3Qg"," return",[1323,1821,1822],{"class":1442}," u",[1323,1824,1825],{"class":1339}," }\n",[1323,1827,1829],{"class":1325,"line":1828},18,[1323,1830,1502],{"emptyLinePlaceholder":1501},[1323,1832,1834,1836,1839,1841],{"class":1325,"line":1833},19,[1323,1835,1792],{"class":1328},[1323,1837,1838],{"class":1461}," main",[1323,1840,1810],{"class":1339},[1323,1842,1340],{"class":1339},[1323,1844,1846,1849,1851,1854,1856,1859],{"class":1325,"line":1845},20,[1323,1847,1848],{"class":1442},"    ctx",[1323,1850,1452],{"class":1442},[1323,1852,1853],{"class":1442}," context",[1323,1855,1458],{"class":1339},[1323,1857,1858],{"class":1461},"Background",[1323,1860,1861],{"class":1339},"()\n",[1323,1863,1865],{"class":1325,"line":1864},21,[1323,1866,1502],{"emptyLinePlaceholder":1501},[1323,1868,1870],{"class":1325,"line":1869},22,[1323,1871,1872],{"class":1507},"    // Create processor\n",[1323,1874,1876,1879,1881,1883,1885,1887,1889,1891,1893,1895],{"class":1325,"line":1875},23,[1323,1877,1878],{"class":1442},"    proc",[1323,1880,1446],{"class":1339},[1323,1882,1449],{"class":1442},[1323,1884,1452],{"class":1442},[1323,1886,1455],{"class":1442},[1323,1888,1458],{"class":1339},[1323,1890,750],{"class":1461},[1323,1892,1464],{"class":1339},[1323,1894,1467],{"class":1332},[1323,1896,1470],{"class":1339},[1323,1898,1900],{"class":1325,"line":1899},24,[1323,1901,1502],{"emptyLinePlaceholder":1501},[1323,1903,1905],{"class":1325,"line":1904},25,[1323,1906,1907],{"class":1507},"    // Configure encryption\n",[1323,1909,1911,1914,1916,1918,1920,1922,1924,1926,1929,1932,1934,1937],{"class":1325,"line":1910},26,[1323,1912,1913],{"class":1442},"    enc",[1323,1915,1446],{"class":1339},[1323,1917,1449],{"class":1442},[1323,1919,1452],{"class":1442},[1323,1921,1455],{"class":1442},[1323,1923,1458],{"class":1339},[1323,1925,867],{"class":1461},[1323,1927,1928],{"class":1339},"([]",[1323,1930,1931],{"class":1332},"byte",[1323,1933,1481],{"class":1339},[1323,1935,1936],{"class":1352},"\"32-byte-key-for-aes-256-encrypt!\"",[1323,1938,1939],{"class":1339},"))\n",[1323,1941,1943,1945,1947,1949,1951,1953,1955,1957,1959,1962],{"class":1325,"line":1942},27,[1323,1944,1878],{"class":1442},[1323,1946,1458],{"class":1339},[1323,1948,819],{"class":1461},[1323,1950,1481],{"class":1339},[1323,1952,1231],{"class":1442},[1323,1954,1458],{"class":1339},[1323,1956,1488],{"class":1442},[1323,1958,1446],{"class":1339},[1323,1960,1961],{"class":1442}," enc",[1323,1963,1496],{"class":1339},[1323,1965,1967],{"class":1325,"line":1966},28,[1323,1968,1502],{"emptyLinePlaceholder":1501},[1323,1970,1972,1975,1977,1979],{"class":1325,"line":1971},29,[1323,1973,1974],{"class":1442},"    user",[1323,1976,1452],{"class":1442},[1323,1978,1333],{"class":1332},[1323,1980,1981],{"class":1339},"{\n",[1323,1983,1985,1988,1991,1994],{"class":1325,"line":1984},30,[1323,1986,1987],{"class":1345},"        ID",[1323,1989,1990],{"class":1339},":",[1323,1992,1993],{"class":1352},"       \"123\"",[1323,1995,1996],{"class":1339},",\n",[1323,1998,2000,2003,2005,2008],{"class":1325,"line":1999},31,[1323,2001,2002],{"class":1345},"        Email",[1323,2004,1990],{"class":1339},[1323,2006,2007],{"class":1352},"    \"alice@example.com\"",[1323,2009,1996],{"class":1339},[1323,2011,2013,2016,2018,2021],{"class":1325,"line":2012},32,[1323,2014,2015],{"class":1345},"        Password",[1323,2017,1990],{"class":1339},[1323,2019,2020],{"class":1352}," \"secret\"",[1323,2022,1996],{"class":1339},[1323,2024,2026],{"class":1325,"line":2025},33,[1323,2027,2028],{"class":1339},"    }\n",[1323,2030,2032],{"class":1325,"line":2031},34,[1323,2033,1502],{"emptyLinePlaceholder":1501},[1323,2035,2037],{"class":1325,"line":2036},35,[1323,2038,2039],{"class":1507},"    // Store: encrypts email before persisting\n",[1323,2041,2043,2046,2048,2050,2052,2054,2056,2058,2060,2062,2064,2066],{"class":1325,"line":2042},36,[1323,2044,2045],{"class":1442},"    stored",[1323,2047,1446],{"class":1339},[1323,2049,1449],{"class":1442},[1323,2051,1452],{"class":1442},[1323,2053,1522],{"class":1442},[1323,2055,1458],{"class":1339},[1323,2057,775],{"class":1461},[1323,2059,1481],{"class":1339},[1323,2061,1531],{"class":1442},[1323,2063,1446],{"class":1339},[1323,2065,1536],{"class":1442},[1323,2067,1496],{"class":1339},[1323,2069,2071,2074,2076,2079,2081,2083,2085,2088],{"class":1325,"line":2070},37,[1323,2072,2073],{"class":1442},"    fmt",[1323,2075,1458],{"class":1339},[1323,2077,2078],{"class":1461},"Println",[1323,2080,1481],{"class":1339},[1323,2082,1553],{"class":1442},[1323,2084,1458],{"class":1339},[1323,2086,2087],{"class":1442},"Email",[1323,2089,1496],{"class":1339},[1323,2091,2093],{"class":1325,"line":2092},38,[1323,2094,2095],{"class":1507},"    // \u003Cencrypted>\n",[1323,2097,2099],{"class":1325,"line":2098},39,[1323,2100,1502],{"emptyLinePlaceholder":1501},[1323,2102,2104],{"class":1325,"line":2103},40,[1323,2105,2106],{"class":1507},"    // Load: decrypts email from storage\n",[1323,2108,2110,2113,2115,2117,2119,2121,2123,2125,2127,2129,2131,2133],{"class":1325,"line":2109},41,[1323,2111,2112],{"class":1442},"    loaded",[1323,2114,1446],{"class":1339},[1323,2116,1449],{"class":1442},[1323,2118,1452],{"class":1442},[1323,2120,1522],{"class":1442},[1323,2122,1458],{"class":1339},[1323,2124,770],{"class":1461},[1323,2126,1481],{"class":1339},[1323,2128,1531],{"class":1442},[1323,2130,1446],{"class":1339},[1323,2132,1614],{"class":1442},[1323,2134,1496],{"class":1339},[1323,2136,2138,2140,2142,2144,2146,2148,2150,2152],{"class":1325,"line":2137},42,[1323,2139,2073],{"class":1442},[1323,2141,1458],{"class":1339},[1323,2143,2078],{"class":1461},[1323,2145,1481],{"class":1339},[1323,2147,1593],{"class":1442},[1323,2149,1458],{"class":1339},[1323,2151,2087],{"class":1442},[1323,2153,1496],{"class":1339},[1323,2155,2157],{"class":1325,"line":2156},43,[1323,2158,2159],{"class":1507},"    // alice@example.com\n",[1323,2161,2163],{"class":1325,"line":2162},44,[1323,2164,1502],{"emptyLinePlaceholder":1501},[1323,2166,2168],{"class":1325,"line":2167},45,[1323,2169,2170],{"class":1507},"    // Send: masks email for API response\n",[1323,2172,2174,2177,2179,2181,2183,2185,2187,2189,2191,2193,2195,2197],{"class":1325,"line":2173},46,[1323,2175,2176],{"class":1442},"    sent",[1323,2178,1446],{"class":1339},[1323,2180,1449],{"class":1442},[1323,2182,1452],{"class":1442},[1323,2184,1522],{"class":1442},[1323,2186,1458],{"class":1339},[1323,2188,780],{"class":1461},[1323,2190,1481],{"class":1339},[1323,2192,1531],{"class":1442},[1323,2194,1446],{"class":1339},[1323,2196,1536],{"class":1442},[1323,2198,1496],{"class":1339},[1323,2200,2202,2204,2206,2208,2210,2212,2214,2216],{"class":1325,"line":2201},47,[1323,2203,2073],{"class":1442},[1323,2205,1458],{"class":1339},[1323,2207,2078],{"class":1461},[1323,2209,1481],{"class":1339},[1323,2211,1633],{"class":1442},[1323,2213,1458],{"class":1339},[1323,2215,2087],{"class":1442},[1323,2217,1496],{"class":1339},[1323,2219,2221],{"class":1325,"line":2220},48,[1323,2222,2223],{"class":1507},"    // a***@example.com\n",[1323,2225,2227],{"class":1325,"line":2226},49,[1323,2228,1502],{"emptyLinePlaceholder":1501},[1323,2230,2232],{"class":1325,"line":2231},50,[1323,2233,2234],{"class":1507},"    // Optional: codec-aware API for marshaling\n",[1323,2236,2238,2240,2242,2244,2246,2249,2251,2254],{"class":1325,"line":2237},51,[1323,2239,1878],{"class":1442},[1323,2241,1458],{"class":1339},[1323,2243,814],{"class":1461},[1323,2245,1481],{"class":1339},[1323,2247,2248],{"class":1442},"json",[1323,2250,1458],{"class":1339},[1323,2252,2253],{"class":1461},"New",[1323,2255,2256],{"class":1339},"())\n",[1323,2258,2260,2263,2265,2267,2269,2271,2273,2275,2277,2279,2281,2284,2287],{"class":1325,"line":2259},52,[1323,2261,2262],{"class":1442},"    sentBytes",[1323,2264,1446],{"class":1339},[1323,2266,1449],{"class":1442},[1323,2268,1452],{"class":1442},[1323,2270,1522],{"class":1442},[1323,2272,1458],{"class":1339},[1323,2274,805],{"class":1461},[1323,2276,1481],{"class":1339},[1323,2278,1531],{"class":1442},[1323,2280,1446],{"class":1339},[1323,2282,2283],{"class":1818}," &",[1323,2285,2286],{"class":1442},"user",[1323,2288,1496],{"class":1339},[1323,2290,2292,2294,2296,2298,2300,2303,2305,2308],{"class":1325,"line":2291},53,[1323,2293,2073],{"class":1442},[1323,2295,1458],{"class":1339},[1323,2297,2078],{"class":1461},[1323,2299,1481],{"class":1339},[1323,2301,2302],{"class":1332},"string",[1323,2304,1481],{"class":1339},[1323,2306,2307],{"class":1442},"sentBytes",[1323,2309,1939],{"class":1339},[1323,2311,2313],{"class":1325,"line":2312},54,[1323,2314,2315],{"class":1507},"    // {\"id\":\"123\",\"email\":\"a***@example.com\",\"password\":\"secret\"}\n",[1323,2317,2319],{"class":1325,"line":2318},55,[1323,2320,1404],{"class":1339},[1306,2322,27],{"id":2323},"capabilities",[2325,2326,2327,2346],"table",{},[2328,2329,2330],"thead",{},[2331,2332,2333,2337,2340,2343],"tr",{},[2334,2335,2336],"th",{},"Capability",[2334,2338,2339],{},"Boundaries",[2334,2341,2342],{},"Description",[2334,2344,2345],{},"Docs",[2347,2348,2349,2366,2381,2396],"tbody",{},[2331,2350,2351,2354,2357,2360],{},[2352,2353,354],"td",{},[2352,2355,2356],{},"store/load",[2352,2358,2359],{},"AES-GCM, RSA-OAEP, envelope",[2352,2361,2362],{},[1237,2363,2365],{"href":2364},"docs/guides/encryption","Guide",[2331,2367,2368,2370,2373,2376],{},[2352,2369,433],{},[2352,2371,2372],{},"send",[2352,2374,2375],{},"Email, SSN, phone, card, IP, UUID, IBAN, name",[2352,2377,2378],{},[1237,2379,2365],{"href":2380},"docs/guides/masking",[2331,2382,2383,2385,2388,2391],{},[2352,2384,388],{},[2352,2386,2387],{},"receive",[2352,2389,2390],{},"SHA-256, SHA-512, Argon2, bcrypt",[2352,2392,2393],{},[1237,2394,1187],{"href":2395},"docs/reference/tags",[2331,2397,2398,2400,2402,2405],{},[2352,2399,452],{},[2352,2401,2372],{},[2352,2403,2404],{},"Full replacement with custom string",[2352,2406,2407],{},[1237,2408,1187],{"href":2395},[1306,2410,2412],{"id":2411},"why-cereal","Why Cereal?",[1406,2414,2415,2421,2427,2433,2445,2451,2459],{},[1409,2416,2417,2420],{},[1412,2418,2419],{},"Boundary-specific transforms"," — Different rules for storage vs. API responses vs. incoming data",[1409,2422,2423,2426],{},[1412,2424,2425],{},"Declarative via struct tags"," — Security requirements live with the type definition",[1409,2428,2429,2432],{},[1412,2430,2431],{},"Non-destructive"," — Original values never modified; processor clones before transforming",[1409,2434,2435,2438,2439,2442,2443],{},[1412,2436,2437],{},"Type-safe generics"," — ",[1320,2440,2441],{},"Processor[User]"," only accepts ",[1320,2444,1467],{},[1409,2446,2447,2450],{},[1412,2448,2449],{},"Thread-safe"," — Processors safe for concurrent use across goroutines",[1409,2452,2453,2456,2457],{},[1412,2454,2455],{},"Provider agnostic"," — JSON, YAML, XML, MessagePack, BSON via optional ",[1320,2458,814],{},[1409,2460,2461,2463],{},[1412,2462,58],{}," — Emits signals for metrics and tracing via capitan",[1306,2465,2467],{"id":2466},"security-as-structure","Security as Structure",[1234,2469,2470,2471,1458],{},"Cereal enables a pattern: ",[1412,2472,2473],{},"declare sensitivity once, enforce everywhere",[1234,2475,2476],{},"Data sensitivity lives in the type definition, not scattered across handlers. When a field is marked for encryption or masking, every boundary crossing respects that declaration automatically. Business logic remains unaware of security transforms—it works with plain structs while the processor handles the rest.",[1314,2478,2480],{"className":1316,"code":2479,"language":1318,"meta":34,"style":34},"// The type declares intent\ntype Payment struct {\n    ID     string `json:\"id\"`\n    Card   string `json:\"card\" store.encrypt:\"aes\" send.mask:\"card\"`\n    Amount int    `json:\"amount\"`\n}\n\n// Business logic stays clean\nfunc ProcessPayment(p *Payment) error {\n    // No encryption calls, no masking logic\n    // Just domain operations on plain fields\n    return chargeCard(p.Card, p.Amount)\n}\n\n// Boundaries handle transforms\nstored, _ := proc.Store(ctx, payment)    // Card encrypted\nsent, _ := proc.Send(ctx, payment)       // Card masked\n",[1320,2481,2482,2487,2498,2507,2518,2529,2533,2537,2542,2566,2571,2576,2605,2609,2613,2618,2648],{"__ignoreMap":34},[1323,2483,2484],{"class":1325,"line":9},[1323,2485,2486],{"class":1507},"// The type declares intent\n",[1323,2488,2489,2491,2494,2496],{"class":1325,"line":19},[1323,2490,1329],{"class":1328},[1323,2492,2493],{"class":1332}," Payment",[1323,2495,1336],{"class":1328},[1323,2497,1340],{"class":1339},[1323,2499,2500,2502,2505],{"class":1325,"line":40},[1323,2501,1346],{"class":1345},[1323,2503,2504],{"class":1332},"     string",[1323,2506,1353],{"class":1352},[1323,2508,2509,2512,2515],{"class":1325,"line":762},[1323,2510,2511],{"class":1345},"    Card",[1323,2513,2514],{"class":1332},"   string",[1323,2516,2517],{"class":1352}," `json:\"card\" store.encrypt:\"aes\" send.mask:\"card\"`\n",[1323,2519,2520,2523,2526],{"class":1325,"line":1378},[1323,2521,2522],{"class":1345},"    Amount",[1323,2524,2525],{"class":1332}," int",[1323,2527,2528],{"class":1352},"    `json:\"amount\"`\n",[1323,2530,2531],{"class":1325,"line":1390},[1323,2532,1404],{"class":1339},[1323,2534,2535],{"class":1325,"line":1401},[1323,2536,1502],{"emptyLinePlaceholder":1501},[1323,2538,2539],{"class":1325,"line":1550},[1323,2540,2541],{"class":1507},"// Business logic stays clean\n",[1323,2543,2544,2546,2549,2551,2553,2556,2559,2561,2564],{"class":1325,"line":1579},[1323,2545,1792],{"class":1328},[1323,2547,2548],{"class":1461}," ProcessPayment",[1323,2550,1481],{"class":1339},[1323,2552,1234],{"class":1798},[1323,2554,2555],{"class":1818}," *",[1323,2557,2558],{"class":1332},"Payment",[1323,2560,1804],{"class":1339},[1323,2562,2563],{"class":1332}," error",[1323,2565,1340],{"class":1339},[1323,2567,2568],{"class":1325,"line":1584},[1323,2569,2570],{"class":1507},"    // No encryption calls, no masking logic\n",[1323,2572,2573],{"class":1325,"line":1590},[1323,2574,2575],{"class":1507},"    // Just domain operations on plain fields\n",[1323,2577,2578,2581,2584,2586,2588,2590,2593,2595,2598,2600,2603],{"class":1325,"line":1619},[1323,2579,2580],{"class":1818},"    return",[1323,2582,2583],{"class":1461}," chargeCard",[1323,2585,1481],{"class":1339},[1323,2587,1234],{"class":1442},[1323,2589,1458],{"class":1339},[1323,2591,2592],{"class":1442},"Card",[1323,2594,1446],{"class":1339},[1323,2596,2597],{"class":1442}," p",[1323,2599,1458],{"class":1339},[1323,2601,2602],{"class":1442},"Amount",[1323,2604,1496],{"class":1339},[1323,2606,2607],{"class":1325,"line":1624},[1323,2608,1404],{"class":1339},[1323,2610,2611],{"class":1325,"line":1630},[1323,2612,1502],{"emptyLinePlaceholder":1501},[1323,2614,2615],{"class":1325,"line":1779},[1323,2616,2617],{"class":1507},"// Boundaries handle transforms\n",[1323,2619,2620,2622,2624,2626,2628,2630,2632,2634,2636,2638,2640,2643,2645],{"class":1325,"line":1784},[1323,2621,1553],{"class":1442},[1323,2623,1446],{"class":1339},[1323,2625,1449],{"class":1442},[1323,2627,1452],{"class":1442},[1323,2629,1522],{"class":1442},[1323,2631,1458],{"class":1339},[1323,2633,775],{"class":1461},[1323,2635,1481],{"class":1339},[1323,2637,1531],{"class":1442},[1323,2639,1446],{"class":1339},[1323,2641,2642],{"class":1442}," payment",[1323,2644,1804],{"class":1339},[1323,2646,2647],{"class":1507},"    // Card encrypted\n",[1323,2649,2650,2652,2654,2656,2658,2660,2662,2664,2666,2668,2670,2672,2674],{"class":1325,"line":1789},[1323,2651,1633],{"class":1442},[1323,2653,1446],{"class":1339},[1323,2655,1449],{"class":1442},[1323,2657,1452],{"class":1442},[1323,2659,1522],{"class":1442},[1323,2661,1458],{"class":1339},[1323,2663,780],{"class":1461},[1323,2665,1481],{"class":1339},[1323,2667,1531],{"class":1442},[1323,2669,1446],{"class":1339},[1323,2671,2642],{"class":1442},[1323,2673,1804],{"class":1339},[1323,2675,2676],{"class":1507},"       // Card masked\n",[1234,2678,2679],{},"Security requirements change in one place. Every serialization path follows.",[1306,2681,2683],{"id":2682},"documentation","Documentation",[1406,2685,2686],{},[1409,2687,2688,2691],{},[1237,2689,6],{"href":2690},"docs/overview"," — Design philosophy",[2693,2694,1153],"h3",{"id":2695},"learn",[1406,2697,2698,2704,2710],{},[1409,2699,2700,2703],{},[1237,2701,1684],{"href":2702},"docs/learn/quickstart"," — Get started in minutes",[1409,2705,2706,2709],{},[1237,2707,106],{"href":2708},"docs/learn/concepts"," — Boundaries, processors, transforms",[1409,2711,2712,2715],{},[1237,2713,22],{"href":2714},"docs/learn/architecture"," — Internal design and components",[2693,2717,1165],{"id":2718},"guides",[1406,2720,2721,2726,2731],{},[1409,2722,2723,2725],{},[1237,2724,354],{"href":2364}," — AES, RSA, envelope encryption",[1409,2727,2728,2730],{},[1237,2729,433],{"href":2380}," — PII protection for API responses",[1409,2732,2733,2736],{},[1237,2734,492],{"href":2735},"docs/guides/providers"," — JSON, YAML, XML, MessagePack, BSON",[2693,2738,1176],{"id":2739},"cookbook",[1406,2741,2742,2749,2755],{},[1409,2743,2744,2748],{},[1237,2745,2747],{"href":2746},"docs/cookbook/escape-hatches","Escape Hatches"," — Custom transforms and overrides",[1409,2750,2751,2754],{},[1237,2752,609],{"href":2753},"docs/cookbook/key-rotation"," — Zero-downtime encryption key updates",[1409,2756,2757,2760],{},[1237,2758,653],{"href":2759},"docs/cookbook/code-generation"," — Generating processors from schemas",[2693,2762,1187],{"id":2763},"reference",[1406,2765,2766,2773,2779],{},[1409,2767,2768,2772],{},[1237,2769,2771],{"href":2770},"docs/reference/api","API"," — Complete function documentation",[1409,2774,2775,2778],{},[1237,2776,2777],{"href":2395},"Tags"," — All struct tag options",[1409,2780,2781,2784],{},[1237,2782,1059],{"href":2783},"docs/reference/errors"," — Error types and handling",[1306,2786,2788],{"id":2787},"contributing","Contributing",[1234,2790,2791,2792,2796],{},"See ",[1237,2793,2795],{"href":2794},"CONTRIBUTING","CONTRIBUTING.md"," for guidelines.",[1306,2798,1284],{"id":2799},"license",[1234,2801,2802,2803,2805],{},"MIT License — see ",[1237,2804,1281],{"href":1281}," for details.",[2807,2808,2809],"style",{},"html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}",{"title":34,"searchDepth":19,"depth":19,"links":2811},[2812,2813,2814,2815,2816,2817,2818,2824,2825],{"id":1308,"depth":19,"text":1309},{"id":1659,"depth":19,"text":1660},{"id":1683,"depth":19,"text":1684},{"id":2323,"depth":19,"text":27},{"id":2411,"depth":19,"text":2412},{"id":2466,"depth":19,"text":2467},{"id":2682,"depth":19,"text":2683,"children":2819},[2820,2821,2822,2823],{"id":2695,"depth":40,"text":1153},{"id":2718,"depth":40,"text":1165},{"id":2739,"depth":40,"text":1176},{"id":2763,"depth":40,"text":1187},{"id":2787,"depth":19,"text":2788},{"id":2799,"depth":19,"text":1284},"md","book-open",{},"/readme",{"title":1224,"description":34},"readme","-uGdNYR02JWz_bsKROWNKRuSMXDcO_3o9JzrMMql3sA",{"id":2834,"title":2835,"body":2836,"description":34,"extension":2826,"icon":2955,"meta":2956,"navigation":1501,"path":2957,"seo":2958,"stem":2959,"__hash__":2960},"resources/security.md","Security",{"type":1226,"value":2837,"toc":2949},[2838,2842,2846,2868,2872,2877,2884,2887,2907,2911,2931,2935,2946],[1229,2839,2841],{"id":2840},"security-policy","Security Policy",[1306,2843,2845],{"id":2844},"supported-versions","Supported Versions",[2325,2847,2848,2858],{},[2328,2849,2850],{},[2331,2851,2852,2855],{},[2334,2853,2854],{},"Version",[2334,2856,2857],{},"Supported",[2347,2859,2860],{},[2331,2861,2862,2865],{},[2352,2863,2864],{},"latest",[2352,2866,2867],{},"✅",[1306,2869,2871],{"id":2870},"reporting-a-vulnerability","Reporting a Vulnerability",[1234,2873,2874],{},[1412,2875,2876],{},"Please do not report security vulnerabilities through public GitHub issues.",[1234,2878,2879,2880],{},"Instead, please report them via email to: ",[1237,2881,2883],{"href":2882},"mailto:security@zoobzio.dev","security@zoobzio.dev",[1234,2885,2886],{},"Include the following information:",[1406,2888,2889,2892,2895,2898,2901,2904],{},[1409,2890,2891],{},"Type of vulnerability",[1409,2893,2894],{},"Full paths of affected source files",[1409,2896,2897],{},"Location of the affected code (tag/branch/commit or direct URL)",[1409,2899,2900],{},"Step-by-step instructions to reproduce",[1409,2902,2903],{},"Proof-of-concept or exploit code (if possible)",[1409,2905,2906],{},"Impact assessment",[1306,2908,2910],{"id":2909},"response-timeline","Response Timeline",[1406,2912,2913,2919,2925],{},[1409,2914,2915,2918],{},[1412,2916,2917],{},"Initial Response",": Within 48 hours",[1409,2920,2921,2924],{},[1412,2922,2923],{},"Status Update",": Within 7 days",[1409,2926,2927,2930],{},[1412,2928,2929],{},"Resolution Target",": Within 30 days for critical issues",[1306,2932,2934],{"id":2933},"disclosure-policy","Disclosure Policy",[1406,2936,2937,2940,2943],{},[1409,2938,2939],{},"We will acknowledge receipt of your report",[1409,2941,2942],{},"We will confirm the vulnerability and determine its impact",[1409,2944,2945],{},"We will release a fix and publicly disclose the issue",[1234,2947,2948],{},"We appreciate your help in keeping Cereal and its users safe.",{"title":34,"searchDepth":19,"depth":19,"links":2950},[2951,2952,2953,2954],{"id":2844,"depth":19,"text":2845},{"id":2870,"depth":19,"text":2871},{"id":2909,"depth":19,"text":2910},{"id":2933,"depth":19,"text":2934},"shield",{},"/security",{"title":2835,"description":34},"security","QdNZDhK-YMeJLgbrlhy8vyNOQYgROrr3dGtvcyrv2_M",{"id":2962,"title":2788,"body":2963,"description":2971,"extension":2826,"icon":1320,"meta":3247,"navigation":1501,"path":3248,"seo":3249,"stem":2787,"__hash__":3250},"resources/contributing.md",{"type":1226,"value":2964,"toc":3235},[2965,2969,2972,2976,3038,3042,3046,3053,3061,3065,3093,3097,3100,3138,3144,3148,3171,3174,3178,3195,3199,3225,3229,3232],[1229,2966,2968],{"id":2967},"contributing-to-cereal","Contributing to Cereal",[1234,2970,2971],{},"Thank you for considering contributing to Cereal.",[1306,2973,2975],{"id":2974},"development-setup","Development Setup",[1314,2977,2979],{"className":1663,"code":2978,"language":1665,"meta":34,"style":34},"# Clone the repository\ngit clone https://github.com/zoobzio/cereal.git\ncd cereal\n\n# Install development tools\nmake install-tools\n\n# Run tests to verify setup\nmake test\n",[1320,2980,2981,2986,2997,3005,3009,3014,3022,3026,3031],{"__ignoreMap":34},[1323,2982,2983],{"class":1325,"line":9},[1323,2984,2985],{"class":1507},"# Clone the repository\n",[1323,2987,2988,2991,2994],{"class":1325,"line":19},[1323,2989,2990],{"class":1461},"git",[1323,2992,2993],{"class":1352}," clone",[1323,2995,2996],{"class":1352}," https://github.com/zoobzio/cereal.git\n",[1323,2998,2999,3002],{"class":1325,"line":40},[1323,3000,3001],{"class":1461},"cd",[1323,3003,3004],{"class":1352}," cereal\n",[1323,3006,3007],{"class":1325,"line":762},[1323,3008,1502],{"emptyLinePlaceholder":1501},[1323,3010,3011],{"class":1325,"line":1378},[1323,3012,3013],{"class":1507},"# Install development tools\n",[1323,3015,3016,3019],{"class":1325,"line":1390},[1323,3017,3018],{"class":1461},"make",[1323,3020,3021],{"class":1352}," install-tools\n",[1323,3023,3024],{"class":1325,"line":1401},[1323,3025,1502],{"emptyLinePlaceholder":1501},[1323,3027,3028],{"class":1325,"line":1550},[1323,3029,3030],{"class":1507},"# Run tests to verify setup\n",[1323,3032,3033,3035],{"class":1325,"line":1579},[1323,3034,3018],{"class":1461},[1323,3036,3037],{"class":1352}," test\n",[1306,3039,3041],{"id":3040},"development-workflow","Development Workflow",[2693,3043,3045],{"id":3044},"available-commands","Available Commands",[1234,3047,3048,3049,3052],{},"Run ",[1320,3050,3051],{},"make help"," to see all available commands:",[1314,3054,3059],{"className":3055,"code":3057,"language":3058},[3056],"language-text","make test             Run all tests with race detector\nmake test-unit        Run unit tests only (with -short flag)\nmake test-integration Run integration tests\nmake test-bench       Run benchmarks\nmake lint             Run golangci-lint\nmake lint-fix         Run linter with auto-fix\nmake coverage         Generate HTML coverage report\nmake check            Quick validation (test + lint)\nmake ci               Full CI simulation\n","text",[1320,3060,3057],{"__ignoreMap":34},[2693,3062,3064],{"id":3063},"before-submitting","Before Submitting",[3066,3067,3068,3077,3085],"ol",{},[1409,3069,3070,3073,3074],{},[1412,3071,3072],{},"Run the full check",": ",[1320,3075,3076],{},"make check",[1409,3078,3079,3073,3082],{},[1412,3080,3081],{},"Ensure tests pass",[1320,3083,3084],{},"make test",[1409,3086,3087,3073,3090],{},[1412,3088,3089],{},"Check coverage",[1320,3091,3092],{},"make coverage",[2693,3094,3096],{"id":3095},"commit-messages","Commit Messages",[1234,3098,3099],{},"Use conventional commit format:",[1406,3101,3102,3108,3114,3120,3126,3132],{},[1409,3103,3104,3107],{},[1320,3105,3106],{},"feat:"," New features",[1409,3109,3110,3113],{},[1320,3111,3112],{},"fix:"," Bug fixes",[1409,3115,3116,3119],{},[1320,3117,3118],{},"docs:"," Documentation changes",[1409,3121,3122,3125],{},[1320,3123,3124],{},"test:"," Test additions or changes",[1409,3127,3128,3131],{},[1320,3129,3130],{},"refactor:"," Code refactoring",[1409,3133,3134,3137],{},[1320,3135,3136],{},"chore:"," Maintenance tasks",[1234,3139,3140,3141],{},"Example: ",[1320,3142,3143],{},"feat: add TOML codec provider",[1306,3145,3147],{"id":3146},"pull-requests","Pull Requests",[3066,3149,3150,3153,3159,3162,3168],{},[1409,3151,3152],{},"Fork the repository",[1409,3154,3155,3156],{},"Create a feature branch from ",[1320,3157,3158],{},"main",[1409,3160,3161],{},"Make your changes",[1409,3163,3048,3164,3167],{},[1320,3165,3166],{},"make ci"," to simulate CI checks",[1409,3169,3170],{},"Submit a pull request",[1234,3172,3173],{},"Please open an issue to discuss significant changes before starting work.",[1306,3175,3177],{"id":3176},"code-style","Code Style",[1406,3179,3180,3183,3189,3192],{},[1409,3181,3182],{},"Follow standard Go conventions",[1409,3184,3048,3185,3188],{},[1320,3186,3187],{},"make lint"," before committing",[1409,3190,3191],{},"Add tests for new functionality",[1409,3193,3194],{},"Update documentation as needed",[1306,3196,3198],{"id":3197},"testing","Testing",[1406,3200,3201,3207,3213,3219],{},[1409,3202,3203,3204,1804],{},"Unit tests live alongside source files (",[1320,3205,3206],{},"*_test.go",[1409,3208,3209,3210],{},"Integration tests go in ",[1320,3211,3212],{},"testing/integration/",[1409,3214,3215,3216],{},"Benchmarks go in ",[1320,3217,3218],{},"testing/benchmarks/",[1409,3220,3221,3222],{},"Test helpers go in ",[1320,3223,3224],{},"testing/helpers.go",[1306,3226,3228],{"id":3227},"questions","Questions?",[1234,3230,3231],{},"Open an issue for questions or discussion.",[2807,3233,3234],{},"html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":34,"searchDepth":19,"depth":19,"links":3236},[3237,3238,3243,3244,3245,3246],{"id":2974,"depth":19,"text":2975},{"id":3040,"depth":19,"text":3041,"children":3239},[3240,3241,3242],{"id":3044,"depth":40,"text":3045},{"id":3063,"depth":40,"text":3064},{"id":3095,"depth":40,"text":3096},{"id":3146,"depth":19,"text":3147},{"id":3176,"depth":19,"text":3177},{"id":3197,"depth":19,"text":3198},{"id":3227,"depth":19,"text":3228},{},"/contributing",{"title":2788,"description":2971},"14rcVqNmc4ZXAXH_X-6-z3Tp9pSZPBSpp0mYoEiUpQ0",{"id":3252,"title":1059,"author":3253,"body":3254,"description":1061,"extension":2826,"meta":5875,"navigation":1501,"path":1058,"published":5876,"readtime":5877,"seo":5878,"stem":1196,"tags":5879,"updated":5876,"__hash__":5881},"cereal/v0.1.2/5.reference/3.errors.md","zoobzio",{"type":1226,"value":3255,"toc":5855},[3256,3259,3261,3264,3267,3272,3323,3412,3415,3419,3471,3535,3538,3541,3552,3624,3731,3734,3737,3789,3882,3885,3888,3920,3923,3926,3933,4034,4132,4135,4145,4194,4205,4293,4296,4299,4302,4493,4496,4499,4669,4672,4675,5051,5054,5057,5295,5298,5301,5417,5420,5423,5426,5503,5510,5616,5619,5622,5625,5849,5852],[1229,3257,1059],{"id":3258},"errors",[1234,3260,1065],{},[1306,3262,1068],{"id":3263},"error-categories",[2693,3265,1072],{"id":3266},"construction-errors",[1234,3268,3269,3270,1990],{},"Returned by ",[1320,3271,750],{},[2325,3273,3274,3284],{},[2328,3275,3276],{},[2331,3277,3278,3281],{},[2334,3279,3280],{},"Error",[2334,3282,3283],{},"Cause",[2347,3285,3286,3300,3310],{},[2331,3287,3288,3293],{},[2352,3289,3290],{},[1320,3291,3292],{},"invalid tag format",[2352,3294,3295,3296,3299],{},"Malformed struct tag (e.g., ",[1320,3297,3298],{},"store.encrypt:"," without value)",[2331,3301,3302,3307],{},[2352,3303,3304],{},[1320,3305,3306],{},"unknown boundary",[2352,3308,3309],{},"Unrecognized boundary prefix (not receive/load/store/send)",[2331,3311,3312,3317],{},[2352,3313,3314],{},[1320,3315,3316],{},"unknown operation",[2352,3318,3319,3320,1804],{},"Invalid operation for boundary (e.g., ",[1320,3321,3322],{},"receive.encrypt",[1314,3324,3326],{"className":1316,"code":3325,"language":1318,"meta":34,"style":34},"proc, err := cereal.NewProcessor[User](json.New())\nif err != nil {\n    // Tag parsing failed - fix struct tags\n    log.Fatalf(\"processor creation failed: %v\", err)\n}\n",[1320,3327,3328,3360,3375,3380,3408],{"__ignoreMap":34},[1323,3329,3330,3332,3334,3337,3339,3341,3343,3345,3347,3349,3352,3354,3356,3358],{"class":1325,"line":9},[1323,3331,1443],{"class":1442},[1323,3333,1446],{"class":1339},[1323,3335,3336],{"class":1442}," err",[1323,3338,1452],{"class":1442},[1323,3340,1455],{"class":1442},[1323,3342,1458],{"class":1339},[1323,3344,750],{"class":1461},[1323,3346,1464],{"class":1339},[1323,3348,1467],{"class":1332},[1323,3350,3351],{"class":1339},"](",[1323,3353,2248],{"class":1442},[1323,3355,1458],{"class":1339},[1323,3357,2253],{"class":1461},[1323,3359,2256],{"class":1339},[1323,3361,3362,3365,3367,3370,3373],{"class":1325,"line":19},[1323,3363,3364],{"class":1818},"if",[1323,3366,3336],{"class":1442},[1323,3368,3369],{"class":1818}," !=",[1323,3371,3372],{"class":1328}," nil",[1323,3374,1340],{"class":1339},[1323,3376,3377],{"class":1325,"line":40},[1323,3378,3379],{"class":1507},"    // Tag parsing failed - fix struct tags\n",[1323,3381,3382,3385,3387,3390,3392,3395,3399,3402,3404,3406],{"class":1325,"line":762},[1323,3383,3384],{"class":1442},"    log",[1323,3386,1458],{"class":1339},[1323,3388,3389],{"class":1461},"Fatalf",[1323,3391,1481],{"class":1339},[1323,3393,3394],{"class":1352},"\"processor creation failed: ",[1323,3396,3398],{"class":3397},"scyPU","%v",[1323,3400,3401],{"class":1352},"\"",[1323,3403,1446],{"class":1339},[1323,3405,3336],{"class":1442},[1323,3407,1496],{"class":1339},[1323,3409,3410],{"class":1325,"line":1378},[1323,3411,1404],{"class":1339},[2693,3413,1077],{"id":3414},"validation-errors",[1234,3416,3269,3417,1990],{},[1320,3418,834],{},[2325,3420,3421,3429],{},[2328,3422,3423],{},[2331,3424,3425,3427],{},[2334,3426,3280],{},[2334,3428,3283],{},[2347,3430,3431,3445,3458],{},[2331,3432,3433,3438],{},[2352,3434,3435],{},[1320,3436,3437],{},"missing encryptor for algorithm \"X\"",[2352,3439,3440,3441,3444],{},"Field uses ",[1320,3442,3443],{},"store.encrypt:\"X\""," but no encryptor registered",[2331,3446,3447,3452],{},[2352,3448,3449],{},[1320,3450,3451],{},"missing hasher for algorithm \"X\"",[2352,3453,3440,3454,3457],{},[1320,3455,3456],{},"receive.hash:\"X\""," but no hasher registered",[2331,3459,3460,3465],{},[2352,3461,3462],{},[1320,3463,3464],{},"missing masker for type \"X\"",[2352,3466,3440,3467,3470],{},[1320,3468,3469],{},"send.mask:\"X\""," but no masker registered",[1314,3472,3474],{"className":1316,"code":3473,"language":1318,"meta":34,"style":34},"err := proc.Validate()\nif err != nil {\n    // Configuration incomplete - register missing handlers\n    log.Fatalf(\"validation failed: %v\", err)\n}\n",[1320,3475,3476,3491,3503,3508,3531],{"__ignoreMap":34},[1323,3477,3478,3481,3483,3485,3487,3489],{"class":1325,"line":9},[1323,3479,3480],{"class":1442},"err",[1323,3482,1452],{"class":1442},[1323,3484,1522],{"class":1442},[1323,3486,1458],{"class":1339},[1323,3488,834],{"class":1461},[1323,3490,1861],{"class":1339},[1323,3492,3493,3495,3497,3499,3501],{"class":1325,"line":19},[1323,3494,3364],{"class":1818},[1323,3496,3336],{"class":1442},[1323,3498,3369],{"class":1818},[1323,3500,3372],{"class":1328},[1323,3502,1340],{"class":1339},[1323,3504,3505],{"class":1325,"line":40},[1323,3506,3507],{"class":1507},"    // Configuration incomplete - register missing handlers\n",[1323,3509,3510,3512,3514,3516,3518,3521,3523,3525,3527,3529],{"class":1325,"line":762},[1323,3511,3384],{"class":1442},[1323,3513,1458],{"class":1339},[1323,3515,3389],{"class":1461},[1323,3517,1481],{"class":1339},[1323,3519,3520],{"class":1352},"\"validation failed: ",[1323,3522,3398],{"class":3397},[1323,3524,3401],{"class":1352},[1323,3526,1446],{"class":1339},[1323,3528,3336],{"class":1442},[1323,3530,1496],{"class":1339},[1323,3532,3533],{"class":1325,"line":1378},[1323,3534,1404],{"class":1339},[1234,3536,3537],{},"Note: SHA-256, SHA-512, and all mask types are registered by default. Only encryption and Argon2/bcrypt require explicit registration.",[2693,3539,1082],{"id":3540},"operation-errors",[1234,3542,3269,3543,3545,3546,3545,3548,3545,3550,1990],{},[1320,3544,765],{},", ",[1320,3547,770],{},[1320,3549,775],{},[1320,3551,780],{},[2325,3553,3554,3562],{},[2328,3555,3556],{},[2331,3557,3558,3560],{},[2334,3559,3280],{},[2334,3561,3283],{},[2347,3563,3564,3574,3584,3594,3604,3614],{},[2331,3565,3566,3571],{},[2352,3567,3568],{},[1320,3569,3570],{},"unmarshal: ...",[2352,3572,3573],{},"Codec failed to parse input bytes",[2331,3575,3576,3581],{},[2352,3577,3578],{},[1320,3579,3580],{},"marshal: ...",[2352,3582,3583],{},"Codec failed to serialize output",[2331,3585,3586,3591],{},[2352,3587,3588],{},[1320,3589,3590],{},"encrypt field X: ...",[2352,3592,3593],{},"Encryption failed for field",[2331,3595,3596,3601],{},[2352,3597,3598],{},[1320,3599,3600],{},"decrypt field X: ...",[2352,3602,3603],{},"Decryption failed for field",[2331,3605,3606,3611],{},[2352,3607,3608],{},[1320,3609,3610],{},"hash field X: ...",[2352,3612,3613],{},"Hashing failed for field",[2331,3615,3616,3621],{},[2352,3617,3618],{},[1320,3619,3620],{},"mask field X: ...",[2352,3622,3623],{},"Masking failed for field (invalid format)",[1314,3625,3627],{"className":1316,"code":3626,"language":1318,"meta":34,"style":34},"user, err := proc.Receive(ctx, data)\nif err != nil {\n    // Check error type for specific handling\n    var unmarshalErr *json.SyntaxError\n    if errors.As(err, &unmarshalErr) {\n        // Invalid JSON input\n    }\n}\n",[1320,3628,3629,3656,3668,3673,3690,3718,3723,3727],{"__ignoreMap":34},[1323,3630,3631,3633,3635,3637,3639,3641,3643,3645,3647,3649,3651,3654],{"class":1325,"line":9},[1323,3632,2286],{"class":1442},[1323,3634,1446],{"class":1339},[1323,3636,3336],{"class":1442},[1323,3638,1452],{"class":1442},[1323,3640,1522],{"class":1442},[1323,3642,1458],{"class":1339},[1323,3644,765],{"class":1461},[1323,3646,1481],{"class":1339},[1323,3648,1531],{"class":1442},[1323,3650,1446],{"class":1339},[1323,3652,3653],{"class":1442}," data",[1323,3655,1496],{"class":1339},[1323,3657,3658,3660,3662,3664,3666],{"class":1325,"line":19},[1323,3659,3364],{"class":1818},[1323,3661,3336],{"class":1442},[1323,3663,3369],{"class":1818},[1323,3665,3372],{"class":1328},[1323,3667,1340],{"class":1339},[1323,3669,3670],{"class":1325,"line":40},[1323,3671,3672],{"class":1507},"    // Check error type for specific handling\n",[1323,3674,3675,3678,3681,3683,3685,3687],{"class":1325,"line":762},[1323,3676,3677],{"class":1328},"    var",[1323,3679,3680],{"class":1442}," unmarshalErr",[1323,3682,2555],{"class":1818},[1323,3684,2248],{"class":1332},[1323,3686,1458],{"class":1339},[1323,3688,3689],{"class":1332},"SyntaxError\n",[1323,3691,3692,3695,3698,3700,3703,3705,3707,3709,3711,3714,3716],{"class":1325,"line":1378},[1323,3693,3694],{"class":1818},"    if",[1323,3696,3697],{"class":1442}," errors",[1323,3699,1458],{"class":1339},[1323,3701,3702],{"class":1461},"As",[1323,3704,1481],{"class":1339},[1323,3706,3480],{"class":1442},[1323,3708,1446],{"class":1339},[1323,3710,2283],{"class":1818},[1323,3712,3713],{"class":1442},"unmarshalErr",[1323,3715,1804],{"class":1339},[1323,3717,1340],{"class":1339},[1323,3719,3720],{"class":1325,"line":1390},[1323,3721,3722],{"class":1507},"        // Invalid JSON input\n",[1323,3724,3725],{"class":1325,"line":1401},[1323,3726,2028],{"class":1339},[1323,3728,3729],{"class":1325,"line":1550},[1323,3730,1404],{"class":1339},[2693,3732,1087],{"id":3733},"encryptor-errors",[1234,3735,3736],{},"From built-in encryptors:",[2325,3738,3739,3747],{},[2328,3740,3741],{},[2331,3742,3743,3745],{},[2334,3744,3280],{},[2334,3746,3283],{},[2347,3748,3749,3759,3769,3779],{},[2331,3750,3751,3756],{},[2352,3752,3753],{},[1320,3754,3755],{},"invalid key size",[2352,3757,3758],{},"AES key not 16, 24, or 32 bytes",[2331,3760,3761,3766],{},[2352,3762,3763],{},[1320,3764,3765],{},"ciphertext too short",[2352,3767,3768],{},"Decryption input shorter than nonce",[2331,3770,3771,3776],{},[2352,3772,3773],{},[1320,3774,3775],{},"authentication failed",[2352,3777,3778],{},"GCM tag verification failed (wrong key or corrupted data)",[2331,3780,3781,3786],{},[2352,3782,3783],{},[1320,3784,3785],{},"message too long",[2352,3787,3788],{},"RSA plaintext exceeds key size limit",[1314,3790,3792],{"className":1316,"code":3791,"language":1318,"meta":34,"style":34},"enc, err := cereal.AES(key)\nif err != nil {\n    // Key size invalid\n}\n\n// During operation\n_, err = proc.Load(ctx, corruptedData)\n// err: \"decrypt field Email: authentication failed\"\n",[1320,3793,3794,3818,3830,3835,3839,3843,3848,3877],{"__ignoreMap":34},[1323,3795,3796,3799,3801,3803,3805,3807,3809,3811,3813,3816],{"class":1325,"line":9},[1323,3797,3798],{"class":1442},"enc",[1323,3800,1446],{"class":1339},[1323,3802,3336],{"class":1442},[1323,3804,1452],{"class":1442},[1323,3806,1455],{"class":1442},[1323,3808,1458],{"class":1339},[1323,3810,867],{"class":1461},[1323,3812,1481],{"class":1339},[1323,3814,3815],{"class":1442},"key",[1323,3817,1496],{"class":1339},[1323,3819,3820,3822,3824,3826,3828],{"class":1325,"line":19},[1323,3821,3364],{"class":1818},[1323,3823,3336],{"class":1442},[1323,3825,3369],{"class":1818},[1323,3827,3372],{"class":1328},[1323,3829,1340],{"class":1339},[1323,3831,3832],{"class":1325,"line":40},[1323,3833,3834],{"class":1507},"    // Key size invalid\n",[1323,3836,3837],{"class":1325,"line":762},[1323,3838,1404],{"class":1339},[1323,3840,3841],{"class":1325,"line":1378},[1323,3842,1502],{"emptyLinePlaceholder":1501},[1323,3844,3845],{"class":1325,"line":1390},[1323,3846,3847],{"class":1507},"// During operation\n",[1323,3849,3850,3853,3855,3857,3860,3862,3864,3866,3868,3870,3872,3875],{"class":1325,"line":1401},[1323,3851,3852],{"class":1442},"_",[1323,3854,1446],{"class":1339},[1323,3856,3336],{"class":1442},[1323,3858,3859],{"class":1442}," =",[1323,3861,1522],{"class":1442},[1323,3863,1458],{"class":1339},[1323,3865,770],{"class":1461},[1323,3867,1481],{"class":1339},[1323,3869,1531],{"class":1442},[1323,3871,1446],{"class":1339},[1323,3873,3874],{"class":1442}," corruptedData",[1323,3876,1496],{"class":1339},[1323,3878,3879],{"class":1325,"line":1550},[1323,3880,3881],{"class":1507},"// err: \"decrypt field Email: authentication failed\"\n",[2693,3883,1092],{"id":3884},"hasher-errors",[1234,3886,3887],{},"From built-in hashers:",[2325,3889,3890,3898],{},[2328,3891,3892],{},[2331,3893,3894,3896],{},[2334,3895,3280],{},[2334,3897,3283],{},[2347,3899,3900,3910],{},[2331,3901,3902,3907],{},[2352,3903,3904],{},[1320,3905,3906],{},"bcrypt: cost out of range",[2352,3908,3909],{},"Cost \u003C 4 or > 31",[2331,3911,3912,3917],{},[2352,3913,3914],{},[1320,3915,3916],{},"argon2: invalid parameters",[2352,3918,3919],{},"Zero values for required params",[1234,3921,3922],{},"Hashers rarely fail during operation. SHA hashers never return errors.",[2693,3924,1097],{"id":3925},"masker-errors",[1234,3927,3928,3929,3932],{},"From built-in maskers (",[1320,3930,3931],{},"ErrMask","):",[2325,3934,3935,3945],{},[2328,3936,3937],{},[2331,3938,3939,3942],{},[2334,3940,3941],{},"Type",[2334,3943,3944],{},"Validation Failure",[2347,3946,3947,3957,3974,3984,3994,4004,4014,4024],{},[2331,3948,3949,3954],{},[2352,3950,3951],{},[1320,3952,3953],{},"ssn",[2352,3955,3956],{},"Not exactly 9 digits",[2331,3958,3959,3964],{},[2352,3960,3961],{},[1320,3962,3963],{},"email",[2352,3965,3966,3967,3970,3971,3973],{},"Missing ",[1320,3968,3969],{},"@"," or ",[1320,3972,3969],{}," at position 0",[2331,3975,3976,3981],{},[2352,3977,3978],{},[1320,3979,3980],{},"phone",[2352,3982,3983],{},"Fewer than 7 digits",[2331,3985,3986,3991],{},[2352,3987,3988],{},[1320,3989,3990],{},"card",[2352,3992,3993],{},"Outside 13-19 digit range",[2331,3995,3996,4001],{},[2352,3997,3998],{},[1320,3999,4000],{},"ip",[2352,4002,4003],{},"Invalid IPv4 or IPv6 format",[2331,4005,4006,4011],{},[2352,4007,4008],{},[1320,4009,4010],{},"uuid",[2352,4012,4013],{},"Wrong segment count or lengths (must be 8-4-4-4-12)",[2331,4015,4016,4021],{},[2352,4017,4018],{},[1320,4019,4020],{},"iban",[2352,4022,4023],{},"Outside 15-34 chars or doesn't start with 2 letters",[2331,4025,4026,4031],{},[2352,4027,4028],{},[1320,4029,4030],{},"name",[2352,4032,4033],{},"Empty or whitespace only",[1314,4035,4037],{"className":1316,"code":4036,"language":1318,"meta":34,"style":34},"user := &User{Email: \"invalid\"}\n_, err := proc.Send(ctx, user)\n// err: \"mask field Email: mask failed: invalid email format, missing or misplaced @\"\n\nif errors.Is(err, cereal.ErrMask) {\n    // Input data doesn't match expected format\n}\n",[1320,4038,4039,4061,4087,4092,4096,4123,4128],{"__ignoreMap":34},[1323,4040,4041,4043,4045,4047,4049,4052,4054,4056,4059],{"class":1325,"line":9},[1323,4042,2286],{"class":1442},[1323,4044,1452],{"class":1442},[1323,4046,2283],{"class":1818},[1323,4048,1467],{"class":1332},[1323,4050,4051],{"class":1339},"{",[1323,4053,2087],{"class":1345},[1323,4055,1990],{"class":1339},[1323,4057,4058],{"class":1352}," \"invalid\"",[1323,4060,1404],{"class":1339},[1323,4062,4063,4065,4067,4069,4071,4073,4075,4077,4079,4081,4083,4085],{"class":1325,"line":19},[1323,4064,3852],{"class":1442},[1323,4066,1446],{"class":1339},[1323,4068,3336],{"class":1442},[1323,4070,1452],{"class":1442},[1323,4072,1522],{"class":1442},[1323,4074,1458],{"class":1339},[1323,4076,780],{"class":1461},[1323,4078,1481],{"class":1339},[1323,4080,1531],{"class":1442},[1323,4082,1446],{"class":1339},[1323,4084,1536],{"class":1442},[1323,4086,1496],{"class":1339},[1323,4088,4089],{"class":1325,"line":40},[1323,4090,4091],{"class":1507},"// err: \"mask field Email: mask failed: invalid email format, missing or misplaced @\"\n",[1323,4093,4094],{"class":1325,"line":762},[1323,4095,1502],{"emptyLinePlaceholder":1501},[1323,4097,4098,4100,4102,4104,4107,4109,4111,4113,4115,4117,4119,4121],{"class":1325,"line":1378},[1323,4099,3364],{"class":1818},[1323,4101,3697],{"class":1442},[1323,4103,1458],{"class":1339},[1323,4105,4106],{"class":1461},"Is",[1323,4108,1481],{"class":1339},[1323,4110,3480],{"class":1442},[1323,4112,1446],{"class":1339},[1323,4114,1455],{"class":1442},[1323,4116,1458],{"class":1339},[1323,4118,3931],{"class":1442},[1323,4120,1804],{"class":1339},[1323,4122,1340],{"class":1339},[1323,4124,4125],{"class":1325,"line":1390},[1323,4126,4127],{"class":1507},"    // Input data doesn't match expected format\n",[1323,4129,4130],{"class":1325,"line":1401},[1323,4131,1404],{"class":1339},[1306,4133,1102],{"id":4134},"error-wrapping",[1234,4136,4137,4138,4141,4142,1990],{},"Errors include context via ",[1320,4139,4140],{},"fmt.Errorf"," with ",[1320,4143,4144],{},"%w",[1314,4146,4148],{"className":1316,"code":4147,"language":1318,"meta":34,"style":34},"// Original error\nerr := enc.Decrypt(ciphertext)\n// \"authentication failed\"\n\n// Wrapped by processor\n// \"decrypt field User.Email: authentication failed\"\n",[1320,4149,4150,4155,4175,4180,4184,4189],{"__ignoreMap":34},[1323,4151,4152],{"class":1325,"line":9},[1323,4153,4154],{"class":1507},"// Original error\n",[1323,4156,4157,4159,4161,4163,4165,4168,4170,4173],{"class":1325,"line":19},[1323,4158,3480],{"class":1442},[1323,4160,1452],{"class":1442},[1323,4162,1961],{"class":1442},[1323,4164,1458],{"class":1339},[1323,4166,4167],{"class":1461},"Decrypt",[1323,4169,1481],{"class":1339},[1323,4171,4172],{"class":1442},"ciphertext",[1323,4174,1496],{"class":1339},[1323,4176,4177],{"class":1325,"line":40},[1323,4178,4179],{"class":1507},"// \"authentication failed\"\n",[1323,4181,4182],{"class":1325,"line":762},[1323,4183,1502],{"emptyLinePlaceholder":1501},[1323,4185,4186],{"class":1325,"line":1378},[1323,4187,4188],{"class":1507},"// Wrapped by processor\n",[1323,4190,4191],{"class":1325,"line":1390},[1323,4192,4193],{"class":1507},"// \"decrypt field User.Email: authentication failed\"\n",[1234,4195,4196,4197,4200,4201,4204],{},"Use ",[1320,4198,4199],{},"errors.Is"," and ",[1320,4202,4203],{},"errors.As"," to inspect:",[1314,4206,4208],{"className":1316,"code":4207,"language":1318,"meta":34,"style":34},"if errors.Is(err, someSpecificError) {\n    // Handle specific case\n}\n\nvar targetErr *SomeErrorType\nif errors.As(err, &targetErr) {\n    // Access error details\n}\n",[1320,4209,4210,4233,4238,4242,4246,4259,4284,4289],{"__ignoreMap":34},[1323,4211,4212,4214,4216,4218,4220,4222,4224,4226,4229,4231],{"class":1325,"line":9},[1323,4213,3364],{"class":1818},[1323,4215,3697],{"class":1442},[1323,4217,1458],{"class":1339},[1323,4219,4106],{"class":1461},[1323,4221,1481],{"class":1339},[1323,4223,3480],{"class":1442},[1323,4225,1446],{"class":1339},[1323,4227,4228],{"class":1442}," someSpecificError",[1323,4230,1804],{"class":1339},[1323,4232,1340],{"class":1339},[1323,4234,4235],{"class":1325,"line":19},[1323,4236,4237],{"class":1507},"    // Handle specific case\n",[1323,4239,4240],{"class":1325,"line":40},[1323,4241,1404],{"class":1339},[1323,4243,4244],{"class":1325,"line":762},[1323,4245,1502],{"emptyLinePlaceholder":1501},[1323,4247,4248,4251,4254,4256],{"class":1325,"line":1378},[1323,4249,4250],{"class":1328},"var",[1323,4252,4253],{"class":1442}," targetErr",[1323,4255,2555],{"class":1818},[1323,4257,4258],{"class":1332},"SomeErrorType\n",[1323,4260,4261,4263,4265,4267,4269,4271,4273,4275,4277,4280,4282],{"class":1325,"line":1390},[1323,4262,3364],{"class":1818},[1323,4264,3697],{"class":1442},[1323,4266,1458],{"class":1339},[1323,4268,3702],{"class":1461},[1323,4270,1481],{"class":1339},[1323,4272,3480],{"class":1442},[1323,4274,1446],{"class":1339},[1323,4276,2283],{"class":1818},[1323,4278,4279],{"class":1442},"targetErr",[1323,4281,1804],{"class":1339},[1323,4283,1340],{"class":1339},[1323,4285,4286],{"class":1325,"line":1401},[1323,4287,4288],{"class":1507},"    // Access error details\n",[1323,4290,4291],{"class":1325,"line":1550},[1323,4292,1404],{"class":1339},[1306,4294,1107],{"id":4295},"handling-patterns",[2693,4297,1111],{"id":4298},"fail-fast",[1234,4300,4301],{},"For startup configuration:",[1314,4303,4305],{"className":1316,"code":4304,"language":1318,"meta":34,"style":34},"proc, err := cereal.NewProcessor[User](json.New())\nif err != nil {\n    log.Fatal(err)\n}\n\nenc, err := cereal.AES(key)\nif err != nil {\n    log.Fatal(err)\n}\nproc.SetEncryptor(cereal.EncryptAES, enc)\n\nif err := proc.Validate(); err != nil {\n    log.Fatal(err)\n}\n",[1320,4306,4307,4337,4349,4364,4368,4372,4394,4406,4420,4424,4446,4450,4475,4489],{"__ignoreMap":34},[1323,4308,4309,4311,4313,4315,4317,4319,4321,4323,4325,4327,4329,4331,4333,4335],{"class":1325,"line":9},[1323,4310,1443],{"class":1442},[1323,4312,1446],{"class":1339},[1323,4314,3336],{"class":1442},[1323,4316,1452],{"class":1442},[1323,4318,1455],{"class":1442},[1323,4320,1458],{"class":1339},[1323,4322,750],{"class":1461},[1323,4324,1464],{"class":1339},[1323,4326,1467],{"class":1332},[1323,4328,3351],{"class":1339},[1323,4330,2248],{"class":1442},[1323,4332,1458],{"class":1339},[1323,4334,2253],{"class":1461},[1323,4336,2256],{"class":1339},[1323,4338,4339,4341,4343,4345,4347],{"class":1325,"line":19},[1323,4340,3364],{"class":1818},[1323,4342,3336],{"class":1442},[1323,4344,3369],{"class":1818},[1323,4346,3372],{"class":1328},[1323,4348,1340],{"class":1339},[1323,4350,4351,4353,4355,4358,4360,4362],{"class":1325,"line":40},[1323,4352,3384],{"class":1442},[1323,4354,1458],{"class":1339},[1323,4356,4357],{"class":1461},"Fatal",[1323,4359,1481],{"class":1339},[1323,4361,3480],{"class":1442},[1323,4363,1496],{"class":1339},[1323,4365,4366],{"class":1325,"line":762},[1323,4367,1404],{"class":1339},[1323,4369,4370],{"class":1325,"line":1378},[1323,4371,1502],{"emptyLinePlaceholder":1501},[1323,4373,4374,4376,4378,4380,4382,4384,4386,4388,4390,4392],{"class":1325,"line":1390},[1323,4375,3798],{"class":1442},[1323,4377,1446],{"class":1339},[1323,4379,3336],{"class":1442},[1323,4381,1452],{"class":1442},[1323,4383,1455],{"class":1442},[1323,4385,1458],{"class":1339},[1323,4387,867],{"class":1461},[1323,4389,1481],{"class":1339},[1323,4391,3815],{"class":1442},[1323,4393,1496],{"class":1339},[1323,4395,4396,4398,4400,4402,4404],{"class":1325,"line":1401},[1323,4397,3364],{"class":1818},[1323,4399,3336],{"class":1442},[1323,4401,3369],{"class":1818},[1323,4403,3372],{"class":1328},[1323,4405,1340],{"class":1339},[1323,4407,4408,4410,4412,4414,4416,4418],{"class":1325,"line":1550},[1323,4409,3384],{"class":1442},[1323,4411,1458],{"class":1339},[1323,4413,4357],{"class":1461},[1323,4415,1481],{"class":1339},[1323,4417,3480],{"class":1442},[1323,4419,1496],{"class":1339},[1323,4421,4422],{"class":1325,"line":1579},[1323,4423,1404],{"class":1339},[1323,4425,4426,4428,4430,4432,4434,4436,4438,4440,4442,4444],{"class":1325,"line":1584},[1323,4427,1443],{"class":1442},[1323,4429,1458],{"class":1339},[1323,4431,819],{"class":1461},[1323,4433,1481],{"class":1339},[1323,4435,1231],{"class":1442},[1323,4437,1458],{"class":1339},[1323,4439,1488],{"class":1442},[1323,4441,1446],{"class":1339},[1323,4443,1961],{"class":1442},[1323,4445,1496],{"class":1339},[1323,4447,4448],{"class":1325,"line":1590},[1323,4449,1502],{"emptyLinePlaceholder":1501},[1323,4451,4452,4454,4456,4458,4460,4462,4464,4467,4469,4471,4473],{"class":1325,"line":1619},[1323,4453,3364],{"class":1818},[1323,4455,3336],{"class":1442},[1323,4457,1452],{"class":1442},[1323,4459,1522],{"class":1442},[1323,4461,1458],{"class":1339},[1323,4463,834],{"class":1461},[1323,4465,4466],{"class":1339},"();",[1323,4468,3336],{"class":1442},[1323,4470,3369],{"class":1818},[1323,4472,3372],{"class":1328},[1323,4474,1340],{"class":1339},[1323,4476,4477,4479,4481,4483,4485,4487],{"class":1325,"line":1624},[1323,4478,3384],{"class":1442},[1323,4480,1458],{"class":1339},[1323,4482,4357],{"class":1461},[1323,4484,1481],{"class":1339},[1323,4486,3480],{"class":1442},[1323,4488,1496],{"class":1339},[1323,4490,4491],{"class":1325,"line":1630},[1323,4492,1404],{"class":1339},[2693,4494,1116],{"id":4495},"graceful-degradation",[1234,4497,4498],{},"For runtime operations:",[1314,4500,4502],{"className":1316,"code":4501,"language":1318,"meta":34,"style":34},"func handleRequest(ctx context.Context, body []byte) (*User, error) {\n    user, err := proc.Receive(ctx, body)\n    if err != nil {\n        // Log full error for debugging\n        log.Printf(\"receive failed: %v\", err)\n\n        // Return safe error to caller\n        return nil, fmt.Errorf(\"invalid request format\")\n    }\n    return user, nil\n}\n",[1320,4503,4504,4549,4575,4587,4592,4617,4621,4626,4650,4654,4665],{"__ignoreMap":34},[1323,4505,4506,4508,4511,4513,4515,4517,4519,4522,4524,4527,4530,4532,4534,4536,4539,4541,4543,4545,4547],{"class":1325,"line":9},[1323,4507,1792],{"class":1328},[1323,4509,4510],{"class":1461}," handleRequest",[1323,4512,1481],{"class":1339},[1323,4514,1531],{"class":1798},[1323,4516,1853],{"class":1332},[1323,4518,1458],{"class":1339},[1323,4520,4521],{"class":1332},"Context",[1323,4523,1446],{"class":1339},[1323,4525,4526],{"class":1798}," body",[1323,4528,4529],{"class":1339}," []",[1323,4531,1931],{"class":1332},[1323,4533,1804],{"class":1339},[1323,4535,1795],{"class":1339},[1323,4537,4538],{"class":1818},"*",[1323,4540,1467],{"class":1332},[1323,4542,1446],{"class":1339},[1323,4544,2563],{"class":1332},[1323,4546,1804],{"class":1339},[1323,4548,1340],{"class":1339},[1323,4550,4551,4553,4555,4557,4559,4561,4563,4565,4567,4569,4571,4573],{"class":1325,"line":19},[1323,4552,1974],{"class":1442},[1323,4554,1446],{"class":1339},[1323,4556,3336],{"class":1442},[1323,4558,1452],{"class":1442},[1323,4560,1522],{"class":1442},[1323,4562,1458],{"class":1339},[1323,4564,765],{"class":1461},[1323,4566,1481],{"class":1339},[1323,4568,1531],{"class":1442},[1323,4570,1446],{"class":1339},[1323,4572,4526],{"class":1442},[1323,4574,1496],{"class":1339},[1323,4576,4577,4579,4581,4583,4585],{"class":1325,"line":40},[1323,4578,3694],{"class":1818},[1323,4580,3336],{"class":1442},[1323,4582,3369],{"class":1818},[1323,4584,3372],{"class":1328},[1323,4586,1340],{"class":1339},[1323,4588,4589],{"class":1325,"line":762},[1323,4590,4591],{"class":1507},"        // Log full error for debugging\n",[1323,4593,4594,4597,4599,4602,4604,4607,4609,4611,4613,4615],{"class":1325,"line":1378},[1323,4595,4596],{"class":1442},"        log",[1323,4598,1458],{"class":1339},[1323,4600,4601],{"class":1461},"Printf",[1323,4603,1481],{"class":1339},[1323,4605,4606],{"class":1352},"\"receive failed: ",[1323,4608,3398],{"class":3397},[1323,4610,3401],{"class":1352},[1323,4612,1446],{"class":1339},[1323,4614,3336],{"class":1442},[1323,4616,1496],{"class":1339},[1323,4618,4619],{"class":1325,"line":1390},[1323,4620,1502],{"emptyLinePlaceholder":1501},[1323,4622,4623],{"class":1325,"line":1401},[1323,4624,4625],{"class":1507},"        // Return safe error to caller\n",[1323,4627,4628,4631,4633,4635,4638,4640,4643,4645,4648],{"class":1325,"line":1550},[1323,4629,4630],{"class":1818},"        return",[1323,4632,3372],{"class":1328},[1323,4634,1446],{"class":1339},[1323,4636,4637],{"class":1442}," fmt",[1323,4639,1458],{"class":1339},[1323,4641,4642],{"class":1461},"Errorf",[1323,4644,1481],{"class":1339},[1323,4646,4647],{"class":1352},"\"invalid request format\"",[1323,4649,1496],{"class":1339},[1323,4651,4652],{"class":1325,"line":1579},[1323,4653,2028],{"class":1339},[1323,4655,4656,4658,4660,4662],{"class":1325,"line":1584},[1323,4657,2580],{"class":1818},[1323,4659,1536],{"class":1442},[1323,4661,1446],{"class":1339},[1323,4663,4664],{"class":1328}," nil\n",[1323,4666,4667],{"class":1325,"line":1590},[1323,4668,1404],{"class":1339},[2693,4670,1121],{"id":4671},"retry-with-backoff",[1234,4673,4674],{},"For transient failures (e.g., KMS errors):",[1314,4676,4678],{"className":1316,"code":4677,"language":1318,"meta":34,"style":34},"func loadWithRetry(ctx context.Context, data []byte) (*User, error) {\n    var user *User\n    var err error\n\n    for attempt := 0; attempt \u003C 3; attempt++ {\n        user, err = proc.Load(ctx, data)\n        if err == nil {\n            return user, nil\n        }\n\n        // Only retry on transient errors\n        if !isTransient(err) {\n            return nil, err\n        }\n\n        time.Sleep(time.Duration(attempt*100) * time.Millisecond)\n    }\n\n    return nil, fmt.Errorf(\"load failed after retries: %w\", err)\n}\n\nfunc isTransient(err error) bool {\n    // KMS timeout, network error, etc.\n    return strings.Contains(err.Error(), \"timeout\") ||\n           strings.Contains(err.Error(), \"connection\")\n}\n",[1320,4679,4680,4721,4732,4741,4745,4777,4804,4818,4829,4834,4838,4843,4861,4872,4876,4880,4922,4926,4930,4959,4963,4967,4987,4992,5023,5047],{"__ignoreMap":34},[1323,4681,4682,4684,4687,4689,4691,4693,4695,4697,4699,4701,4703,4705,4707,4709,4711,4713,4715,4717,4719],{"class":1325,"line":9},[1323,4683,1792],{"class":1328},[1323,4685,4686],{"class":1461}," loadWithRetry",[1323,4688,1481],{"class":1339},[1323,4690,1531],{"class":1798},[1323,4692,1853],{"class":1332},[1323,4694,1458],{"class":1339},[1323,4696,4521],{"class":1332},[1323,4698,1446],{"class":1339},[1323,4700,3653],{"class":1798},[1323,4702,4529],{"class":1339},[1323,4704,1931],{"class":1332},[1323,4706,1804],{"class":1339},[1323,4708,1795],{"class":1339},[1323,4710,4538],{"class":1818},[1323,4712,1467],{"class":1332},[1323,4714,1446],{"class":1339},[1323,4716,2563],{"class":1332},[1323,4718,1804],{"class":1339},[1323,4720,1340],{"class":1339},[1323,4722,4723,4725,4727,4729],{"class":1325,"line":19},[1323,4724,3677],{"class":1328},[1323,4726,1536],{"class":1442},[1323,4728,2555],{"class":1818},[1323,4730,4731],{"class":1332},"User\n",[1323,4733,4734,4736,4738],{"class":1325,"line":40},[1323,4735,3677],{"class":1328},[1323,4737,3336],{"class":1442},[1323,4739,4740],{"class":1332}," error\n",[1323,4742,4743],{"class":1325,"line":762},[1323,4744,1502],{"emptyLinePlaceholder":1501},[1323,4746,4747,4750,4753,4755,4759,4762,4764,4767,4770,4772,4775],{"class":1325,"line":1378},[1323,4748,4749],{"class":1818},"    for",[1323,4751,4752],{"class":1442}," attempt",[1323,4754,1452],{"class":1442},[1323,4756,4758],{"class":4757},"sMAmT"," 0",[1323,4760,4761],{"class":1339},";",[1323,4763,4752],{"class":1442},[1323,4765,4766],{"class":1818}," \u003C",[1323,4768,4769],{"class":4757}," 3",[1323,4771,4761],{"class":1339},[1323,4773,4774],{"class":1442}," attempt++",[1323,4776,1340],{"class":1339},[1323,4778,4779,4782,4784,4786,4788,4790,4792,4794,4796,4798,4800,4802],{"class":1325,"line":1390},[1323,4780,4781],{"class":1442},"        user",[1323,4783,1446],{"class":1339},[1323,4785,3336],{"class":1442},[1323,4787,3859],{"class":1442},[1323,4789,1522],{"class":1442},[1323,4791,1458],{"class":1339},[1323,4793,770],{"class":1461},[1323,4795,1481],{"class":1339},[1323,4797,1531],{"class":1442},[1323,4799,1446],{"class":1339},[1323,4801,3653],{"class":1442},[1323,4803,1496],{"class":1339},[1323,4805,4806,4809,4811,4814,4816],{"class":1325,"line":1401},[1323,4807,4808],{"class":1818},"        if",[1323,4810,3336],{"class":1442},[1323,4812,4813],{"class":1818}," ==",[1323,4815,3372],{"class":1328},[1323,4817,1340],{"class":1339},[1323,4819,4820,4823,4825,4827],{"class":1325,"line":1550},[1323,4821,4822],{"class":1818},"            return",[1323,4824,1536],{"class":1442},[1323,4826,1446],{"class":1339},[1323,4828,4664],{"class":1328},[1323,4830,4831],{"class":1325,"line":1579},[1323,4832,4833],{"class":1339},"        }\n",[1323,4835,4836],{"class":1325,"line":1584},[1323,4837,1502],{"emptyLinePlaceholder":1501},[1323,4839,4840],{"class":1325,"line":1590},[1323,4841,4842],{"class":1507},"        // Only retry on transient errors\n",[1323,4844,4845,4847,4850,4853,4855,4857,4859],{"class":1325,"line":1619},[1323,4846,4808],{"class":1818},[1323,4848,4849],{"class":1818}," !",[1323,4851,4852],{"class":1461},"isTransient",[1323,4854,1481],{"class":1339},[1323,4856,3480],{"class":1442},[1323,4858,1804],{"class":1339},[1323,4860,1340],{"class":1339},[1323,4862,4863,4865,4867,4869],{"class":1325,"line":1624},[1323,4864,4822],{"class":1818},[1323,4866,3372],{"class":1328},[1323,4868,1446],{"class":1339},[1323,4870,4871],{"class":1442}," err\n",[1323,4873,4874],{"class":1325,"line":1630},[1323,4875,4833],{"class":1339},[1323,4877,4878],{"class":1325,"line":1779},[1323,4879,1502],{"emptyLinePlaceholder":1501},[1323,4881,4882,4885,4887,4890,4892,4895,4897,4900,4902,4905,4908,4910,4912,4915,4917,4920],{"class":1325,"line":1784},[1323,4883,4884],{"class":1442},"        time",[1323,4886,1458],{"class":1339},[1323,4888,4889],{"class":1461},"Sleep",[1323,4891,1481],{"class":1339},[1323,4893,4894],{"class":1442},"time",[1323,4896,1458],{"class":1339},[1323,4898,4899],{"class":1461},"Duration",[1323,4901,1481],{"class":1339},[1323,4903,4904],{"class":1442},"attempt*",[1323,4906,4907],{"class":4757},"100",[1323,4909,1804],{"class":1339},[1323,4911,2555],{"class":1442},[1323,4913,4914],{"class":1442}," time",[1323,4916,1458],{"class":1339},[1323,4918,4919],{"class":1442},"Millisecond",[1323,4921,1496],{"class":1339},[1323,4923,4924],{"class":1325,"line":1789},[1323,4925,2028],{"class":1339},[1323,4927,4928],{"class":1325,"line":1828},[1323,4929,1502],{"emptyLinePlaceholder":1501},[1323,4931,4932,4934,4936,4938,4940,4942,4944,4946,4949,4951,4953,4955,4957],{"class":1325,"line":1833},[1323,4933,2580],{"class":1818},[1323,4935,3372],{"class":1328},[1323,4937,1446],{"class":1339},[1323,4939,4637],{"class":1442},[1323,4941,1458],{"class":1339},[1323,4943,4642],{"class":1461},[1323,4945,1481],{"class":1339},[1323,4947,4948],{"class":1352},"\"load failed after retries: ",[1323,4950,4144],{"class":3397},[1323,4952,3401],{"class":1352},[1323,4954,1446],{"class":1339},[1323,4956,3336],{"class":1442},[1323,4958,1496],{"class":1339},[1323,4960,4961],{"class":1325,"line":1845},[1323,4962,1404],{"class":1339},[1323,4964,4965],{"class":1325,"line":1864},[1323,4966,1502],{"emptyLinePlaceholder":1501},[1323,4968,4969,4971,4974,4976,4978,4980,4982,4985],{"class":1325,"line":1869},[1323,4970,1792],{"class":1328},[1323,4972,4973],{"class":1461}," isTransient",[1323,4975,1481],{"class":1339},[1323,4977,3480],{"class":1798},[1323,4979,2563],{"class":1332},[1323,4981,1804],{"class":1339},[1323,4983,4984],{"class":1332}," bool",[1323,4986,1340],{"class":1339},[1323,4988,4989],{"class":1325,"line":1875},[1323,4990,4991],{"class":1507},"    // KMS timeout, network error, etc.\n",[1323,4993,4994,4996,4999,5001,5004,5006,5008,5010,5012,5015,5018,5020],{"class":1325,"line":1899},[1323,4995,2580],{"class":1818},[1323,4997,4998],{"class":1442}," strings",[1323,5000,1458],{"class":1339},[1323,5002,5003],{"class":1461},"Contains",[1323,5005,1481],{"class":1339},[1323,5007,3480],{"class":1442},[1323,5009,1458],{"class":1339},[1323,5011,3280],{"class":1461},[1323,5013,5014],{"class":1339},"(),",[1323,5016,5017],{"class":1352}," \"timeout\"",[1323,5019,1804],{"class":1339},[1323,5021,5022],{"class":1818}," ||\n",[1323,5024,5025,5028,5030,5032,5034,5036,5038,5040,5042,5045],{"class":1325,"line":1904},[1323,5026,5027],{"class":1442},"           strings",[1323,5029,1458],{"class":1339},[1323,5031,5003],{"class":1461},[1323,5033,1481],{"class":1339},[1323,5035,3480],{"class":1442},[1323,5037,1458],{"class":1339},[1323,5039,3280],{"class":1461},[1323,5041,5014],{"class":1339},[1323,5043,5044],{"class":1352}," \"connection\"",[1323,5046,1496],{"class":1339},[1323,5048,5049],{"class":1325,"line":1910},[1323,5050,1404],{"class":1339},[2693,5052,1126],{"id":5053},"field-level-recovery",[1234,5055,5056],{},"Using override interfaces for partial success:",[1314,5058,5060],{"className":1316,"code":5059,"language":1318,"meta":34,"style":34},"func (u *User) Decrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n    enc := encryptors[cereal.EncryptAES]\n\n    // Try to decrypt email, leave as-is if failed\n    if u.Email != \"\" {\n        ciphertext, err := base64.StdEncoding.DecodeString(u.Email)\n        if err == nil {\n            plaintext, err := enc.Decrypt(ciphertext)\n            if err == nil {\n                u.Email = string(plaintext)\n            }\n            // Silently skip on decrypt failure (maybe already plaintext)\n        }\n    }\n\n    return nil // Never fail\n}\n",[1320,5061,5062,5111,5131,5135,5140,5157,5192,5204,5227,5240,5260,5265,5270,5274,5278,5282,5291],{"__ignoreMap":34},[1323,5063,5064,5066,5068,5070,5072,5074,5076,5079,5081,5084,5087,5089,5091,5093,5095,5098,5100,5102,5105,5107,5109],{"class":1325,"line":9},[1323,5065,1792],{"class":1328},[1323,5067,1795],{"class":1339},[1323,5069,1799],{"class":1798},[1323,5071,4538],{"class":1818},[1323,5073,1467],{"class":1332},[1323,5075,1804],{"class":1339},[1323,5077,5078],{"class":1461}," Decrypt",[1323,5080,1481],{"class":1339},[1323,5082,5083],{"class":1798},"encryptors",[1323,5085,5086],{"class":1328}," map",[1323,5088,1464],{"class":1339},[1323,5090,1231],{"class":1332},[1323,5092,1458],{"class":1339},[1323,5094,862],{"class":1332},[1323,5096,5097],{"class":1339},"]",[1323,5099,1231],{"class":1332},[1323,5101,1458],{"class":1339},[1323,5103,5104],{"class":1332},"Encryptor",[1323,5106,1804],{"class":1339},[1323,5108,2563],{"class":1332},[1323,5110,1340],{"class":1339},[1323,5112,5113,5115,5117,5120,5122,5124,5126,5128],{"class":1325,"line":19},[1323,5114,1913],{"class":1442},[1323,5116,1452],{"class":1442},[1323,5118,5119],{"class":1442}," encryptors",[1323,5121,1464],{"class":1339},[1323,5123,1231],{"class":1442},[1323,5125,1458],{"class":1339},[1323,5127,1488],{"class":1442},[1323,5129,5130],{"class":1339},"]\n",[1323,5132,5133],{"class":1325,"line":40},[1323,5134,1502],{"emptyLinePlaceholder":1501},[1323,5136,5137],{"class":1325,"line":762},[1323,5138,5139],{"class":1507},"    // Try to decrypt email, leave as-is if failed\n",[1323,5141,5142,5144,5146,5148,5150,5152,5155],{"class":1325,"line":1378},[1323,5143,3694],{"class":1818},[1323,5145,1822],{"class":1442},[1323,5147,1458],{"class":1339},[1323,5149,2087],{"class":1442},[1323,5151,3369],{"class":1818},[1323,5153,5154],{"class":1352}," \"\"",[1323,5156,1340],{"class":1339},[1323,5158,5159,5162,5164,5166,5168,5171,5173,5176,5178,5181,5183,5186,5188,5190],{"class":1325,"line":1390},[1323,5160,5161],{"class":1442},"        ciphertext",[1323,5163,1446],{"class":1339},[1323,5165,3336],{"class":1442},[1323,5167,1452],{"class":1442},[1323,5169,5170],{"class":1442}," base64",[1323,5172,1458],{"class":1339},[1323,5174,5175],{"class":1442},"StdEncoding",[1323,5177,1458],{"class":1339},[1323,5179,5180],{"class":1461},"DecodeString",[1323,5182,1481],{"class":1339},[1323,5184,5185],{"class":1442},"u",[1323,5187,1458],{"class":1339},[1323,5189,2087],{"class":1442},[1323,5191,1496],{"class":1339},[1323,5193,5194,5196,5198,5200,5202],{"class":1325,"line":1401},[1323,5195,4808],{"class":1818},[1323,5197,3336],{"class":1442},[1323,5199,4813],{"class":1818},[1323,5201,3372],{"class":1328},[1323,5203,1340],{"class":1339},[1323,5205,5206,5209,5211,5213,5215,5217,5219,5221,5223,5225],{"class":1325,"line":1550},[1323,5207,5208],{"class":1442},"            plaintext",[1323,5210,1446],{"class":1339},[1323,5212,3336],{"class":1442},[1323,5214,1452],{"class":1442},[1323,5216,1961],{"class":1442},[1323,5218,1458],{"class":1339},[1323,5220,4167],{"class":1461},[1323,5222,1481],{"class":1339},[1323,5224,4172],{"class":1442},[1323,5226,1496],{"class":1339},[1323,5228,5229,5232,5234,5236,5238],{"class":1325,"line":1579},[1323,5230,5231],{"class":1818},"            if",[1323,5233,3336],{"class":1442},[1323,5235,4813],{"class":1818},[1323,5237,3372],{"class":1328},[1323,5239,1340],{"class":1339},[1323,5241,5242,5245,5247,5249,5251,5253,5255,5258],{"class":1325,"line":1584},[1323,5243,5244],{"class":1442},"                u",[1323,5246,1458],{"class":1339},[1323,5248,2087],{"class":1442},[1323,5250,3859],{"class":1442},[1323,5252,1372],{"class":1332},[1323,5254,1481],{"class":1339},[1323,5256,5257],{"class":1442},"plaintext",[1323,5259,1496],{"class":1339},[1323,5261,5262],{"class":1325,"line":1590},[1323,5263,5264],{"class":1339},"            }\n",[1323,5266,5267],{"class":1325,"line":1619},[1323,5268,5269],{"class":1507},"            // Silently skip on decrypt failure (maybe already plaintext)\n",[1323,5271,5272],{"class":1325,"line":1624},[1323,5273,4833],{"class":1339},[1323,5275,5276],{"class":1325,"line":1630},[1323,5277,2028],{"class":1339},[1323,5279,5280],{"class":1325,"line":1779},[1323,5281,1502],{"emptyLinePlaceholder":1501},[1323,5283,5284,5286,5288],{"class":1325,"line":1784},[1323,5285,2580],{"class":1818},[1323,5287,3372],{"class":1328},[1323,5289,5290],{"class":1507}," // Never fail\n",[1323,5292,5293],{"class":1325,"line":1789},[1323,5294,1404],{"class":1339},[1306,5296,1131],{"id":5297},"nil-pointer-behavior",[1234,5299,5300],{},"Fields in nil nested structs are silently skipped:",[1314,5302,5304],{"className":1316,"code":5303,"language":1318,"meta":34,"style":34},"type User struct {\n    Profile *Profile `json:\"profile\"`\n}\n\ntype Profile struct {\n    Email string `json:\"email\" store.encrypt:\"aes\"`\n}\n\nuser := &User{Profile: nil}\ndata, err := proc.Store(ctx, user)\n// No error - Profile.Email skipped because Profile is nil\n",[1320,5305,5306,5316,5329,5333,5337,5348,5357,5361,5365,5385,5412],{"__ignoreMap":34},[1323,5307,5308,5310,5312,5314],{"class":1325,"line":9},[1323,5309,1329],{"class":1328},[1323,5311,1333],{"class":1332},[1323,5313,1336],{"class":1328},[1323,5315,1340],{"class":1339},[1323,5317,5318,5321,5323,5326],{"class":1325,"line":19},[1323,5319,5320],{"class":1345},"    Profile",[1323,5322,2555],{"class":1818},[1323,5324,5325],{"class":1332},"Profile",[1323,5327,5328],{"class":1352}," `json:\"profile\"`\n",[1323,5330,5331],{"class":1325,"line":40},[1323,5332,1404],{"class":1339},[1323,5334,5335],{"class":1325,"line":762},[1323,5336,1502],{"emptyLinePlaceholder":1501},[1323,5338,5339,5341,5344,5346],{"class":1325,"line":1378},[1323,5340,1329],{"class":1328},[1323,5342,5343],{"class":1332}," Profile",[1323,5345,1336],{"class":1328},[1323,5347,1340],{"class":1339},[1323,5349,5350,5352,5354],{"class":1325,"line":1390},[1323,5351,1358],{"class":1345},[1323,5353,1372],{"class":1332},[1323,5355,5356],{"class":1352}," `json:\"email\" store.encrypt:\"aes\"`\n",[1323,5358,5359],{"class":1325,"line":1401},[1323,5360,1404],{"class":1339},[1323,5362,5363],{"class":1325,"line":1550},[1323,5364,1502],{"emptyLinePlaceholder":1501},[1323,5366,5367,5369,5371,5373,5375,5377,5379,5381,5383],{"class":1325,"line":1579},[1323,5368,2286],{"class":1442},[1323,5370,1452],{"class":1442},[1323,5372,2283],{"class":1818},[1323,5374,1467],{"class":1332},[1323,5376,4051],{"class":1339},[1323,5378,5325],{"class":1345},[1323,5380,1990],{"class":1339},[1323,5382,3372],{"class":1328},[1323,5384,1404],{"class":1339},[1323,5386,5387,5390,5392,5394,5396,5398,5400,5402,5404,5406,5408,5410],{"class":1325,"line":1584},[1323,5388,5389],{"class":1442},"data",[1323,5391,1446],{"class":1339},[1323,5393,3336],{"class":1442},[1323,5395,1452],{"class":1442},[1323,5397,1522],{"class":1442},[1323,5399,1458],{"class":1339},[1323,5401,775],{"class":1461},[1323,5403,1481],{"class":1339},[1323,5405,1531],{"class":1442},[1323,5407,1446],{"class":1339},[1323,5409,1536],{"class":1442},[1323,5411,1496],{"class":1339},[1323,5413,5414],{"class":1325,"line":1590},[1323,5415,5416],{"class":1507},"// No error - Profile.Email skipped because Profile is nil\n",[1234,5418,5419],{},"This is by design. Nil checks happen during field traversal; nil pointers short-circuit without error.",[1306,5421,1136],{"id":5422},"validation-timing",[1234,5424,5425],{},"Validation runs automatically on the first operation. Missing handlers are caught immediately:",[1314,5427,5429],{"className":1316,"code":5428,"language":1318,"meta":34,"style":34},"proc, _ := cereal.NewProcessor[User](json.New())\n// Forgot SetEncryptor...\n\ndata, err := proc.Store(ctx, &user)\n// err: \"missing encryptor for algorithm \\\"aes\\\" (field Email)\"\n",[1320,5430,5431,5461,5466,5470,5498],{"__ignoreMap":34},[1323,5432,5433,5435,5437,5439,5441,5443,5445,5447,5449,5451,5453,5455,5457,5459],{"class":1325,"line":9},[1323,5434,1443],{"class":1442},[1323,5436,1446],{"class":1339},[1323,5438,1449],{"class":1442},[1323,5440,1452],{"class":1442},[1323,5442,1455],{"class":1442},[1323,5444,1458],{"class":1339},[1323,5446,750],{"class":1461},[1323,5448,1464],{"class":1339},[1323,5450,1467],{"class":1332},[1323,5452,3351],{"class":1339},[1323,5454,2248],{"class":1442},[1323,5456,1458],{"class":1339},[1323,5458,2253],{"class":1461},[1323,5460,2256],{"class":1339},[1323,5462,5463],{"class":1325,"line":19},[1323,5464,5465],{"class":1507},"// Forgot SetEncryptor...\n",[1323,5467,5468],{"class":1325,"line":40},[1323,5469,1502],{"emptyLinePlaceholder":1501},[1323,5471,5472,5474,5476,5478,5480,5482,5484,5486,5488,5490,5492,5494,5496],{"class":1325,"line":762},[1323,5473,5389],{"class":1442},[1323,5475,1446],{"class":1339},[1323,5477,3336],{"class":1442},[1323,5479,1452],{"class":1442},[1323,5481,1522],{"class":1442},[1323,5483,1458],{"class":1339},[1323,5485,775],{"class":1461},[1323,5487,1481],{"class":1339},[1323,5489,1531],{"class":1442},[1323,5491,1446],{"class":1339},[1323,5493,2283],{"class":1818},[1323,5495,2286],{"class":1442},[1323,5497,1496],{"class":1339},[1323,5499,5500],{"class":1325,"line":1378},[1323,5501,5502],{"class":1507},"// err: \"missing encryptor for algorithm \\\"aes\\\" (field Email)\"\n",[1234,5504,5505,5506,5509],{},"For earlier detection, call ",[1320,5507,5508],{},"Validate()"," explicitly at startup:",[1314,5511,5513],{"className":1316,"code":5512,"language":1318,"meta":34,"style":34},"proc, _ := cereal.NewProcessor[User](json.New())\nproc.SetEncryptor(cereal.EncryptAES, enc)\n\nif err := proc.Validate(); err != nil {\n    log.Fatal(err) // Fail fast at startup\n}\n",[1320,5514,5515,5545,5567,5571,5595,5612],{"__ignoreMap":34},[1323,5516,5517,5519,5521,5523,5525,5527,5529,5531,5533,5535,5537,5539,5541,5543],{"class":1325,"line":9},[1323,5518,1443],{"class":1442},[1323,5520,1446],{"class":1339},[1323,5522,1449],{"class":1442},[1323,5524,1452],{"class":1442},[1323,5526,1455],{"class":1442},[1323,5528,1458],{"class":1339},[1323,5530,750],{"class":1461},[1323,5532,1464],{"class":1339},[1323,5534,1467],{"class":1332},[1323,5536,3351],{"class":1339},[1323,5538,2248],{"class":1442},[1323,5540,1458],{"class":1339},[1323,5542,2253],{"class":1461},[1323,5544,2256],{"class":1339},[1323,5546,5547,5549,5551,5553,5555,5557,5559,5561,5563,5565],{"class":1325,"line":19},[1323,5548,1443],{"class":1442},[1323,5550,1458],{"class":1339},[1323,5552,819],{"class":1461},[1323,5554,1481],{"class":1339},[1323,5556,1231],{"class":1442},[1323,5558,1458],{"class":1339},[1323,5560,1488],{"class":1442},[1323,5562,1446],{"class":1339},[1323,5564,1961],{"class":1442},[1323,5566,1496],{"class":1339},[1323,5568,5569],{"class":1325,"line":40},[1323,5570,1502],{"emptyLinePlaceholder":1501},[1323,5572,5573,5575,5577,5579,5581,5583,5585,5587,5589,5591,5593],{"class":1325,"line":762},[1323,5574,3364],{"class":1818},[1323,5576,3336],{"class":1442},[1323,5578,1452],{"class":1442},[1323,5580,1522],{"class":1442},[1323,5582,1458],{"class":1339},[1323,5584,834],{"class":1461},[1323,5586,4466],{"class":1339},[1323,5588,3336],{"class":1442},[1323,5590,3369],{"class":1818},[1323,5592,3372],{"class":1328},[1323,5594,1340],{"class":1339},[1323,5596,5597,5599,5601,5603,5605,5607,5609],{"class":1325,"line":1378},[1323,5598,3384],{"class":1442},[1323,5600,1458],{"class":1339},[1323,5602,4357],{"class":1461},[1323,5604,1481],{"class":1339},[1323,5606,3480],{"class":1442},[1323,5608,1804],{"class":1339},[1323,5610,5611],{"class":1507}," // Fail fast at startup\n",[1323,5613,5614],{"class":1325,"line":1390},[1323,5615,1404],{"class":1339},[1234,5617,5618],{},"Both approaches produce the same error—explicit validation just moves it earlier in the lifecycle.",[1306,5620,1141],{"id":5621},"override-interface-errors",[1234,5623,5624],{},"When implementing override interfaces, return errors to stop the operation:",[1314,5626,5628],{"className":1316,"code":5627,"language":1318,"meta":34,"style":34},"func (u *User) Encrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {\n    enc, ok := encryptors[cereal.EncryptAES]\n    if !ok {\n        return fmt.Errorf(\"AES encryptor not configured\")\n    }\n\n    ciphertext, err := enc.Encrypt([]byte(u.Email))\n    if err != nil {\n        return fmt.Errorf(\"encrypt email: %w\", err)\n    }\n\n    u.Email = base64.StdEncoding.EncodeToString(ciphertext)\n    return nil\n}\n",[1320,5629,5630,5675,5698,5709,5726,5730,5734,5766,5778,5803,5807,5811,5839,5845],{"__ignoreMap":34},[1323,5631,5632,5634,5636,5638,5640,5642,5644,5647,5649,5651,5653,5655,5657,5659,5661,5663,5665,5667,5669,5671,5673],{"class":1325,"line":9},[1323,5633,1792],{"class":1328},[1323,5635,1795],{"class":1339},[1323,5637,1799],{"class":1798},[1323,5639,4538],{"class":1818},[1323,5641,1467],{"class":1332},[1323,5643,1804],{"class":1339},[1323,5645,5646],{"class":1461}," Encrypt",[1323,5648,1481],{"class":1339},[1323,5650,5083],{"class":1798},[1323,5652,5086],{"class":1328},[1323,5654,1464],{"class":1339},[1323,5656,1231],{"class":1332},[1323,5658,1458],{"class":1339},[1323,5660,862],{"class":1332},[1323,5662,5097],{"class":1339},[1323,5664,1231],{"class":1332},[1323,5666,1458],{"class":1339},[1323,5668,5104],{"class":1332},[1323,5670,1804],{"class":1339},[1323,5672,2563],{"class":1332},[1323,5674,1340],{"class":1339},[1323,5676,5677,5679,5681,5684,5686,5688,5690,5692,5694,5696],{"class":1325,"line":19},[1323,5678,1913],{"class":1442},[1323,5680,1446],{"class":1339},[1323,5682,5683],{"class":1442}," ok",[1323,5685,1452],{"class":1442},[1323,5687,5119],{"class":1442},[1323,5689,1464],{"class":1339},[1323,5691,1231],{"class":1442},[1323,5693,1458],{"class":1339},[1323,5695,1488],{"class":1442},[1323,5697,5130],{"class":1339},[1323,5699,5700,5702,5704,5707],{"class":1325,"line":40},[1323,5701,3694],{"class":1818},[1323,5703,4849],{"class":1818},[1323,5705,5706],{"class":1442},"ok",[1323,5708,1340],{"class":1339},[1323,5710,5711,5713,5715,5717,5719,5721,5724],{"class":1325,"line":762},[1323,5712,4630],{"class":1818},[1323,5714,4637],{"class":1442},[1323,5716,1458],{"class":1339},[1323,5718,4642],{"class":1461},[1323,5720,1481],{"class":1339},[1323,5722,5723],{"class":1352},"\"AES encryptor not configured\"",[1323,5725,1496],{"class":1339},[1323,5727,5728],{"class":1325,"line":1378},[1323,5729,2028],{"class":1339},[1323,5731,5732],{"class":1325,"line":1390},[1323,5733,1502],{"emptyLinePlaceholder":1501},[1323,5735,5736,5739,5741,5743,5745,5747,5749,5752,5754,5756,5758,5760,5762,5764],{"class":1325,"line":1401},[1323,5737,5738],{"class":1442},"    ciphertext",[1323,5740,1446],{"class":1339},[1323,5742,3336],{"class":1442},[1323,5744,1452],{"class":1442},[1323,5746,1961],{"class":1442},[1323,5748,1458],{"class":1339},[1323,5750,5751],{"class":1461},"Encrypt",[1323,5753,1928],{"class":1339},[1323,5755,1931],{"class":1332},[1323,5757,1481],{"class":1339},[1323,5759,5185],{"class":1442},[1323,5761,1458],{"class":1339},[1323,5763,2087],{"class":1442},[1323,5765,1939],{"class":1339},[1323,5767,5768,5770,5772,5774,5776],{"class":1325,"line":1550},[1323,5769,3694],{"class":1818},[1323,5771,3336],{"class":1442},[1323,5773,3369],{"class":1818},[1323,5775,3372],{"class":1328},[1323,5777,1340],{"class":1339},[1323,5779,5780,5782,5784,5786,5788,5790,5793,5795,5797,5799,5801],{"class":1325,"line":1579},[1323,5781,4630],{"class":1818},[1323,5783,4637],{"class":1442},[1323,5785,1458],{"class":1339},[1323,5787,4642],{"class":1461},[1323,5789,1481],{"class":1339},[1323,5791,5792],{"class":1352},"\"encrypt email: ",[1323,5794,4144],{"class":3397},[1323,5796,3401],{"class":1352},[1323,5798,1446],{"class":1339},[1323,5800,3336],{"class":1442},[1323,5802,1496],{"class":1339},[1323,5804,5805],{"class":1325,"line":1584},[1323,5806,2028],{"class":1339},[1323,5808,5809],{"class":1325,"line":1590},[1323,5810,1502],{"emptyLinePlaceholder":1501},[1323,5812,5813,5816,5818,5820,5822,5824,5826,5828,5830,5833,5835,5837],{"class":1325,"line":1619},[1323,5814,5815],{"class":1442},"    u",[1323,5817,1458],{"class":1339},[1323,5819,2087],{"class":1442},[1323,5821,3859],{"class":1442},[1323,5823,5170],{"class":1442},[1323,5825,1458],{"class":1339},[1323,5827,5175],{"class":1442},[1323,5829,1458],{"class":1339},[1323,5831,5832],{"class":1461},"EncodeToString",[1323,5834,1481],{"class":1339},[1323,5836,4172],{"class":1442},[1323,5838,1496],{"class":1339},[1323,5840,5841,5843],{"class":1325,"line":1624},[1323,5842,2580],{"class":1818},[1323,5844,4664],{"class":1328},[1323,5846,5847],{"class":1325,"line":1630},[1323,5848,1404],{"class":1339},[1234,5850,5851],{},"The processor wraps your error and returns it from the operation.",[2807,5853,5854],{},"html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"title":34,"searchDepth":19,"depth":19,"links":5856},[5857,5865,5866,5872,5873,5874],{"id":3263,"depth":19,"text":1068,"children":5858},[5859,5860,5861,5862,5863,5864],{"id":3266,"depth":40,"text":1072},{"id":3414,"depth":40,"text":1077},{"id":3540,"depth":40,"text":1082},{"id":3733,"depth":40,"text":1087},{"id":3884,"depth":40,"text":1092},{"id":3925,"depth":40,"text":1097},{"id":4134,"depth":19,"text":1102},{"id":4295,"depth":19,"text":1107,"children":5867},[5868,5869,5870,5871],{"id":4298,"depth":40,"text":1111},{"id":4495,"depth":40,"text":1116},{"id":4671,"depth":40,"text":1121},{"id":5053,"depth":40,"text":1126},{"id":5297,"depth":19,"text":1131},{"id":5422,"depth":19,"text":1136},{"id":5621,"depth":19,"text":1141},{},"2025-12-27T00:00:00.000Z",null,{"title":1059,"description":1061},[1187,1059,5880],"Error Handling","-Aammiex3EUqk5iCE1r8aTzlwkhRHKETvFvh9VD-26A",[5883,5877],{"title":978,"path":977,"stem":1194,"description":980,"children":-1},1776266720438]