{"record":{"id":"5d618e84ceb3605a","repo":"Tencent/WeKnora","slug":"parse-dingtalk-credentials-w","errorCode":null,"errorMessage":"parse dingtalk credentials: %w","messagePattern":"parse dingtalk credentials: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/im/dingtalk/factory.go","lineNumber":16,"sourceCode":"package dingtalk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/Tencent/WeKnora/internal/im\"\n)\n\n// NewFactory returns an im.AdapterFactory for DingTalk channels.\n// Supports \"webhook\" and \"websocket\" (stream mode, default).\nfunc NewFactory() im.AdapterFactory {\n\treturn func(factoryCtx context.Context, channel *im.IMChannel, msgHandler func(context.Context, *im.IncomingMessage) error) (im.Adapter, context.CancelFunc, error) {\n\t\tcreds, err := im.ParseCredentials(channel.Credentials)\n\t\tif err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"parse dingtalk credentials: %w\", err)\n\t\t}\n\n\t\tclientID := im.GetString(creds, \"client_id\")\n\t\tclientSecret := im.GetString(creds, \"client_secret\")\n\t\tcardTemplateID := im.GetString(creds, \"card_template_id\")\n\n\t\tmode := im.ResolveMode(channel, \"websocket\")\n\n\t\tswitch mode {\n\t\tcase \"webhook\":\n\t\t\tadapter := NewWebhookAdapter(clientID, clientSecret, cardTemplateID)\n\t\t\treturn adapter, nil, nil\n\n\t\tcase \"websocket\":\n\t\t\twsCtx, wsCancel := context.WithCancel(context.Background())\n\t\t\tgo im.RunSupervised(wsCtx, im.SupervisorConfig{\n\t\t\t\tName: fmt.Sprintf(\"DingTalk channel %s\", channel.ID),\n\t\t\t\tConnect: func(ctx context.Context) (func(), error) {","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/im/dingtalk/factory.go#L1-L34","documentation":"The DingTalk IM adapter factory parses channel.Credentials via im.ParseCredentials before building the adapter. This error wraps any failure from that parse — malformed JSON or a non-map credentials value — so the DingTalk-specific context is preserved.","triggerScenarios":"Creating a DingTalk channel adapter where channel.Credentials is invalid JSON, null, or a non-object value.","commonSituations":"IM channel saved with malformed credentials JSON in the database/config; credentials field left as a raw string instead of an object; manual edits to channel config breaking JSON syntax.","solutions":["Fix the wrapped im.ParseCredentials error — validate that channel.Credentials is a JSON object with the expected keys.","Re-save the DingTalk channel credentials as valid JSON: {\"client_id\":..., \"client_secret\":..., optional \"card_template_id\"}.","Validate the credentials JSON with a JSON parser/linter before persisting the channel."],"exampleFix":"// before\n\"credentials\": \"{client_id: abc}\" // invalid JSON string\n// after\n\"credentials\": {\"client_id\": \"abc\", \"client_secret\": \"xyz\"}","handlingStrategy":"validation","validationCode":"var probe map[string]any\nif err := json.Unmarshal(channel.Credentials, &probe); err != nil {\n    return fmt.Errorf(\"dingtalk credentials must be a JSON object: %w\", err)\n}","typeGuard":"func isJSONObject(b []byte) bool {\n    var m map[string]any\n    return json.Unmarshal(b, &m) == nil\n}","tryCatchPattern":"adapter, cancel, err := dingtalkFactory(ctx, channel, handler)\nif err != nil {\n    if strings.Contains(err.Error(), \"parse dingtalk credentials\") {\n        return fmt.Errorf(\"re-save DingTalk channel credentials as valid JSON\")\n    }\n    return err\n}","preventionTips":["Store IM channel credentials as JSON objects, never raw strings.","Validate credentials JSON before persisting the channel.","Include client_id and client_secret keys with string values."],"tags":["credentials","json","dingtalk"],"backgroundTag":"invalid-credentials-json","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}