-
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: fixed nested decoding with missing container (#44)
- Loading branch information
1 parent
31db2fd
commit 495cea4
Showing
19 changed files
with
4,008 additions
and
1,339 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
Sources/CodableMacroPlugin/Variables/Data/DecodingFallback.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
@_implementationOnly import SwiftSyntax | ||
|
||
/// Represents possible fallback options for decoding failure. | ||
/// | ||
/// When decoding fails for variable, variable can have fallback | ||
/// to throw the failure error or handle it completely or handle | ||
/// it only when variable is missing or `null`. | ||
enum DecodingFallback { | ||
/// Represents no fallback option. | ||
/// | ||
/// Indicates decoding failure error | ||
/// is thrown without any handling. | ||
case `throw` | ||
/// Represents fallback option for missing | ||
/// or `null` value. | ||
/// | ||
/// Indicates if variable data is missing or `null`, | ||
/// provided fallback syntax will be used for initialization. | ||
case ifMissing(CodeBlockItemListSyntax) | ||
/// Represents fallback option handling | ||
/// decoding failure completely. | ||
/// | ||
/// Indicates for any type of failure error in decoding, | ||
/// provided fallback syntax will be used for initialization. | ||
case ifError(CodeBlockItemListSyntax) | ||
|
||
/// Provides the code block list syntax for decoding provided | ||
/// container applying current fallback options. | ||
/// | ||
/// - Parameters: | ||
/// - container: The container to decode from. | ||
/// - key: The key from where to decode. | ||
/// - decoding: The nested container decoding | ||
/// code block generator. | ||
/// | ||
/// - Returns: The generated code block. | ||
func represented( | ||
decodingContainer container: TokenSyntax, | ||
fromKey key: Registrar.Key, | ||
nestedDecoding decoding: (TokenSyntax) -> CodeBlockItemListSyntax | ||
) -> CodeBlockItemListSyntax { | ||
let nestedContainer: TokenSyntax = "\(key.raw)_\(container)" | ||
return CodeBlockItemListSyntax { | ||
switch self { | ||
case .throw: | ||
""" | ||
let \(nestedContainer) = try \(container).nestedContainer(keyedBy: \(key.type), forKey: \(key.expr)) | ||
""" | ||
decoding(nestedContainer) | ||
case .ifMissing(let fallbacks): | ||
try! IfExprSyntax( | ||
""" | ||
if (try? \(container).decodeNil(forKey: \(key.expr))) == false | ||
""" | ||
) { | ||
""" | ||
let \(nestedContainer) = try \(container).nestedContainer(keyedBy: \(key.type), forKey: \(key.expr)) | ||
""" | ||
decoding(nestedContainer) | ||
} else: { | ||
fallbacks | ||
} | ||
case .ifError(let fallbacks): | ||
try! IfExprSyntax( | ||
""" | ||
if let \(nestedContainer) = try? \(container).nestedContainer(keyedBy: \(key.type), forKey: \(key.expr)) | ||
""" | ||
) { | ||
decoding(nestedContainer) | ||
} else: { | ||
fallbacks | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
extension Collection where Element == DecodingFallback { | ||
/// The combined fallback option for all variable elements. | ||
/// | ||
/// Represents the fallback to use when decoding container | ||
/// of all the element variables fails. | ||
var aggregate: Element { | ||
var aggregated = Element.ifError(.init()) | ||
for fallback in self { | ||
switch (aggregated, fallback) { | ||
case (_, .throw), (.throw, _): | ||
return .throw | ||
case (.ifMissing(var a), .ifMissing(let f)), | ||
(.ifMissing(var a), .ifError(let f)), | ||
(.ifError(var a), .ifMissing(let f)): | ||
a.append(contentsOf: f) | ||
aggregated = .ifMissing(a) | ||
case (.ifError(var a), .ifError(let f)): | ||
a.append(contentsOf: f) | ||
aggregated = .ifError(a) | ||
} | ||
} | ||
return aggregated | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
#if SWIFT_SYNTAX_EXTENSION_MACRO_FIXED | ||
import SwiftDiagnostics | ||
import XCTest | ||
|
||
@testable import CodableMacroPlugin | ||
|
||
final class CodedByTests: XCTestCase { | ||
|
||
func testMisuseOnNonVariableDeclaration() throws { | ||
assertMacroExpansion( | ||
""" | ||
struct SomeCodable { | ||
@CodedBy(Since1970DateCoder()) | ||
func someFunc() { | ||
} | ||
} | ||
""", | ||
expandedSource: | ||
""" | ||
struct SomeCodable { | ||
func someFunc() { | ||
} | ||
} | ||
""", | ||
diagnostics: [ | ||
.init( | ||
id: CodedBy.misuseID, | ||
message: | ||
"@CodedBy only applicable to variable declarations", | ||
line: 2, column: 5, | ||
fixIts: [ | ||
.init(message: "Remove @CodedBy attribute") | ||
] | ||
) | ||
] | ||
) | ||
} | ||
|
||
func testMisuseOnStaticVariable() throws { | ||
assertMacroExpansion( | ||
""" | ||
struct SomeCodable { | ||
@CodedBy(Since1970DateCoder()) | ||
static let value: String | ||
} | ||
""", | ||
expandedSource: | ||
""" | ||
struct SomeCodable { | ||
static let value: String | ||
} | ||
""", | ||
diagnostics: [ | ||
.init( | ||
id: CodedBy.misuseID, | ||
message: | ||
"@CodedBy can't be used with static variables declarations", | ||
line: 2, column: 5, | ||
fixIts: [ | ||
.init(message: "Remove @CodedBy attribute") | ||
] | ||
) | ||
] | ||
) | ||
} | ||
|
||
func testDuplicatedMisuse() throws { | ||
assertMacroExpansion( | ||
""" | ||
struct SomeCodable { | ||
@CodedBy(Since1970DateCoder()) | ||
@CodedBy(Since1970DateCoder()) | ||
let one: String | ||
} | ||
""", | ||
expandedSource: | ||
""" | ||
struct SomeCodable { | ||
let one: String | ||
} | ||
""", | ||
diagnostics: [ | ||
.init( | ||
id: CodedBy.misuseID, | ||
message: | ||
"@CodedBy can only be applied once per declaration", | ||
line: 2, column: 5, | ||
fixIts: [ | ||
.init(message: "Remove @CodedBy attribute") | ||
] | ||
), | ||
.init( | ||
id: CodedBy.misuseID, | ||
message: | ||
"@CodedBy can only be applied once per declaration", | ||
line: 3, column: 5, | ||
fixIts: [ | ||
.init(message: "Remove @CodedBy attribute") | ||
] | ||
), | ||
] | ||
) | ||
} | ||
} | ||
#endif |
Oops, something went wrong.