{"record":{"id":"a8cabc4c7935d109","repo":"netbirdio/netbird","slug":"invalid-duration","errorCode":null,"errorMessage":"invalid duration","messagePattern":"invalid duration","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/duration.go","lineNumber":35,"sourceCode":"\nfunc (d *Duration) UnmarshalJSON(b []byte) error {\n\tvar v interface{}\n\tif err := json.Unmarshal(b, &v); err != nil {\n\t\treturn err\n\t}\n\tswitch value := v.(type) {\n\tcase float64:\n\t\td.Duration = time.Duration(value)\n\t\treturn nil\n\tcase string:\n\t\tvar err error\n\t\td.Duration, err = time.ParseDuration(value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\tdefault:\n\t\treturn errors.New(\"invalid duration\")\n\t}\n}\n","sourceCodeStart":17,"sourceCodeEnd":38,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/util/duration.go#L17-L38","documentation":"util.Duration is a JSON-only duration wrapper. UnmarshalJSON first decodes the raw JSON into interface{} and switches on the type: float64 is taken as nanoseconds, string is parsed with time.ParseDuration; every other JSON type (bool, null, array, object) falls to the default branch and returns this generic error. Note that a malformed string such as \"5x\" returns the time.ParseDuration error instead, not this one.","triggerScenarios":"Sending {\"timeout\": null} (or true, [], {}) to an API field typed util.Duration; a frontend serializing an unset optional field as null instead of omitting it; hand-built JSON with the wrong type.","commonSituations":"JS clients where an undefined field becomes null in JSON.stringify; OpenAPI generators emitting null for optional durations; a boolean mistakenly serialized into a duration field.","solutions":["Send durations as strings (\"30s\", \"5m\") or numbers (nanoseconds), or omit the field entirely.","Fix the producer to skip null fields (e.g. JSON.stringify drops undefined, not null).","If null must be tolerated, decode into a custom nullable type or a map first and only call UnmarshalJSON for present, non-null values."],"exampleFix":"// before\n{\"pollInterval\": null}\n\n// after\n{\"pollInterval\": \"30s\"}\n// or omit the key entirely","handlingStrategy":"validation","validationCode":"// Validate payload shape before unmarshalling into util.Duration fields\nfunc isValidDurationJSON(raw []byte) bool {\n    var v any\n    if err := json.Unmarshal(raw, &v); err != nil {\n        return false\n    }\n    switch v.(type) {\n    case float64, string:\n        return true\n    }\n    return false\n}","typeGuard":"func isInvalidDuration(err error) bool {\n    return err != nil && err.Error() == \"invalid duration\"\n}","tryCatchPattern":"if err := json.Unmarshal(data, &cfg); err != nil {\n    if isInvalidDuration(err) {\n        // payload had a null/bool/array/object where a duration belongs:\n        // fix the producer to send \"30s\" or omit the field\n    }\n    return err\n}","preventionTips":["Always send durations as strings (\"30s\") or omit the field; never serialize null for optional durations.","In JS, use undefined (not null) for unset fields so JSON.stringify drops them.","Add API contract tests that assert the JSON type of every duration field."],"tags":["json","serialization","duration","api","validation"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}