|
8 | 8 | import Foundation |
9 | 9 |
|
10 | 10 | public struct PartialUpdate: Equatable { |
| 11 | + |
| 12 | + typealias Storage = [Attribute: Action] |
11 | 13 |
|
12 | | - let attribute: Attribute |
13 | | - let content: PartialUpdate.Content |
| 14 | + let storage: Storage |
14 | 15 |
|
15 | 16 | } |
16 | 17 |
|
| 18 | +extension PartialUpdate: ExpressibleByDictionaryLiteral { |
| 19 | + |
| 20 | + public init(dictionaryLiteral elements: (Attribute, Action)...) { |
| 21 | + self.init(storage: .init(uniqueKeysWithValues: elements)) |
| 22 | + } |
| 23 | + |
| 24 | +} |
| 25 | + |
17 | 26 | public extension PartialUpdate { |
18 | 27 |
|
19 | 28 | /// Partially update an object field. |
20 | 29 | /// - Parameter attribute: Attribute name to update |
21 | 30 | /// - Parameter value: Updated value |
22 | 31 | static func update(attribute: Attribute, value: JSON) -> Self { |
23 | | - .init(attribute: attribute, content: .init(value: value, operation: nil)) |
| 32 | + .init(storage: [attribute: .set(value)]) |
24 | 33 | } |
25 | 34 |
|
26 | 35 | } |
@@ -180,105 +189,74 @@ public extension PartialUpdate { |
180 | 189 |
|
181 | 190 | extension PartialUpdate { |
182 | 191 |
|
183 | | - static func operation(attribute: Attribute, operation: PartialUpdate.Operation, value: JSON) -> Self { |
184 | | - .init(attribute: attribute, content: .init(value: value, operation: operation)) |
| 192 | + static func operation(attribute: Attribute, operation: Operation.Kind, value: JSON) -> Self { |
| 193 | + return .init(storage: [attribute: .init(operation: operation, value: value)]) |
185 | 194 | } |
186 | 195 |
|
187 | 196 | } |
188 | 197 |
|
189 | 198 | extension PartialUpdate: Codable { |
190 | 199 |
|
191 | 200 | public init(from decoder: Decoder) throws { |
192 | | - let container = try decoder.container(keyedBy: DynamicKey.self) |
193 | | - guard let rawAttribute = container.allKeys.first else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "No attribute found")) } |
194 | | - self.attribute = Attribute(rawValue: rawAttribute.stringValue) |
195 | | - self.content = try container.decode(PartialUpdate.Content.self, forKey: rawAttribute) |
| 201 | + let rawStorage = try [String: Action](from: decoder) |
| 202 | + storage = rawStorage.mapKeys(Attribute.init) |
196 | 203 | } |
197 | 204 |
|
198 | 205 | public func encode(to encoder: Encoder) throws { |
199 | | - var container = encoder.container(keyedBy: DynamicKey.self) |
200 | | - let key = DynamicKey(stringValue: attribute.rawValue) |
201 | | - try container.encode(content, forKey: key) |
| 206 | + try storage.mapKeys(\.rawValue).encode(to: encoder) |
202 | 207 | } |
203 | 208 |
|
204 | 209 | } |
205 | 210 |
|
206 | 211 | extension PartialUpdate { |
207 | | - |
208 | | - enum Operation: String, Codable { |
209 | | - /// Increment a numeric attribute |
210 | | - case increment = "Increment" |
211 | | - |
212 | | - /** |
213 | | - Increment a numeric integer attribute only if the provided value matches the current value, |
214 | | - and otherwise ignore the whole object update. |
215 | | - |
216 | | - For example, if you pass an IncrementFrom value of 2 for the version attribute, |
217 | | - but the current value of the attribute is 1, the engine ignores the update. |
218 | | - If the object doesn’t exist, the engine only creates it if you pass an IncrementFrom value of 0. |
219 | | - */ |
220 | | - case incrementFrom = "IncrementFrom" |
221 | | - |
222 | | - /** |
223 | | - Increment a numeric integer attribute only if the provided value is greater than the current value, |
224 | | - and otherwise ignore the whole object update. |
225 | | - |
226 | | - For example, if you pass an IncrementSet value of 2 for the version attribute, |
227 | | - and the current value of the attribute is 1, the engine updates the object. |
228 | | - If the object doesn’t exist yet, the engine only creates it if you pass an IncrementSet value that’s greater than 0. |
229 | | - */ |
230 | | - case incrementSet = "IncrementSet" |
231 | | - |
232 | | - /// Decrement a numeric attribute |
233 | | - case decrement = "Decrement" |
234 | | - |
235 | | - /// Append a number or string element to an array attribute |
236 | | - case add = "Add" |
237 | | - |
238 | | - /// Remove all matching number or string elements from an array attribute |
239 | | - case remove = "Remove" |
| 212 | + |
| 213 | + struct Operation: Codable, Equatable { |
240 | 214 |
|
241 | | - /// Add a number or string element to an array attribute only if it’s not already present |
242 | | - case addUnique = "AddUnique" |
243 | | - } |
244 | | - |
245 | | -} |
246 | | - |
247 | | -extension PartialUpdate { |
248 | | - |
249 | | - struct Content: Equatable { |
| 215 | + let kind: Kind |
250 | 216 | let value: JSON |
251 | | - let operation: Operation? |
252 | | - } |
253 | | - |
254 | | -} |
255 | | - |
256 | | -extension PartialUpdate.Content: Codable { |
257 | | - |
258 | | - enum CodingKeys: String, CodingKey { |
259 | | - case value |
260 | | - case operation = "_operation" |
261 | | - } |
262 | | - |
263 | | - public init(from decoder: Decoder) throws { |
264 | | - guard let container = try? decoder.container(keyedBy: CodingKeys.self), container.contains(.operation) else { |
265 | | - self.operation = nil |
266 | | - self.value = try JSON(from: decoder) |
267 | | - return |
| 217 | + |
| 218 | + enum CodingKeys: String, CodingKey { |
| 219 | + case value |
| 220 | + case kind = "_operation" |
268 | 221 | } |
269 | | - self.operation = try container.decode(forKey: .operation) |
270 | | - self.value = try container.decode(forKey: .value) |
271 | | - } |
272 | | - |
273 | | - public func encode(to encoder: Encoder) throws { |
274 | | - if let operation = operation { |
275 | | - var container = encoder.container(keyedBy: CodingKeys.self) |
276 | | - try container.encode(operation, forKey: .operation) |
277 | | - try container.encode(value, forKey: .value) |
278 | | - } else { |
279 | | - try value.encode(to: encoder) |
| 222 | + |
| 223 | + enum Kind: String, Codable { |
| 224 | + /// Increment a numeric attribute |
| 225 | + case increment = "Increment" |
| 226 | + |
| 227 | + /** |
| 228 | + Increment a numeric integer attribute only if the provided value matches the current value, |
| 229 | + and otherwise ignore the whole object update. |
| 230 | + |
| 231 | + For example, if you pass an IncrementFrom value of 2 for the version attribute, |
| 232 | + but the current value of the attribute is 1, the engine ignores the update. |
| 233 | + If the object doesn’t exist, the engine only creates it if you pass an IncrementFrom value of 0. |
| 234 | + */ |
| 235 | + case incrementFrom = "IncrementFrom" |
| 236 | + |
| 237 | + /** |
| 238 | + Increment a numeric integer attribute only if the provided value is greater than the current value, |
| 239 | + and otherwise ignore the whole object update. |
| 240 | + |
| 241 | + For example, if you pass an IncrementSet value of 2 for the version attribute, |
| 242 | + and the current value of the attribute is 1, the engine updates the object. |
| 243 | + If the object doesn’t exist yet, the engine only creates it if you pass an IncrementSet value that’s greater than 0. |
| 244 | + */ |
| 245 | + case incrementSet = "IncrementSet" |
| 246 | + |
| 247 | + /// Decrement a numeric attribute |
| 248 | + case decrement = "Decrement" |
| 249 | + |
| 250 | + /// Append a number or string element to an array attribute |
| 251 | + case add = "Add" |
| 252 | + |
| 253 | + /// Remove all matching number or string elements from an array attribute |
| 254 | + case remove = "Remove" |
| 255 | + |
| 256 | + /// Add a number or string element to an array attribute only if it’s not already present |
| 257 | + case addUnique = "AddUnique" |
280 | 258 | } |
281 | | - |
| 259 | + |
282 | 260 | } |
283 | 261 |
|
284 | 262 | } |
0 commit comments