Skip to content

Commit 46e4bf7

Browse files
authored
Revert: custom unmarshalling for SyftRPCMessage (#61)
* revert: add custom unmarshalling for SyftRPCMessage - Implemented unit tests for unmarshaling SyftRPCMessage, including handling base64 encoded bodies. - Verified correct parsing of fields such as sender, URL, and body content. - Added tests for both empty and complex body scenarios to ensure robust functionality. * tests: remove redudant test for rpc msg marshalling * test: test rpc msg unmarshalling to map interface.
1 parent 01ece50 commit 46e4bf7

File tree

2 files changed

+301
-0
lines changed

2 files changed

+301
-0
lines changed

internal/syftmsg/rpc_msg.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,40 @@ func (m *SyftRPCMessage) MarshalJSON() ([]byte, error) {
158158
})
159159
}
160160

161+
// UnmarshalJSON implements custom JSON unmarshaling to handle base64 encoded body
162+
func (m *SyftRPCMessage) UnmarshalJSON(data []byte) error {
163+
type Alias SyftRPCMessage
164+
aux := &struct {
165+
*Alias
166+
URL string `json:"url"`
167+
Body string `json:"body,omitempty"`
168+
}{
169+
Alias: (*Alias)(m),
170+
}
171+
172+
if err := json.Unmarshal(data, &aux); err != nil {
173+
return err
174+
}
175+
176+
// Parse the URL
177+
syftURL, err := utils.FromSyftURL(aux.URL)
178+
if err != nil {
179+
return fmt.Errorf("failed to parse URL: %w", err)
180+
}
181+
m.URL = *syftURL
182+
183+
// Decode the base64 body if present
184+
if aux.Body != "" {
185+
decodedBody, err := base64.URLEncoding.DecodeString(aux.Body)
186+
if err != nil {
187+
return fmt.Errorf("failed to decode base64 body: %w", err)
188+
}
189+
m.Body = decodedBody
190+
}
191+
192+
return nil
193+
}
194+
161195
// JSONString returns a properly formatted JSON string with decoded body
162196
func (m *SyftRPCMessage) ToJsonMap() map[string]interface{} {
163197
var bodyContent interface{}

internal/syftmsg/rpc_msg_test.go

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
package syftmsg
2+
3+
import (
4+
"encoding/base64"
5+
"encoding/json"
6+
"testing"
7+
"time"
8+
9+
"github.com/google/uuid"
10+
"github.com/openmined/syftbox/internal/utils"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestSyftRPCMessage_UnmarshalJSON(t *testing.T) {
15+
// Test data with base64 encoded body
16+
jsonData := `{
17+
"id": "0bd4fc1a-8e0e-4075-a82a-3ef114d9254f",
18+
"sender": "[email protected]",
19+
"url": "syft://[email protected]/app_data/mit/rpc/search",
20+
"body": "eyJpZCI6IjBhNWE5MThhLWUxYzctNDNlZi05NzgxLTI3ZTUyNzc5MzBkYiIsInF1ZXJ5IjoiV2hhdCBpcyBlbmNyeXB0ZWQgcHJvbXB0ICA_ICJ9",
21+
"headers": {
22+
"content-type": "application/json"
23+
},
24+
"created": "2025-07-29T11:53:51.103784+00:00",
25+
"expires": "2025-07-30T11:53:50.950340+00:00",
26+
"status_code": 200
27+
}`
28+
29+
var msg SyftRPCMessage
30+
err := json.Unmarshal([]byte(jsonData), &msg)
31+
assert.NoError(t, err)
32+
33+
// Verify the fields were parsed correctly
34+
assert.Equal(t, "[email protected]", msg.Sender)
35+
assert.Equal(t, 200, int(msg.StatusCode))
36+
37+
// Verify the URL was parsed correctly
38+
expectedURL, err := utils.FromSyftURL("syft://[email protected]/app_data/mit/rpc/search")
39+
assert.NoError(t, err)
40+
assert.Equal(t, expectedURL.Datasite, msg.URL.Datasite)
41+
assert.Equal(t, expectedURL.AppName, msg.URL.AppName)
42+
assert.Equal(t, expectedURL.Endpoint, msg.URL.Endpoint)
43+
44+
// Verify the body was decoded correctly
45+
// The base64 string "eyJpZCI6IjBhNWE5MThhLWUxYzctNDNlZi05NzgxLTI3ZTUyNzc5MzBkYiIsInF1ZXJ5IjoiV2hhdCBpcyBlbmNyeXB0ZWQgcHJvbXB0ICA_ICJ9"
46+
// decodes to: {"id":"0a5a918a-e1c7-43ef-9781-27e5277930db","query":"What is encrypted prompt ? "}
47+
assert.Contains(t, string(msg.Body), `"id":"0a5a918a-e1c7-43ef-9781-27e5277930db"`)
48+
assert.Contains(t, string(msg.Body), `"query":"What is encrypted prompt ? "`)
49+
}
50+
51+
func TestSyftRPCMessage_UnmarshalJSON_EmptyBody(t *testing.T) {
52+
// Test data without body
53+
jsonData := `{
54+
"id": "0bd4fc1a-8e0e-4075-a82a-3ef114d9254f",
55+
"sender": "[email protected]",
56+
"url": "syft://[email protected]/app_data/mit/rpc/search",
57+
"headers": {
58+
"content-type": "application/json"
59+
},
60+
"created": "2025-07-29T11:53:51.103784+00:00",
61+
"expires": "2025-07-30T11:53:50.950340+00:00",
62+
"status_code": 200
63+
}`
64+
65+
var msg SyftRPCMessage
66+
err := json.Unmarshal([]byte(jsonData), &msg)
67+
assert.NoError(t, err)
68+
69+
// Verify the body is empty
70+
assert.Empty(t, msg.Body)
71+
}
72+
73+
func TestSyftRPCMessage_MarshalUnmarshal_RoundTrip(t *testing.T) {
74+
// Create a test message with various data types
75+
originalID := uuid.New()
76+
originalSender := "[email protected]"
77+
originalURL, err := utils.FromSyftURL("syft://[email protected]/app_data/myapp/rpc/endpoint")
78+
assert.NoError(t, err)
79+
80+
originalBody := []byte(`{"key": "value", "number": 42, "array": [1, 2, 3]}`)
81+
originalHeaders := map[string]string{
82+
"content-type": "application/json",
83+
"authorization": "Bearer token123",
84+
}
85+
originalCreated := time.Now().UTC().Truncate(time.Microsecond)
86+
originalExpires := originalCreated.Add(24 * time.Hour)
87+
originalMethod := MethodPOST
88+
originalStatusCode := StatusOK
89+
90+
originalMsg := &SyftRPCMessage{
91+
ID: originalID,
92+
Sender: originalSender,
93+
URL: *originalURL,
94+
Body: originalBody,
95+
Headers: originalHeaders,
96+
Created: originalCreated,
97+
Expires: originalExpires,
98+
Method: originalMethod,
99+
StatusCode: originalStatusCode,
100+
}
101+
102+
// Marshal the message to JSON
103+
jsonData, err := json.Marshal(originalMsg)
104+
assert.NoError(t, err)
105+
assert.NotEmpty(t, jsonData)
106+
107+
// Verify the marshaled JSON contains base64 encoded body
108+
var jsonMap map[string]interface{}
109+
err = json.Unmarshal(jsonData, &jsonMap)
110+
assert.NoError(t, err)
111+
112+
// Check that body is base64 encoded
113+
bodyStr, ok := jsonMap["body"].(string)
114+
assert.True(t, ok)
115+
assert.NotEmpty(t, bodyStr)
116+
117+
// Verify it's valid base64
118+
decodedBody, err := base64.URLEncoding.DecodeString(bodyStr)
119+
assert.NoError(t, err)
120+
assert.Equal(t, originalBody, decodedBody)
121+
122+
// Unmarshal the JSON back to a message
123+
var unmarshaledMsg SyftRPCMessage
124+
err = json.Unmarshal(jsonData, &unmarshaledMsg)
125+
assert.NoError(t, err)
126+
127+
// Verify all fields match the original
128+
assert.Equal(t, originalID, unmarshaledMsg.ID)
129+
assert.Equal(t, originalSender, unmarshaledMsg.Sender)
130+
assert.Equal(t, originalURL.Datasite, unmarshaledMsg.URL.Datasite)
131+
assert.Equal(t, originalURL.AppName, unmarshaledMsg.URL.AppName)
132+
assert.Equal(t, originalURL.Endpoint, unmarshaledMsg.URL.Endpoint)
133+
assert.Equal(t, originalBody, unmarshaledMsg.Body)
134+
assert.Equal(t, originalHeaders, unmarshaledMsg.Headers)
135+
assert.Equal(t, originalCreated, unmarshaledMsg.Created)
136+
assert.Equal(t, originalExpires, unmarshaledMsg.Expires)
137+
assert.Equal(t, originalMethod, unmarshaledMsg.Method)
138+
assert.Equal(t, originalStatusCode, unmarshaledMsg.StatusCode)
139+
}
140+
141+
func TestSyftRPCMessage_MarshalUnmarshal_EmptyBody(t *testing.T) {
142+
// Create a test message with empty body
143+
originalID := uuid.New()
144+
originalSender := "[email protected]"
145+
originalURL, err := utils.FromSyftURL("syft://[email protected]/app_data/myapp/rpc/endpoint")
146+
assert.NoError(t, err)
147+
148+
originalHeaders := map[string]string{
149+
"content-type": "application/json",
150+
}
151+
originalCreated := time.Now().UTC().Truncate(time.Microsecond)
152+
originalExpires := originalCreated.Add(24 * time.Hour)
153+
154+
originalMsg := &SyftRPCMessage{
155+
ID: originalID,
156+
Sender: originalSender,
157+
URL: *originalURL,
158+
Body: nil, // Empty body
159+
Headers: originalHeaders,
160+
Created: originalCreated,
161+
Expires: originalExpires,
162+
}
163+
164+
// Marshal the message to JSON
165+
jsonData, err := json.Marshal(originalMsg)
166+
assert.NoError(t, err)
167+
assert.NotEmpty(t, jsonData)
168+
169+
// Unmarshal the JSON back to a message
170+
var unmarshaledMsg SyftRPCMessage
171+
err = json.Unmarshal(jsonData, &unmarshaledMsg)
172+
assert.NoError(t, err)
173+
174+
// Verify all fields match the original
175+
assert.Equal(t, originalID, unmarshaledMsg.ID)
176+
assert.Equal(t, originalSender, unmarshaledMsg.Sender)
177+
assert.Equal(t, originalURL.Datasite, unmarshaledMsg.URL.Datasite)
178+
assert.Equal(t, originalURL.AppName, unmarshaledMsg.URL.AppName)
179+
assert.Equal(t, originalURL.Endpoint, unmarshaledMsg.URL.Endpoint)
180+
assert.Empty(t, unmarshaledMsg.Body) // Body should remain empty
181+
assert.Equal(t, originalHeaders, unmarshaledMsg.Headers)
182+
assert.Equal(t, originalCreated, unmarshaledMsg.Created)
183+
assert.Equal(t, originalExpires, unmarshaledMsg.Expires)
184+
}
185+
186+
func TestSyftRPCMessage_MarshalUnmarshal_ComplexBody(t *testing.T) {
187+
// Create a test message with complex JSON body
188+
originalID := uuid.New()
189+
originalSender := "[email protected]"
190+
originalURL, err := utils.FromSyftURL("syft://[email protected]/app_data/myapp/rpc/search")
191+
assert.NoError(t, err)
192+
193+
// Complex JSON body with nested structures
194+
originalBody := []byte(`{
195+
"query": "What is encrypted prompt?",
196+
"results": [
197+
{
198+
"id": "0a5a918a-e1c7-43ef-9781-27e5277930db",
199+
"score": 0.46056801080703735,
200+
"content": "## LLMs Can Understand Encrypted Prompt: Towards Privacy-Computing Friendly Transformers",
201+
"metadata": {
202+
"filename": "LLMs Can Understand Encrypted Prompt.pdf"
203+
}
204+
}
205+
],
206+
"providerInfo": {
207+
"provider": "local_rag"
208+
},
209+
"cost": 0.1
210+
}`)
211+
212+
originalHeaders := map[string]string{
213+
"content-type": "application/json",
214+
"content-length": "2075",
215+
}
216+
originalCreated := time.Now().UTC().Truncate(time.Microsecond)
217+
originalExpires := originalCreated.Add(24 * time.Hour)
218+
originalStatusCode := StatusOK
219+
220+
originalMsg := &SyftRPCMessage{
221+
ID: originalID,
222+
Sender: originalSender,
223+
URL: *originalURL,
224+
Body: originalBody,
225+
Headers: originalHeaders,
226+
Created: originalCreated,
227+
Expires: originalExpires,
228+
StatusCode: originalStatusCode,
229+
}
230+
231+
base64encodedBody := base64.URLEncoding.EncodeToString(originalBody)
232+
233+
// Marshal the message to JSON
234+
jsonData, err := json.Marshal(originalMsg)
235+
assert.NoError(t, err)
236+
assert.NotEmpty(t, jsonData)
237+
238+
// Unmarshal to a map
239+
var jsonMap map[string]interface{}
240+
err = json.Unmarshal(jsonData, &jsonMap)
241+
assert.NoError(t, err)
242+
assert.NotEmpty(t, jsonMap)
243+
assert.Equal(t, jsonMap["body"], base64encodedBody)
244+
245+
// Unmarshal the JSON back to a message
246+
var unmarshaledMsg SyftRPCMessage
247+
err = json.Unmarshal(jsonData, &unmarshaledMsg)
248+
assert.NoError(t, err)
249+
250+
// Verify all fields match the original
251+
assert.Equal(t, originalID, unmarshaledMsg.ID)
252+
assert.Equal(t, originalSender, unmarshaledMsg.Sender)
253+
assert.Equal(t, originalURL.Datasite, unmarshaledMsg.URL.Datasite)
254+
assert.Equal(t, originalURL.AppName, unmarshaledMsg.URL.AppName)
255+
assert.Equal(t, originalURL.Endpoint, unmarshaledMsg.URL.Endpoint)
256+
assert.Equal(t, originalBody, unmarshaledMsg.Body)
257+
assert.Equal(t, originalHeaders, unmarshaledMsg.Headers)
258+
assert.Equal(t, originalCreated, unmarshaledMsg.Created)
259+
assert.Equal(t, originalExpires, unmarshaledMsg.Expires)
260+
assert.Equal(t, originalStatusCode, unmarshaledMsg.StatusCode)
261+
262+
// Verify the body content is preserved correctly
263+
assert.Contains(t, string(unmarshaledMsg.Body), `"query"`)
264+
assert.Contains(t, string(unmarshaledMsg.Body), `"What is encrypted prompt?"`)
265+
assert.Contains(t, string(unmarshaledMsg.Body), `"0a5a918a-e1c7-43ef-9781-27e5277930db"`)
266+
assert.Contains(t, string(unmarshaledMsg.Body), `"local_rag"`)
267+
}

0 commit comments

Comments
 (0)