16
16
from synapse .api .constants import EventTypes
17
17
from . import EventBase
18
18
19
+ import re
20
+
21
+ # Split strings on "." but not "\." This uses a negative lookbehind assertion for '\'
22
+ # (?<!stuff) matches if the current position in the string is not preceded
23
+ # by a match for 'stuff'.
24
+ # TODO: This is fast, but fails to handle "foo\\.bar" which should be treated as
25
+ # the literal fields "foo\" and "bar" but will instead be treated as "foo\\.bar"
26
+ SPLIT_FIELD_REGEX = re .compile (r'(?<!\\)\.' )
27
+
19
28
20
29
def prune_event (event ):
21
30
""" Returns a pruned version of the given event, which removes all keys we
@@ -97,6 +106,87 @@ def add_fields(*fields):
97
106
)
98
107
99
108
109
+ def _copy_field (src , dst , field ):
110
+ """Copy the field in 'src' to 'dst'.
111
+
112
+ For example, if src={"foo":{"bar":5}} and dst={}, and field=["foo","bar"]
113
+ then dst={"foo":{"bar":5}}.
114
+
115
+ Args:
116
+ src(dict): The dict to read from.
117
+ dst(dict): The dict to modify.
118
+ field(list<str>): List of keys to drill down to in 'src'.
119
+ """
120
+ if len (field ) == 0 : # this should be impossible
121
+ return
122
+ if len (field ) == 1 : # common case e.g. 'origin_server_ts'
123
+ if field [0 ] in src :
124
+ dst [field [0 ]] = src [field [0 ]]
125
+ return
126
+
127
+ # Else is a nested field e.g. 'content.body'
128
+ # Pop the last field as that's the key to move across and we need the
129
+ # parent dict in order to access the data. Drill down to the right dict.
130
+ key_to_move = field .pop (- 1 )
131
+ sub_dict = src
132
+ for sub_field in field : # e.g. sub_field => "content"
133
+ if sub_field in sub_dict and type (sub_dict [sub_field ]) == dict :
134
+ sub_dict = sub_dict [sub_field ]
135
+ else :
136
+ return
137
+
138
+ if key_to_move not in sub_dict :
139
+ return
140
+
141
+ # Insert the key into the output dictionary, creating nested objects
142
+ # as required. We couldn't do this any earlier or else we'd need to delete
143
+ # the empty objects if the key didn't exist.
144
+ sub_out_dict = dst
145
+ for sub_field in field :
146
+ if sub_field not in sub_out_dict :
147
+ sub_out_dict [sub_field ] = {}
148
+ sub_out_dict = sub_out_dict [sub_field ]
149
+ sub_out_dict [key_to_move ] = sub_dict [key_to_move ]
150
+
151
+
152
+ def only_fields (dictionary , fields ):
153
+ """Return a new dict with only the fields in 'dictionary' which are present
154
+ in 'fields'.
155
+
156
+ If there are no event fields specified then all fields are included.
157
+ The entries may include '.' charaters to indicate sub-fields.
158
+ So ['content.body'] will include the 'body' field of the 'content' object.
159
+ A literal '.' character in a field name may be escaped using a '\' .
160
+
161
+ Args:
162
+ dictionary(dict): The dictionary to read from.
163
+ fields(list<str>): A list of fields to copy over. Only shallow refs are
164
+ taken.
165
+ Returns:
166
+ dict: A new dictionary with only the given fields. If fields was empty,
167
+ the same dictionary is returned.
168
+ """
169
+ if len (fields ) == 0 :
170
+ return dictionary
171
+
172
+ # for each field, convert it:
173
+ # ["content.body.thing\.with\.dots"] => [["content", "body", "thing\.with\.dots"]]
174
+ split_fields = [SPLIT_FIELD_REGEX .split (f ) for f in fields ]
175
+
176
+ # for each element of the output array of arrays:
177
+ # remove escaping so we can use the right key names. This purposefully avoids
178
+ # using list comprehensions to avoid needless allocations as this may be called
179
+ # on a lot of events.
180
+ for field_array in split_fields :
181
+ for i , field in enumerate (field_array ):
182
+ field_array [i ] = field .replace (r'\.' , r'.' )
183
+
184
+ output = {}
185
+ for field_array in split_fields :
186
+ _copy_field (dictionary , output , field_array )
187
+ return output
188
+
189
+
100
190
def format_event_raw (d ):
101
191
return d
102
192
@@ -137,7 +227,7 @@ def format_event_for_client_v2_without_room_id(d):
137
227
138
228
def serialize_event (e , time_now_ms , as_client_event = True ,
139
229
event_format = format_event_for_client_v1 ,
140
- token_id = None ):
230
+ token_id = None , event_fields = None ):
141
231
# FIXME(erikj): To handle the case of presence events and the like
142
232
if not isinstance (e , EventBase ):
143
233
return e
@@ -164,6 +254,9 @@ def serialize_event(e, time_now_ms, as_client_event=True,
164
254
d ["unsigned" ]["transaction_id" ] = txn_id
165
255
166
256
if as_client_event :
167
- return event_format (d )
168
- else :
169
- return d
257
+ d = event_format (d )
258
+
259
+ if isinstance (event_fields , list ):
260
+ d = only_fields (d , event_fields )
261
+
262
+ return d
0 commit comments