{"record":{"id":"aa57d95eca46a5a5","repo":"Tencent/WeKnora","slug":"resource-binding-requires-owner-type-and-id","errorCode":null,"errorMessage":"resource binding requires owner type and id","messagePattern":"resource binding requires owner type and id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/application/service/resource.go","lineNumber":144,"sourceCode":"\nfunc (s *resourceCatalog) ResolvePath(ctx context.Context, value string) (string, *types.StoredResource, error) {\n\tif _, ok := types.ParseResourcePath(value); !ok {\n\t\treturn value, nil, nil\n\t}\n\tresource, err := s.Resolve(ctx, value)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\treturn resource.PhysicalPath, resource, nil\n}\n\nfunc (s *resourceCatalog) Bind(ctx context.Context, reference, ownerType, ownerID, relation string) error {\n\tresource, err := s.Resolve(ctx, reference)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif strings.TrimSpace(ownerType) == \"\" || strings.TrimSpace(ownerID) == \"\" {\n\t\treturn fmt.Errorf(\"resource binding requires owner type and id\")\n\t}\n\tif relation == \"\" {\n\t\trelation = \"attachment\"\n\t}\n\treturn s.repo.CreateBinding(ctx, &types.ResourceBinding{\n\t\tResourceID: resource.ID,\n\t\tTenantID:   resource.TenantID,\n\t\tOwnerType:  ownerType,\n\t\tOwnerID:    ownerID,\n\t\tRelation:   relation,\n\t})\n}\n\n// Release implements interfaces.ResourceCatalog.\n//\n// Unbinding and counting are deliberately not a single transaction. A racing\n// bind that lands between them makes the count too high, which keeps a live\n// file — the safe direction. The opposite ordering could delete bytes another","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/service/resource.go#L126-L162","documentation":"Bind refuses to create a resource binding when ownerType or ownerID is blank. Every binding must record which entity owns the resource and how; without both, the binding row would be unattributable and unusable for later Release/ownership checks. Thrown after the reference itself resolves successfully.","triggerScenarios":"Calling Bind(ctx, ref, \"\", \"\", rel) or passing whitespace-only owner fields, often from unmarshaled config or optional struct fields left unset.","commonSituations":"Zero-value struct fields when the owner object failed to hydrate; empty string from an optional request field; forgetting to set owner fields when binding service-owned resources.","solutions":["Populate ownerType/ownerID from the authenticated principal or owning entity before calling Bind","Validate both fields with strings.TrimSpace before the call","Return a 400-style error upstream when owner metadata is missing from the request","Default the owner to a system principal if the resource is genuinely service-owned"],"exampleFix":"// before\ncatalog.Bind(ctx, ref, cfg.OwnerType, cfg.OwnerID, \"attachment\")\n// after\nif strings.TrimSpace(cfg.OwnerType) == \"\" || strings.TrimSpace(cfg.OwnerID) == \"\" {\n    return fmt.Errorf(\"owner metadata required\")\n}\ncatalog.Bind(ctx, ref, cfg.OwnerType, cfg.OwnerID, \"attachment\")","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(ownerType) == \"\" || strings.TrimSpace(ownerID) == \"\" {\n    return errors.New(\"owner type and id are required\")\n}","typeGuard":"func hasOwner(o Owner) bool { return strings.TrimSpace(o.Type) != \"\" && strings.TrimSpace(o.ID) != \"\" }","tryCatchPattern":"err := catalog.Bind(ctx, ref, ownerType, ownerID, rel)\nif err != nil && strings.Contains(err.Error(), \"requires owner type and id\") {\n    return ErrMissingOwner\n}","preventionTips":["Validate owner fields at the request-decoding layer","Make ownerType/ownerID required params in your service's own API","Use non-zero defaults for service-owned resources","Unit test Bind with zero-value owner structs"],"tags":["input-validation","binding"],"backgroundTag":"missing-required-argument","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}