{"record":{"id":"d775ed21827121c6","repo":"Tencent/WeKnora","slug":"flexiblestatus-expected-string-or-integer-got-s","errorCode":null,"errorMessage":"flexibleStatus: expected string or integer, got %s: %w","messagePattern":"flexibleStatus: expected string or integer, got (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/datasource/connector/yuque/types.go","lineNumber":115,"sourceCode":"\tif string(b) == \"null\" {\n\t\t*s = \"\"\n\t\treturn nil\n\t}\n\tif len(b) > 0 && b[0] == '\"' {\n\t\tvar str string\n\t\tif err := json.Unmarshal(b, &str); err != nil {\n\t\t\treturn err\n\t\t}\n\t\t*s = flexibleStatus(str)\n\t\treturn nil\n\t}\n\t// Integer form. We decode to int64 so that floats, booleans, arrays, and\n\t// objects fail loudly instead of being silently stringified — if Yuque\n\t// changes the shape again, we'd rather surface a clear error than feed\n\t// garbage to the Status == \"1\" comparison.\n\tvar i int64\n\tif err := json.Unmarshal(b, &i); err != nil {\n\t\treturn fmt.Errorf(\"flexibleStatus: expected string or integer, got %s: %w\", b, err)\n\t}\n\t*s = flexibleStatus(strconv.FormatInt(i, 10))\n\treturn nil\n}\n\n// apiErrorBody is the error body shape Yuque sometimes returns on non-2xx.\ntype apiErrorBody struct {\n\tMessage string `json:\"message\"`\n\tStatus  int    `json:\"status\"`\n}\n\n// v2UserResponse wraps GET /api/v2/user.\ntype v2UserResponse struct {\n\tData v2User `json:\"data\"`\n}\n\ntype v2User struct {\n\tID    int64  `json:\"id\"`","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/datasource/connector/yuque/types.go#L97-L133","documentation":"flexibleStatus is a custom JSON unmarshaler that accepts Yuque status fields as either a string or an integer (Yuque has changed this shape historically). When the JSON value is neither, it decodes as int64 and reports the raw bytes plus the underlying unmarshal error. This exists so silent coercion of booleans/floats/objects into status strings cannot happen.","triggerScenarios":"json.Unmarshal into a struct containing a flexibleStatus field where the API returned a boolean, float, array, or object for the status field.","commonSituations":"A Yuque API version change altering the status field shape; mocking the API with a wrong type (e.g. status: true); proxy fixtures returning different serialization.","solutions":["Inspect the 'got %s' portion of the message to see the actual JSON bytes returned and confirm the new shape.","Update flexibleStatus (or the fixture) to support the new shape if Yuque changed their API, adding an explicit case for it.","Fix mocked/fixture data to return status as a string (e.g. \"1\") or integer (e.g. 1)."],"exampleFix":"// before (fixture)\n{\"status\": true}\n// after\n{\"status\": \"1\"}","handlingStrategy":"validation","validationCode":"switch v := raw.(type) {\ncase string, int, int64, float64:\n    // acceptable status forms\ndefault:\n    return fmt.Errorf(\"status must be string or integer, got %T\", raw)\n}","typeGuard":"func isStringOrNumber(b []byte) bool {\n    var v any\n    if json.Unmarshal(b, &v) != nil { return false }\n    switch v.(type) { case string, float64: return true; default: return false }\n}","tryCatchPattern":"if err := json.Unmarshal(body, &resp); err != nil {\n    if strings.Contains(err.Error(), \"flexibleStatus\") {\n        log.Printf(\"Yuque status shape changed; raw=%s\", body)\n    }\n    return err\n}","preventionTips":["Pin/mock Yuque API responses with realistic status types (string \"1\" or int 1).","Keep fixtures updated when the Yuque API version changes.","Handle errors from UnmarshalJSON loudly rather than ignoring partial decode failures."],"tags":["json","unmarshal","type-mismatch"],"backgroundTag":"json-type-mismatch","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}