Skip to content

Commit fbb50ba

Browse files
authored
feat: added IgnoreEncoding(basedOn:) macro to ignore encoding based on other properties (#139)
1 parent b468de5 commit fbb50ba

File tree

7 files changed

+331
-20
lines changed

7 files changed

+331
-20
lines changed

Sources/MetaCodable/IgnoreCoding.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,32 @@ public macro IgnoreEncoding<each T>(if condition: (repeat each T) -> Bool) =
176176
@available(swift 5.9)
177177
public macro IgnoreEncoding<T>(if condition: (T) -> Bool) =
178178
#externalMacro(module: "MacroPlugin", type: "IgnoreEncoding")
179+
180+
/// Indicates the field needs to be encoded only if provided condition
181+
/// is not satisfied, based on the containing object.
182+
///
183+
/// This macro evaluates the condition using the containing object itself,
184+
/// rather than just the property value. This is useful when the encoding
185+
/// decision depends on other properties or the overall state of the object.
186+
/// ```swift
187+
/// let shouldNotEncodeField: Bool
188+
/// @IgnoreEncoding(basedOn: \Self.shouldNotEncodeField)
189+
/// let field: String
190+
/// ```
191+
///
192+
/// The decoding data needs to have applicable data in `field` key.
193+
/// But the encoded data might not have any `field` key depending on
194+
/// the value of `shouldNotEncodeField` property of the containing object.
195+
///
196+
/// - Parameter condition: The condition to be checked using the containing object.
197+
///
198+
/// - Note: This macro on its own only validates if attached declaration
199+
/// is a variable declaration. ``Codable(commonStrategies:)`` macro uses this macro
200+
/// when generating final implementations.
201+
///
202+
/// - Important: The condition takes the containing object as its parameter
203+
/// which must conform to `Encodable`.
204+
@attached(peer)
205+
@available(swift 5.9)
206+
public macro IgnoreEncoding<T>(basedOn condition: (T) -> Bool) =
207+
#externalMacro(module: "MacroPlugin", type: "IgnoreEncoding")

Sources/MetaCodable/MetaCodable.docc/MetaCodable.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ Supercharge `Swift`'s `Codable` implementations with macros.
112112
- ``IgnoreEncoding()``
113113
- ``IgnoreEncoding(if:)-1iuvv``
114114
- ``IgnoreEncoding(if:)-7toka``
115+
- ``IgnoreEncoding(basedOn:)``
115116
- ``IgnoreCodingInitialized()``
116117

117118
### Dynamic Coding

Sources/PluginCore/Attributes/Codable/IgnoreCodingInitialized.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ extension Registration where Var: ValuedVariable {
6969
let attr = IgnoreCodingInitialized(from: decl)
7070
let code = attr != nil ? self.variable.value == nil : nil
7171
let options = Output.Options(
72-
decode: code, encode: code, encodingConditionExpr: nil
72+
decode: code, encode: code, encodingCondition: nil
7373
)
7474
let newVariable = Output(base: self.variable, options: options)
7575
return self.updating(with: newVariable)

Sources/PluginCore/Attributes/IgnoreCoding/IgnoreCoding.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,23 @@ where
106106
let ignoreEncoding = ignoreEncodingAttr != nil && conditionExpr == nil
107107
let decode = !ignoreCoding && !ignoreDecoding
108108
let encode = !ignoreCoding && !ignoreEncoding
109+
110+
let encodingCondition: Output.Options.Condition?
111+
if let conditionExpr = conditionExpr {
112+
switch conditionExpr.label?.tokenKind {
113+
case .identifier("if"):
114+
encodingCondition = .if(conditionExpr.expression)
115+
case .identifier("basedOn"):
116+
encodingCondition = .basedOn(conditionExpr.expression)
117+
default:
118+
encodingCondition = nil
119+
}
120+
} else {
121+
encodingCondition = nil
122+
}
123+
109124
let options = Output.Options(
110-
decode: decode, encode: encode, encodingConditionExpr: conditionExpr
125+
decode: decode, encode: encode, encodingCondition: encodingCondition
111126
)
112127
let newVariable = Output(base: self.variable, options: options)
113128
return self.updating(with: newVariable)

Sources/PluginCore/Attributes/IgnoreCoding/IgnoreEncoding.swift

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,20 @@ package struct IgnoreEncoding: PropertyAttribute {
1212
/// during initialization.
1313
let node: AttributeSyntax
1414

15-
/// Optional encoding condition closure specified.
15+
/// Optional encoding condition closure expression with label specified.
1616
///
1717
/// This closure may take one or multiple arguments depending on
18-
/// whether attached to property or enum case.
19-
///
20-
/// The return type of closure in `Bool`, based on this value encoding
21-
/// is decided to be performed or ignored.
22-
var conditionExpr: ExprSyntax? {
23-
return node.arguments?.as(LabeledExprListSyntax.self)?.first { expr in
24-
expr.label?.tokenKind == .identifier("if")
25-
}?.expression
18+
/// whether attached to property or enum case, and includes information
19+
/// about whether it's an 'if' or 'basedOn' condition.
20+
var conditionExpr: LabeledExprSyntax? {
21+
guard let args = node.arguments?.as(LabeledExprListSyntax.self) else {
22+
return nil
23+
}
24+
25+
return args.first { expr in
26+
expr.label?.tokenKind == .identifier("if") ||
27+
expr.label?.tokenKind == .identifier("basedOn")
28+
}
2629
}
2730

2831
/// Creates a new instance with the provided node.

Sources/PluginCore/Variables/ConditionalCodingVariable.swift

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,28 @@ where Var: ConditionalVariable, Var.Generated: ConditionalVariableSyntax {
2424
/// False for variables with `@IgnoreCoding`
2525
/// and `@IgnoreEncoding` attributes.
2626
let encode: Bool?
27-
/// The condition expression based on which encoding is decided.
27+
28+
/// The condition based on which encoding is decided.
2829
///
29-
/// This expression accepts arguments from this variable and resolves
30+
/// This condition accepts arguments from this variable and resolves
3031
/// to either `true` or `false` based on which encoding is ignored.
31-
let encodingConditionExpr: ExprSyntax?
32+
let encodingCondition: Condition?
33+
34+
/// Condition for determining when encoding should not be performed.
35+
enum Condition {
36+
/// Encoding is performed only if the provided expression evaluates
37+
/// to false.
38+
///
39+
/// The expression accepts arguments from this variable and resolves
40+
/// to either `true` or `false` based on which encoding is decided.
41+
case `if`(ExprSyntax)
42+
/// Encoding is performed only if the provided expression evaluates
43+
/// to false.
44+
///
45+
/// The expression accepts argument as the value that contains
46+
/// this property.
47+
case basedOn(ExprSyntax)
48+
}
3249
}
3350

3451
/// The value wrapped by this instance.
@@ -59,17 +76,27 @@ where Var: ConditionalVariable, Var.Generated: ConditionalVariableSyntax {
5976
to location: Var.CodingLocation
6077
) -> Var.Generated {
6178
let syntax = base.encoding(in: context, to: location)
62-
guard
63-
let conditionExpr = options.encodingConditionExpr
64-
else { return syntax }
65-
let args = self.conditionArguments
79+
guard let condition = options.encodingCondition else { return syntax }
80+
81+
let conditionExpr: ExprSyntax
82+
let args: LabeledExprListSyntax
83+
switch condition {
84+
case .if(let expr):
85+
conditionExpr = expr
86+
args = self.conditionArguments
87+
case .basedOn(let expr):
88+
conditionExpr = expr
89+
args = LabeledExprListSyntax {
90+
LabeledExprSyntax(expression: "self" as ExprSyntax)
91+
}
92+
}
93+
6694
let returnList = TupleTypeElementListSyntax {
6795
for _ in args {
6896
TupleTypeElementSyntax(type: "_" as TypeSyntax)
6997
}
7098
}
71-
let expr: ExprSyntax =
72-
"!{ () -> (\(returnList)) -> Bool in \(conditionExpr) }()(\(args))"
99+
let expr: ExprSyntax = "!{ () -> (\(returnList)) -> Bool in \(conditionExpr) }()(\(args))"
73100
return syntax.adding(condition: [.init(expression: expr)])
74101
}
75102
}

0 commit comments

Comments
 (0)