{"record":{"id":"1f019fdab25cf90d","repo":"Tencent/WeKnora","slug":"app-id-is-required","errorCode":null,"errorMessage":"app_id is required","messagePattern":"app_id is required","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/application/service/weknoracloud.go","lineNumber":39,"sourceCode":"\n// NewWeKnoraCloudService 构造 WeKnoraCloudService\nfunc NewWeKnoraCloudService(\n\trepo interfaces.ModelRepository,\n\ttenantRepo interfaces.TenantRepository,\n) interfaces.WeKnoraCloudService {\n\treturn &weKnoraCloudService{\n\t\ttenantRepo: tenantRepo,\n\t}\n}\n\nfunc IsWeKnoraCloudDocReaderAddr(addr string) bool {\n\treturn strings.TrimSuffix(strings.TrimSpace(addr), \"/\") == strings.TrimRight(provider.WeKnoraCloudBaseURL, \"/\")+\"/api/v1/doc/reader\"\n}\n\n// SaveCredentials 仅保存 APPID/APPSECRET 凭证，不自动创建模型\nfunc (s *weKnoraCloudService) SaveCredentials(ctx context.Context, appID, appSecret string) error {\n\tif appID == \"\" {\n\t\treturn fmt.Errorf(\"app_id is required\")\n\t}\n\tif appSecret == \"\" {\n\t\treturn fmt.Errorf(\"app_secret is required\")\n\t}\n\n\tif err := s.verifyCredentials(ctx, appID, appSecret); err != nil {\n\t\treturn fmt.Errorf(\"credential verification failed: %w\", err)\n\t}\n\n\ttenantID := types.MustTenantIDFromContext(ctx)\n\treturn s.updateTenantCredentials(ctx, tenantID, appID, appSecret)\n}\n\n// verifyCredentials 向 WeKnoraCloud /api/v1/health 发送带签名头的 GET。\n//\n// 注意：health 一般为探活接口，远端常不校验 APPID/SECRET 或签名；HTTP 200 通常只表示\n// 「网关/服务可达」，不能严格证明凭证有效。若需强校验，应改为调用必须鉴权的业务接口。\nfunc (s *weKnoraCloudService) verifyCredentials(ctx context.Context, appID, appSecret string) error {","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/service/weknoracloud.go#L21-L57","documentation":"weKnoraCloudService.SaveCredentials stores WeKnora Cloud APPID/APPSECRET credentials for a tenant, but first requires appID to be non-empty. An empty appID is rejected immediately before any network verification happens.","triggerScenarios":"Calling SaveCredentials(ctx, \"\", secret) — e.g. an API handler that did not bind the app_id field from the request body, or a form where the APPID input was left blank.","commonSituations":"Frontend sends only the secret after a partial form fill; JSON field name mismatch (appID vs app_id) so the value unmarshals to empty string; config loader omits APPID because it's assumed to live elsewhere.","solutions":["Pass a non-empty appID to SaveCredentials after reading it from the request/config.","Fix the request binding so the client's field name matches the struct's JSON tag (app_id).","Add client-side required-field validation on the APPID input before submit.","Return a 400 with this message from the handler so users see the missing field clearly."],"exampleFix":"// before\nsvc.SaveCredentials(ctx, r.FormValue(\"appid\"), r.FormValue(\"app_secret\")) // typo -> \"\"\n// after\nappID := r.FormValue(\"app_id\")\nif appID == \"\" { http.Error(w, \"app_id is required\", 400); return }\nsvc.SaveCredentials(ctx, appID, r.FormValue(\"app_secret\"))","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(appID) == \"\" {\n\treturn errors.New(\"app_id must be provided before saving credentials\")\n}","typeGuard":null,"tryCatchPattern":"if err := svc.SaveCredentials(ctx, appID, appSecret); err != nil {\n\tif strings.Contains(err.Error(), \"app_id is required\") {\n\t\treturn fieldError(\"app_id\", err)\n\t}\n\treturn err\n}","preventionTips":["Mark the APPID field required in both frontend forms and API request validation.","Match JSON tags exactly (app_id) between client payloads and Go structs.","Fail fast at config load if APPID is expected but missing.","Trim input to avoid whitespace-only values passing naive checks."],"tags":["validation","weknora-cloud","credentials","input-validation"],"backgroundTag":"missing-required-argument","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}