{"record":{"id":"3cb57a69cadb6b9a","repo":"fatedier/frp","slug":"unsupported-value-source-type-s","errorCode":null,"errorMessage":"unsupported value source type: %s","messagePattern":"unsupported value source type: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/v1/value_source.go","lineNumber":85,"sourceCode":"\t\treturn v.Exec.Validate()\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported value source type: %s (only 'file' and 'exec' are supported)\", v.Type)\n\t}\n}\n\n// Resolve resolves the value from the configured source.\nfunc (v *ValueSource) Resolve(ctx context.Context) (string, error) {\n\tif err := v.Validate(); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tswitch v.Type {\n\tcase \"file\":\n\t\treturn v.File.Resolve(ctx)\n\tcase \"exec\":\n\t\treturn v.Exec.Resolve(ctx)\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"unsupported value source type: %s\", v.Type)\n\t}\n}\n\n// Validate validates the FileSource configuration.\nfunc (f *FileSource) Validate() error {\n\tif f == nil {\n\t\treturn errors.New(\"fileSource cannot be nil\")\n\t}\n\n\tif f.Path == \"\" {\n\t\treturn errors.New(\"file path cannot be empty\")\n\t}\n\treturn nil\n}\n\n// Resolve reads and returns the content from the specified file.\nfunc (f *FileSource) Resolve(_ context.Context) (string, error) {\n\tif err := f.Validate(); err != nil {","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/config/v1/value_source.go#L67-L103","documentation":"ValueSource.Resolve() hits its default switch branch when Type is neither \"file\" nor \"exec\". In practice this branch is nearly unreachable because Resolve() calls Validate() first, which rejects the same condition with a more descriptive message (error 260). Seeing this exact message usually means Validate() was bypassed or the struct was mutated between validation and resolution.","triggerScenarios":"Calling ValueSource.Resolve(ctx) with an unsupported Type while somehow skipping the upfront Validate() error — e.g. code that ignores the Validate() error inside Resolve, or a ValueSource mutated concurrently after validation.","commonSituations":"A concurrent goroutine rewrites v.Type between the Validate() call at the top of Resolve and the switch; custom code copies the struct and changes Type without revalidating.","solutions":["Fix the Type to \"file\" or \"exec\" as with error 260 — this is the same root cause.","If you mutate ValueSource fields, re-run Validate() after mutation and before Resolve().","Avoid sharing a ValueSource across goroutines with writes; construct a fresh one per resolution."],"exampleFix":"// before\nvs := &ValueSource{Type: \"vault\"}\nval, err := vs.Resolve(ctx)\n\n// after\nvs := &ValueSource{Type: \"exec\", Exec: &ExecSource{Command: \"vault\", Args: []string{\"kv\", \"get\", \"-field=tok\", \"frp\"}}}\nif err := vs.Validate(); err != nil { return err }\nval, err := vs.Resolve(ctx)","handlingStrategy":"validation","validationCode":"if err := vs.Validate(); err != nil {\n\treturn \"\", fmt.Errorf(\"value source invalid: %w\", err)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always call Validate() before Resolve() and surface its richer message.","Treat ValueSource as immutable after construction; do not mutate Type concurrently."],"tags":["config","validation","frp","value-source","unreachable-branch"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}