Skip to content

Commit b468de5

Browse files
authored
feat: added DecodedAt and EncodedAt macros for separate decoding and encoding CodingKeys (#138)
1 parent 89d9cc0 commit b468de5

33 files changed

+3048
-140
lines changed

Sources/MacroPlugin/Definitions.swift

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,72 @@ struct CodedAt: PeerMacro {
198198
}
199199
}
200200

201+
/// Attribute type for `DecodedAt` macro-attribute.
202+
///
203+
/// This type can validate`DecodedAt` macro-attribute
204+
/// usage and extract data for `Codable` macro to
205+
/// generate implementation.
206+
struct DecodedAt: PeerMacro {
207+
/// Provide metadata to `Codable` macro for final expansion
208+
/// and verify proper usage of this macro.
209+
///
210+
/// This macro doesn't perform any expansion rather `Codable` macro
211+
/// uses when performing expansion.
212+
///
213+
/// This macro verifies that macro usage condition is met by attached
214+
/// declaration by using the `validate` implementation provided.
215+
///
216+
/// - Parameters:
217+
/// - node: The attribute describing this macro.
218+
/// - declaration: The declaration this macro attribute is attached to.
219+
/// - context: The context in which to perform the macro expansion.
220+
///
221+
/// - Returns: No declaration is returned, only attached declaration is
222+
/// analyzed.
223+
static func expansion(
224+
of node: AttributeSyntax,
225+
providingPeersOf declaration: some DeclSyntaxProtocol,
226+
in context: some MacroExpansionContext
227+
) throws -> [DeclSyntax] {
228+
return try PluginCore.DecodedAt.expansion(
229+
of: node, providingPeersOf: declaration, in: context
230+
)
231+
}
232+
}
233+
234+
/// Attribute type for `EncodedAt` macro-attribute.
235+
///
236+
/// This type can validate `EncodedAt` macro-attribute
237+
/// usage and extract data for `Codable` macro to
238+
/// generate implementation.
239+
struct EncodedAt: PeerMacro {
240+
/// Provide metadata to `Codable` macro for final expansion
241+
/// and verify proper usage of this macro.
242+
///
243+
/// This macro doesn't perform any expansion rather `Codable` macro
244+
/// uses when performing expansion.
245+
///
246+
/// This macro verifies that macro usage condition is met by attached
247+
/// declaration by using the `validate` implementation provided.
248+
///
249+
/// - Parameters:
250+
/// - node: The attribute describing this macro.
251+
/// - declaration: The declaration this macro attribute is attached to.
252+
/// - context: The context in which to perform the macro expansion.
253+
///
254+
/// - Returns: No declaration is returned, only attached declaration is
255+
/// analyzed.
256+
static func expansion(
257+
of node: AttributeSyntax,
258+
providingPeersOf declaration: some DeclSyntaxProtocol,
259+
in context: some MacroExpansionContext
260+
) throws -> [DeclSyntax] {
261+
return try PluginCore.EncodedAt.expansion(
262+
of: node, providingPeersOf: declaration, in: context
263+
)
264+
}
265+
}
266+
201267
/// Attribute type for `CodedIn` macro-attribute.
202268
///
203269
/// This type can validate`CodedIn` macro-attribute

Sources/MacroPlugin/Plugin.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ struct MetaCodablePlugin: CompilerPlugin {
1717
Default.self,
1818
CodedAs.self,
1919
ContentAt.self,
20+
DecodedAt.self,
21+
EncodedAt.self,
2022
IgnoreCoding.self,
2123
IgnoreDecoding.self,
2224
IgnoreEncoding.self,

Sources/MetaCodable/Codable/Decodable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
/// * Use ``CodedAt(_:)`` to provide enum-case/protocol identifier tag path.
2222
/// * Use ``CodedAs()`` to provide enum-case/protocol identifier tag type.
2323
/// * Use ``ContentAt(_:_:)`` to provided enum-case/protocol content path.
24-
/// * Use ``IgnoreCoding()``, ``IgnoreDecoding()`` to ignore specific
24+
/// * Use ``IgnoreCoding()``, ``IgnoreDecoding()`` to ignore specific
2525
/// properties/cases/types from decoding.
2626
/// * Use ``CodingKeys(_:)`` to work with different case style `CodingKey`s.
2727
/// * Use ``IgnoreCodingInitialized()`` to ignore decoding

Sources/MetaCodable/Codable/Encodable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
/// * Use ``CodedAt(_:)`` to provide enum-case/protocol identifier tag path.
2323
/// * Use ``CodedAs()`` to provide enum-case/protocol identifier tag type.
2424
/// * Use ``ContentAt(_:_:)`` to provided enum-case/protocol content path.
25-
/// * Use ``IgnoreCoding()``, ``IgnoreEncoding()`` to ignore specific
25+
/// * Use ``IgnoreCoding()``, ``IgnoreEncoding()`` to ignore specific
2626
/// properties/cases/types from encoding.
2727
/// * Use ``IgnoreEncoding(if:)-1iuvv`` and ``IgnoreEncoding(if:)-7toka``
2828
/// to ignore encoding based on custom conditions.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// Indicates the field needs to be decoded to a specific
2+
/// `CodingKey` path provided, different from the encoding path.
3+
///
4+
/// See ``CodedAt(_:)`` for all configurations and use-cases.
5+
///
6+
/// - Parameter path: The `CodingKey` path value located at for decoding only.
7+
///
8+
/// - Note: This macro on its own only validates if attached declaration
9+
/// is a variable declaration. ``Codable(commonStrategies:)`` macro uses this macro
10+
/// when generating final implementations.
11+
///
12+
/// - Important: When applied to fields, the field type must confirm to
13+
/// `Decodable`.
14+
///
15+
/// - Important: This macro affects only decoding operations. Encoding will use
16+
/// the default variable name or any encoding path specified with ``EncodedAt(_:)``.
17+
@attached(peer)
18+
@available(swift 5.9)
19+
public macro DecodedAt(_ path: StaticString...) = #externalMacro(
20+
module: "MacroPlugin", type: "DecodedAt"
21+
)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// Indicates the field needs to be encoded to a specific
2+
/// `CodingKey` path provided, different from the decoding path.
3+
///
4+
/// See ``CodedAt(_:)`` for all configurations and use-cases.
5+
///
6+
/// - Parameter path: The `CodingKey` path value located at for encoding only.
7+
///
8+
/// - Note: This macro on its own only validates if attached declaration
9+
/// is a variable declaration. ``Codable(commonStrategies:)`` macro uses this macro
10+
/// when generating final implementations.
11+
///
12+
/// - Important: When applied to fields, the field type must confirm to
13+
/// `Encodable`.
14+
///
15+
/// - Important: This macro affects only encoding operations. Decoding will use
16+
/// the default variable name or any encoding path specified with ``DecodedAt(_:)``.
17+
@attached(peer)
18+
@available(swift 5.9)
19+
public macro EncodedAt(_ path: StaticString...) = #externalMacro(
20+
module: "MacroPlugin", type: "EncodedAt"
21+
)

Sources/MetaCodable/MetaCodable.docc/MetaCodable.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ Supercharge `Swift`'s `Codable` implementations with macros.
7777
### Strategies
7878

7979
- ``CodedAt(_:)``
80+
- ``DecodedAt(_:)``
81+
- ``EncodedAt(_:)``
8082
- ``CodedIn(_:)``
8183
- ``CodedAs()``
8284
- ``CodedAs(_:_:)``

Sources/PluginCore/Attributes/Codable/CodingKeys/CodingKeys.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ package struct CodingKeys: PeerAttribute {
5959
}
6060
}
6161

62-
extension Registration where Key == [String] {
62+
extension Registration where Key == PathKey {
6363
/// Update current registration `CodingKey` path data.
6464
///
6565
/// New registration is updated with the transformed `CodingKey` path
@@ -72,6 +72,10 @@ extension Registration where Key == [String] {
7272
) -> Self where D: AttributableDeclSyntax {
7373
guard let attr = CodingKeys(from: decl) else { return self }
7474
let strategy = attr.strategy
75-
return self.updating(with: strategy.transform(keyPath: self.key))
75+
let newKey = PathKey(
76+
decoding: strategy.transform(keyPath: self.key.decoding),
77+
encoding: strategy.transform(keyPath: self.key.encoding)
78+
)
79+
return self.updating(with: newKey)
7680
}
7781
}

Sources/PluginCore/Attributes/CodedAs.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ package struct CodedAs: PropertyAttribute {
7272
syntaxes: EnumDeclSyntax.self, ProtocolDeclSyntax.self
7373
)
7474
mustBeCombined(with: Codable.self)
75-
mustBeCombined(with: CodedAt.self)
75+
mustBeCombined(
76+
with: CodedAt.self, or: DecodedAt.self, EncodedAt.self
77+
)
7678
},
7779
else: `if`(
7880
isVariable,
@@ -128,7 +130,7 @@ extension Registration where Key == [ExprSyntax], Decl: AttributableDeclSyntax {
128130
}
129131

130132
extension Registration
131-
where Key == [String], Decl: AttributableDeclSyntax, Var: PropertyVariable {
133+
where Decl: AttributableDeclSyntax, Var: PropertyVariable {
132134
/// Update registration with alternate `CodingKey`s data.
133135
///
134136
/// New registration is updated with `CodingKey`s data that will be

Sources/PluginCore/Attributes/CodedBy.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ package struct CodedBy: PropertyAttribute {
5454
isEnum || isProtocol,
5555
AggregatedDiagnosticProducer {
5656
mustBeCombined(with: Codable.self)
57-
mustBeCombined(with: CodedAt.self)
57+
mustBeCombined(
58+
with: CodedAt.self, or: DecodedAt.self, EncodedAt.self
59+
)
5860
},
5961
else: AggregatedDiagnosticProducer {
6062
expect(syntaxes: VariableDeclSyntax.self)

0 commit comments

Comments
 (0)