{"record":{"id":"a982711451c04e07","repo":"Billionmail/BillionMail","slug":"error-marshalling-default-footer-config-v","errorCode":null,"errorMessage":"error marshalling default footer config: %v","messagePattern":"error marshalling default footer config: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/askai/project.go","lineNumber":718,"sourceCode":"// GetFooter retrieves the footer configuration for a given domain from a JSON file.\n// It reads the footer configuration file and returns a FooterConfig struct.\n// If the file does not exist or cannot be read, it returns an error.\nfunc GetFooter(Domain string) (FooterConfig, error) {\n\tfilename := fmt.Sprintf(PRODUCT_CONFIG_PATH+\"/%s/footer_config.json\", Domain)\n\tif !public.FileExists(filename) {\n\t\t// If the footer config file does not exist, return a default footer config\n\t\t// This allows the system to handle cases where the footer configuration has not been set up\n\t\t// and avoids errors when trying to read a non-existent file.\n\t\t// It also allows the user to create a new footer configuration without needing to handle file\n\t\t// not found errors.\n\t\tdefaultFooterConfig := FooterConfig{\n\t\t\tCopyrightText: \"© 2023 Your Company. All rights reserved.\",\n\t\t\tDisclaimer:    \"This is a sample disclaimer.\",\n\t\t}\n\t\tdefaultFooterConfig.UpdateTime = public.GetNowTime()\n\t\tdefaultFooterJson, err := json.MarshalIndent(defaultFooterConfig, \"\", \"  \")\n\t\tif err != nil {\n\t\t\treturn FooterConfig{}, fmt.Errorf(\"error marshalling default footer config: %v\", err)\n\t\t}\n\t\terr = os.WriteFile(filename, defaultFooterJson, 0644)\n\t\tif err != nil {\n\t\t\treturn FooterConfig{}, fmt.Errorf(\"error saving default footer config file: %v\", err)\n\t\t}\n\t\t// Return the default footer config if the file does not exist\n\t\treturn defaultFooterConfig, nil\n\t}\n\tdata, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn FooterConfig{}, fmt.Errorf(\"error reading footer config file: %v\", err)\n\t}\n\n\tvar config FooterConfig\n\terr = json.Unmarshal(data, &config)\n\tif err != nil {\n\t\treturn FooterConfig{}, fmt.Errorf(\"error unmarshalling footer config: %v\", err)\n\t}","sourceCodeStart":700,"sourceCodeEnd":736,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/askai/project.go#L700-L736","documentation":"GetFooter auto-provisions a default FooterConfig (sample copyright text and disclaimer) when footer_config.json does not exist, serializing it with json.MarshalIndent before writing. This error wraps a failure of that default-config serialization. FooterConfig contains only plain string/time fields, so this wrapper practically fires only if the struct gains a field encoding/json cannot handle or a custom marshaler errors.","triggerScenarios":"Calling GetFooter(Domain) (directly, or via GetFooterPrompt/ModifyFooter/AutoGetProjectInfo) when footer_config.json is absent and the constructed defaultFooterConfig cannot be JSON-encoded.","commonSituations":"A developer adds a non-serializable field (func, chan) or a json.RawMessage populated with invalid data to FooterConfig; first-run provisioning of a new domain then fails on every call.","solutions":["Inspect the wrapped 'json: unsupported type' message to identify the offending FooterConfig field; remove it or tag it `json:\"-\"`.","If FooterConfig has a custom MarshalJSON, fix the branch returning an error for default values.","Add a test marshalling the default FooterConfig used for auto-provisioning."],"exampleFix":"// before\ntype FooterConfig struct {\n    Renderer func(w io.Writer) // unsupported\n}\n// after\ntype FooterConfig struct {\n    Renderer func(w io.Writer) `json:\"-\"`\n}","handlingStrategy":"try-catch","validationCode":"if _, err := json.Marshal(askai.FooterConfig{CopyrightText: \"x\", Disclaimer: \"y\"}); err != nil {\n    return fmt.Errorf(\"FooterConfig not encodable: %w\", err)\n}","typeGuard":"func encodableConfig(c askai.FooterConfig) bool { _, err := json.Marshal(c); return err == nil }","tryCatchPattern":"cfg, err := askai.GetFooter(domain)\nif err != nil {\n    var ute *json.UnsupportedTypeError\n    if errors.As(err, &ute) { /* FooterConfig schema bug — fix struct */ }\n    return err\n}","preventionTips":["Test-marshall the default FooterConfig whenever the struct changes.","Avoid func/chan fields in persisted config structs; use `json:\"-\"`.","Keep custom marshalers side-effect free."],"tags":["go","json","serialization","footer","askai"],"backgroundTag":"json-unsupported-type","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}