Skip to content

Commit dba604c

Browse files
Encode tag policy key as a single path segment in Get/Delete/UpdateTagPolicy
Governed tag keys may be hierarchical and contain "/" (e.g. "a/b"). The generated TagPoliciesAPI builds the request path as fmt.Sprintf("/api/2.1/tag-policies/%v", request.TagKey), so a raw "/" is treated as a path separator and the request resolves to no matching endpoint (404 / ENDPOINT_NOT_FOUND). Get, Delete, and Update are affected. Add hand-written overrides on TagPoliciesAPI that url.PathEscape the path-param TagKey before delegating to the generated impl; the server decodes it back to the original key. The encoded fields are `json:"-" url:"-"`, so only the path is affected. CreateTagPolicy is intentionally not overridden -- it sends the key in the JSON body, where "/" is valid and must be stored verbatim. Co-authored-by: Isaac Signed-off-by: Lizhen Xiang <lizhen.xiang@databricks.com>
1 parent 9be376d commit dba604c

3 files changed

Lines changed: 98 additions & 0 deletions

File tree

NEXT_CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
### Bug Fixes
1010

11+
* Encode the tag policy key as a single path segment in `GetTagPolicy`, `DeleteTagPolicy`, and `UpdateTagPolicy` ([tags.TagPoliciesAPI](https://pkg.go.dev/github.com/databricks/databricks-sdk-go/service/tags#TagPoliciesAPI)). Keys may be hierarchical and contain `/`; the raw value was previously treated as a path separator, so such requests resolved to no endpoint (`404` / `ENDPOINT_NOT_FOUND`).
12+
1113
### Documentation
1214

1315
### Internal Changes

service/tags/ext_impl.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package tags
2+
3+
import (
4+
"context"
5+
"net/url"
6+
)
7+
8+
// This file is hand-written (not generated). It extends the generated
9+
// TagPoliciesAPI to percent-encode the governed-tag key when it travels as a
10+
// URL *path* parameter.
11+
//
12+
// Subdomain tag keys are hierarchical and contain "/" (e.g.
13+
// "Field/Shared Technical Services"). The generated impl builds the path as
14+
// fmt.Sprintf("/api/2.1/tag-policies/%v", request.TagKey), so a raw "/" is
15+
// treated as a path separator and the request resolves to no endpoint (404).
16+
// Encoding the key ("/" -> %2F) makes it route as a single path segment; the
17+
// server decodes it back to the original key.
18+
//
19+
// These methods shadow the promoted (generated) tagPoliciesImpl methods on
20+
// TagPoliciesAPI. CreateTagPolicy is intentionally NOT overridden: it sends the
21+
// key in the JSON body (CreateTagPolicyRequest.TagPolicy.TagKey), where a raw
22+
// "/" is correct and must be stored verbatim. The TagKey fields encoded below
23+
// are `json:"-" url:"-"`, so they are used only in the path — never the body.
24+
25+
func (a *TagPoliciesAPI) GetTagPolicy(ctx context.Context, request GetTagPolicyRequest) (*TagPolicy, error) {
26+
request.TagKey = url.PathEscape(request.TagKey)
27+
return a.tagPoliciesImpl.GetTagPolicy(ctx, request)
28+
}
29+
30+
func (a *TagPoliciesAPI) DeleteTagPolicy(ctx context.Context, request DeleteTagPolicyRequest) error {
31+
request.TagKey = url.PathEscape(request.TagKey)
32+
return a.tagPoliciesImpl.DeleteTagPolicy(ctx, request)
33+
}
34+
35+
func (a *TagPoliciesAPI) UpdateTagPolicy(ctx context.Context, request UpdateTagPolicyRequest) (*TagPolicy, error) {
36+
request.TagKey = url.PathEscape(request.TagKey)
37+
return a.tagPoliciesImpl.UpdateTagPolicy(ctx, request)
38+
}

service/tags/ext_impl_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package tags
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/databricks/databricks-sdk-go/qa"
8+
)
9+
10+
// These tests verify that the governed-tag key is percent-encoded when it is
11+
// sent as a URL *path* parameter, so a hierarchical key containing "/" (e.g.
12+
// "Field/Shared Technical Services") routes as a single path segment rather
13+
// than being split into multiple segments (which resolves to no endpoint).
14+
// The qa fixture matches on the raw request URI, so a passing match proves the
15+
// "/" was encoded to %2F.
16+
17+
func TestGetTagPolicyEncodesPathKey(t *testing.T) {
18+
requestMocks := qa.HTTPFixtures{
19+
{
20+
Method: "GET",
21+
Resource: "/api/2.1/tag-policies/Field%2FShared%20Technical%20Services?",
22+
Response: TagPolicy{TagKey: "Field/Shared Technical Services"},
23+
},
24+
}
25+
client, server := requestMocks.Client(t)
26+
defer server.Close()
27+
api := &TagPoliciesAPI{tagPoliciesImpl: tagPoliciesImpl{client: client}}
28+
29+
resp, err := api.GetTagPolicy(context.Background(), GetTagPolicyRequest{
30+
TagKey: "Field/Shared Technical Services",
31+
})
32+
if err != nil {
33+
t.Fatalf("GetTagPolicy returned error: %v", err)
34+
}
35+
if resp.TagKey != "Field/Shared Technical Services" {
36+
t.Fatalf("unexpected tag_key in response: %q", resp.TagKey)
37+
}
38+
}
39+
40+
func TestDeleteTagPolicyEncodesPathKey(t *testing.T) {
41+
requestMocks := qa.HTTPFixtures{
42+
{
43+
Method: "DELETE",
44+
Resource: "/api/2.1/tag-policies/Field%2FShared%20Technical%20Services?",
45+
Response: map[string]any{},
46+
},
47+
}
48+
client, server := requestMocks.Client(t)
49+
defer server.Close()
50+
api := &TagPoliciesAPI{tagPoliciesImpl: tagPoliciesImpl{client: client}}
51+
52+
err := api.DeleteTagPolicy(context.Background(), DeleteTagPolicyRequest{
53+
TagKey: "Field/Shared Technical Services",
54+
})
55+
if err != nil {
56+
t.Fatalf("DeleteTagPolicy returned error: %v", err)
57+
}
58+
}

0 commit comments

Comments
 (0)