Skip to content

Commit 689f9db

Browse files
[azservicebus] Ignore the leading XMLNS for properties in subscription rules (Azure#18813)
The individual properties for a rule in Service Bus come with an XML ns prefix. We don't actually care about this as we're just trying to figure out what the assigned type is (string, int, etc..) so this just strips out the prefix. Fixes Azure#18785
1 parent e17eb3d commit 689f9db

File tree

16 files changed

+425
-114
lines changed

16 files changed

+425
-114
lines changed

sdk/messaging/azservicebus/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
not being delivered, but still incrementing their delivery count and requiring the message lock
2020
timeout to expire.
2121
- Link detach could result in messages being ignored, requiring the message lock timeout to expire.
22+
- Subscription rules weren't deserializing properly when created from the portal (PR#18813)
2223

2324
## 1.0.2-beta.0 (2022-07-07)
2425

sdk/messaging/azservicebus/admin/admin_client.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ type ClientOptions struct {
3434
// NewClientFromConnectionString creates a Client authenticating using a connection string.
3535
// connectionString can be a Service Bus connection string for the namespace or for an entity, which contains a
3636
// SharedAccessKeyName and SharedAccessKey properties (for instance, from the Azure Portal):
37-
// Endpoint=sb://<sb>.servicebus.windows.net/;SharedAccessKeyName=<key name>;SharedAccessKey=<key value>
37+
//
38+
// Endpoint=sb://<sb>.servicebus.windows.net/;SharedAccessKeyName=<key name>;SharedAccessKey=<key value>
39+
//
3840
// Or it can be a connection string with a SharedAccessSignature:
39-
// Endpoint=sb://<sb>.servicebus.windows.net;SharedAccessSignature=SharedAccessSignature sr=<sb>.servicebus.windows.net&sig=<base64-sig>&se=<expiry>&skn=<keyname>
41+
//
42+
// Endpoint=sb://<sb>.servicebus.windows.net;SharedAccessSignature=SharedAccessSignature sr=<sb>.servicebus.windows.net&sig=<base64-sig>&se=<expiry>&skn=<keyname>
4043
func NewClientFromConnectionString(connectionString string, options *ClientOptions) (*Client, error) {
4144
var clientOptions *azcore.ClientOptions
4245

sdk/messaging/azservicebus/admin/admin_client_rules.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"fmt"
1010
"net/http"
1111
"strconv"
12+
"strings"
1213
"time"
1314

1415
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
@@ -578,34 +579,43 @@ func internalSQLParametersToPublic(kvlist *atom.KeyValueList) (map[string]interf
578579
params := map[string]interface{}{}
579580

580581
for _, p := range kvlist.KeyValues {
581-
switch p.Value.Type {
582-
case "d6p1:string":
582+
// we only care about the actual type here since we can assume the
583+
// service is able to properly format/namespace its own XML
584+
valueType := p.Value.Type
585+
typeParts := strings.Split(p.Value.Type, ":")
586+
587+
if len(typeParts) == 2 {
588+
valueType = typeParts[1]
589+
}
590+
591+
switch valueType {
592+
case "string":
583593
params[p.Key] = p.Value.Text
584-
case "d6p1:boolean":
594+
case "boolean":
585595
val, err := strconv.ParseBool(p.Value.Text)
586596

587597
if err != nil {
588598
return nil, err
589599
}
590600

591601
params[p.Key] = val
592-
case "d6p1:int":
602+
case "int":
593603
val, err := strconv.ParseInt(p.Value.Text, 10, 64)
594604

595605
if err != nil {
596606
return nil, err
597607
}
598608

599609
params[p.Key] = val
600-
case "d6p1:double":
610+
case "double":
601611
val, err := strconv.ParseFloat(p.Value.Text, 64)
602612

603613
if err != nil {
604614
return nil, err
605615
}
606616

607617
params[p.Key] = val
608-
case "d6p1:dateTime":
618+
case "dateTime":
609619
val, err := time.Parse(time.RFC3339Nano, p.Value.Text)
610620

611621
if err != nil {
@@ -615,7 +625,7 @@ func internalSQLParametersToPublic(kvlist *atom.KeyValueList) (map[string]interf
615625
params[p.Key] = val.UTC()
616626
default:
617627
// TODO: timespan
618-
return nil, fmt.Errorf("type %s of parameter %s is not a handled type for SQL parameters", p.Value.Type, p.Key)
628+
return nil, fmt.Errorf("type %s of parameter %s is not a handled type for SQL parameters", valueType, p.Key)
619629
}
620630
}
621631

sdk/messaging/azservicebus/admin/admin_client_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import (
88
"context"
99
cryptoRand "crypto/rand"
1010
"encoding/hex"
11+
"encoding/json"
1112
"encoding/xml"
1213
"fmt"
14+
"io"
1315
"net/http"
1416
"os"
1517
"sort"
@@ -1078,6 +1080,80 @@ func TestAdminClient_LackPermissions_Subscription(t *testing.T) {
10781080
require.Contains(t, err.Error(), "401 SubCode=40100: Unauthorized : Unauthorized access for 'DeleteSubscription'")
10791081
}
10801082

1083+
type fakeEM struct {
1084+
atom.EntityManager
1085+
1086+
getResponses []string
1087+
}
1088+
1089+
func (em *fakeEM) Get(ctx context.Context, entityPath string, respObj interface{}) (*http.Response, error) {
1090+
jsonPath := em.getResponses[0]
1091+
em.getResponses = em.getResponses[1:]
1092+
1093+
reader, err := os.Open(jsonPath)
1094+
1095+
if err != nil {
1096+
return nil, err
1097+
}
1098+
1099+
defer reader.Close()
1100+
1101+
bytes, err := io.ReadAll(reader)
1102+
1103+
if err != nil {
1104+
return nil, err
1105+
}
1106+
1107+
if err := json.Unmarshal(bytes, respObj); err != nil {
1108+
return nil, err
1109+
}
1110+
1111+
return &http.Response{
1112+
Body: http.NoBody,
1113+
}, nil
1114+
}
1115+
1116+
func TestAdminClient_ruleWithDifferentXMLNamespaces(t *testing.T) {
1117+
adminClient := &Client{
1118+
em: &fakeEM{
1119+
getResponses: []string{
1120+
"testdata/rulefeed.json",
1121+
"testdata/emptyrulefeed.json",
1122+
},
1123+
},
1124+
}
1125+
1126+
// we're stubbed out at this point, so no need to use "real" entities
1127+
pager := adminClient.NewListRulesPager("test", "test", nil)
1128+
1129+
require.True(t, pager.More())
1130+
listRulesResp, err := pager.NextPage(context.Background())
1131+
require.NoError(t, err)
1132+
require.NotNil(t, listRulesResp)
1133+
1134+
expectedTime, err := time.Parse(time.RFC3339, "2020-01-01T01:02:03Z")
1135+
require.NoError(t, err)
1136+
1137+
correlationFilter := listRulesResp.Rules[0].Filter.(*CorrelationFilter)
1138+
require.Equal(t, map[string]any{
1139+
"hello": "world",
1140+
"hellodatetime": expectedTime,
1141+
"hellodouble": 1.1,
1142+
"helloint": int64(101),
1143+
}, correlationFilter.ApplicationProperties)
1144+
require.Equal(t, "11", *correlationFilter.CorrelationID)
1145+
1146+
sqlFilter := listRulesResp.Rules[1].Filter.(*SQLFilter)
1147+
require.Equal(t, "1=1", sqlFilter.Expression)
1148+
1149+
require.True(t, pager.More())
1150+
listRulesResp, err = pager.NextPage(context.Background())
1151+
require.NoError(t, err)
1152+
require.Empty(t, listRulesResp.Rules)
1153+
1154+
require.False(t, pager.More())
1155+
}
1156+
10811157
func TestAdminClient_CreateRules(t *testing.T) {
10821158
adminClient, topicName := createTestSub(t)
10831159
defer func() {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"XMLName": {
3+
"Space": "",
4+
"Local": ""
5+
},
6+
"ID": "https://fakeservicebus/csharp/Subscriptions/subwithfilter/Rules/?%24skip=2\u0026api-version=2021-05",
7+
"Title": "Rules",
8+
"Entries": null
9+
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
2+
{
3+
"XMLName": {
4+
"Space": "",
5+
"Local": ""
6+
},
7+
"ID": "https://fakeservicebus/csharp/Subscriptions/subwithfilter/Rules/?api-version=2021-05",
8+
"Title": "Rules",
9+
"Entries": [
10+
{
11+
"XMLName": {
12+
"Space": "",
13+
"Local": ""
14+
},
15+
"ID": "https://fakeservicebus/csharp/Subscriptions/subwithfilter/Rules/CorrelationFilter1?api-version=2021-05",
16+
"Title": "CorrelationFilter1",
17+
"Author": null,
18+
"Link": {
19+
"XMLName": {
20+
"Space": "http://www.w3.org/2005/Atom",
21+
"Local": "link"
22+
},
23+
"Rel": "self",
24+
"HREF": "CorrelationFilter1?api-version=2021-05"
25+
},
26+
"DataServiceSchema": "",
27+
"DataServiceMetadataSchema": "",
28+
"AtomSchema": "",
29+
"Content": {
30+
"XMLName": {
31+
"Space": "http://www.w3.org/2005/Atom",
32+
"Local": "content"
33+
},
34+
"Type": "application/xml",
35+
"RuleDescription": {
36+
"XMLName": {
37+
"Space": "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect",
38+
"Local": "RuleDescription"
39+
},
40+
"XMLNS": "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect",
41+
"XMLNSI": "",
42+
"InstanceMetadataSchema": null,
43+
"ServiceBusSchema": null,
44+
"CreatedAt": "2022-08-08T22:23:20.3432504Z",
45+
"Filter": {
46+
"XMLName": {
47+
"Space": "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect",
48+
"Local": "Filter"
49+
},
50+
"RawXML": "PENvcnJlbGF0aW9uSWQ+MTE8L0NvcnJlbGF0aW9uSWQ+PFByb3BlcnRpZXM+PEtleVZhbHVlT2ZzdHJpbmdhbnlUeXBlPjxLZXk+aGVsbG88L0tleT48VmFsdWUgaTp0eXBlPSJkNXAxOnN0cmluZyIgeG1sbnM6ZDVwMT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiPndvcmxkPC9WYWx1ZT48L0tleVZhbHVlT2ZzdHJpbmdhbnlUeXBlPjwvUHJvcGVydGllcz4=",
51+
"RawAttrs": null,
52+
"CorrelationID": "11",
53+
"MessageID": null,
54+
"To": null,
55+
"ReplyTo": null,
56+
"Label": null,
57+
"SessionID": null,
58+
"ReplyToSessionID": null,
59+
"ContentType": null,
60+
"Properties": {
61+
"KeyValues": [
62+
{
63+
"Key": "hello",
64+
"Value": {
65+
"Type": "d5p1:string",
66+
"L28NS": "",
67+
"Text": "world"
68+
}
69+
},
70+
{
71+
"Key": "helloint",
72+
"Value": {
73+
"Type": "doesnotmatter:int",
74+
"L28NS": "",
75+
"Text": "101"
76+
}
77+
},
78+
{
79+
"Key": "hellodouble",
80+
"Value": {
81+
"Type": "d5p1:double",
82+
"L28NS": "",
83+
"Text": "1.1"
84+
}
85+
},
86+
{
87+
"Key": "hellodatetime",
88+
"Value": {
89+
"Type": "d6p1:dateTime",
90+
"L28NS": "",
91+
"Text": "2020-01-01T01:02:03Z"
92+
}
93+
}
94+
]
95+
},
96+
"Type": "CorrelationFilter",
97+
"SQLExpression": null,
98+
"CompatibilityLevel": 0,
99+
"Parameters": null
100+
},
101+
"Action": {
102+
"Type": "EmptyRuleAction",
103+
"SQLExpression": "",
104+
"Parameters": null,
105+
"RawXML": null,
106+
"RawAttrs": null
107+
},
108+
"Name": "CorrelationFilter1"
109+
}
110+
}
111+
},
112+
{
113+
"XMLName": {
114+
"Space": "",
115+
"Local": ""
116+
},
117+
"ID": "https://fakeservicebus/csharp/Subscriptions/subwithfilter/Rules/SQLFilter1?api-version=2021-05",
118+
"Title": "SQLFilter1",
119+
"Author": null,
120+
"Link": {
121+
"XMLName": {
122+
"Space": "http://www.w3.org/2005/Atom",
123+
"Local": "link"
124+
},
125+
"Rel": "self",
126+
"HREF": "SQLFilter1?api-version=2021-05"
127+
},
128+
"DataServiceSchema": "",
129+
"DataServiceMetadataSchema": "",
130+
"AtomSchema": "",
131+
"Content": {
132+
"XMLName": {
133+
"Space": "http://www.w3.org/2005/Atom",
134+
"Local": "content"
135+
},
136+
"Type": "application/xml",
137+
"RuleDescription": {
138+
"XMLName": {
139+
"Space": "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect",
140+
"Local": "RuleDescription"
141+
},
142+
"XMLNS": "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect",
143+
"XMLNSI": "",
144+
"InstanceMetadataSchema": null,
145+
"ServiceBusSchema": null,
146+
"CreatedAt": "2022-08-08T22:47:21.5507406Z",
147+
"Filter": {
148+
"XMLName": {
149+
"Space": "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect",
150+
"Local": "Filter"
151+
},
152+
"RawXML": "PFNxbEV4cHJlc3Npb24+MT0xPC9TcWxFeHByZXNzaW9uPjxDb21wYXRpYmlsaXR5TGV2ZWw+MjA8L0NvbXBhdGliaWxpdHlMZXZlbD48UGFyYW1ldGVycy8+",
153+
"RawAttrs": null,
154+
"CorrelationID": null,
155+
"MessageID": null,
156+
"To": null,
157+
"ReplyTo": null,
158+
"Label": null,
159+
"SessionID": null,
160+
"ReplyToSessionID": null,
161+
"ContentType": null,
162+
"Properties": null,
163+
"Type": "SqlFilter",
164+
"SQLExpression": "1=1",
165+
"CompatibilityLevel": 20,
166+
"Parameters": {
167+
"KeyValues": null
168+
}
169+
},
170+
"Action": {
171+
"Type": "EmptyRuleAction",
172+
"SQLExpression": "",
173+
"Parameters": null,
174+
"RawXML": null,
175+
"RawAttrs": null
176+
},
177+
"Name": "SQLFilter1"
178+
}
179+
}
180+
}
181+
]
182+
}

sdk/messaging/azservicebus/amqp_message.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import (
1111

1212
// AMQPAnnotatedMessage represents the AMQP message, as received from Service Bus.
1313
// For details about these properties, refer to the AMQP specification:
14-
// https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#section-message-format
14+
//
15+
// https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#section-message-format
1516
//
1617
// Some fields in this struct are typed 'any', which means they will accept AMQP primitives, or in some
1718
// cases slices and maps.

0 commit comments

Comments
 (0)