Skip to content

Commit

Permalink
fix: fixed default value not respected for optional types (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
soumyamahunt committed Nov 6, 2023
1 parent db55d96 commit 4eb999c
Show file tree
Hide file tree
Showing 2 changed files with 305 additions and 3 deletions.
10 changes: 7 additions & 3 deletions Sources/CodableMacroPlugin/Variables/DefaultValueVariable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,14 @@ where Var.Initialization == RequiredInitialization {
let catchClauses = CatchClauseListSyntax {
CatchClauseSyntax { "self.\(name) = \(options.expr)" }
}
var doClauses = base.decoding(in: context, from: location)
if type.isOptional, !doClauses.isEmpty {
let lastIndex = doClauses.index(before: doClauses.endIndex)
let assignmentblock = doClauses.remove(at: lastIndex)
doClauses.append("\(assignmentblock) ?? \(options.expr)")
}
return CodeBlockItemListSyntax {
DoStmtSyntax(catchClauses: catchClauses) {
base.decoding(in: context, from: location)
}
DoStmtSyntax(catchClauses: catchClauses) { doClauses }
}
}
}
298 changes: 298 additions & 0 deletions Tests/MetaCodableTests/CodedAtTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,51 @@ final class CodedAtTests: XCTestCase {
)
}

func testWithNoPathAndDefaultValueOnOptionalType() throws {
assertMacroExpansion(
"""
@Codable
@MemberInit
struct SomeCodable {
@Default("some")
@CodedAt
let value: String?
}
""",
expandedSource:
"""
struct SomeCodable {
let value: String?
init(value: String? = "some") {
self.value = value
}
}
extension SomeCodable: Decodable {
init(from decoder: any Decoder) throws {
do {
self.value = try String?(from: decoder) ?? "some"
} catch {
self.value = "some"
}
}
}
extension SomeCodable: Encodable {
func encode(to encoder: any Encoder) throws {
try self.value.encode(to: encoder)
}
}
extension SomeCodable {
enum CodingKeys: String, CodingKey {
}
}
"""
)
}

func testWithNoPathWithHelperInstance() throws {
assertMacroExpansion(
"""
Expand Down Expand Up @@ -304,6 +349,52 @@ final class CodedAtTests: XCTestCase {
)
}

func testWithNoPathWithHelperInstanceAndDefaultValueOnOptional() throws {
assertMacroExpansion(
"""
@Codable
@MemberInit
struct SomeCodable {
@Default(["some"])
@CodedBy(LossySequenceCoder<[String]>())
@CodedAt
let value: [String]?
}
""",
expandedSource:
"""
struct SomeCodable {
let value: [String]?
init(value: [String]? = ["some"]) {
self.value = value
}
}
extension SomeCodable: Decodable {
init(from decoder: any Decoder) throws {
do {
self.value = try LossySequenceCoder<[String]>().decodeIfPresent(from: decoder) ?? ["some"]
} catch {
self.value = ["some"]
}
}
}
extension SomeCodable: Encodable {
func encode(to encoder: any Encoder) throws {
try LossySequenceCoder<[String]>().encodeIfPresent(self.value, to: encoder)
}
}
extension SomeCodable {
enum CodingKeys: String, CodingKey {
}
}
"""
)
}

func testWithSinglePath() throws {
assertMacroExpansion(
"""
Expand Down Expand Up @@ -395,6 +486,54 @@ final class CodedAtTests: XCTestCase {
)
}

func testWithSinglePathAndDefaultValueOnOptionalType() throws {
assertMacroExpansion(
"""
@Codable
@MemberInit
struct SomeCodable {
@Default("some")
@CodedAt("key")
let value: String?
}
""",
expandedSource:
"""
struct SomeCodable {
let value: String?
init(value: String? = "some") {
self.value = value
}
}
extension SomeCodable: Decodable {
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
self.value = try container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some"
} catch {
self.value = "some"
}
}
}
extension SomeCodable: Encodable {
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(self.value, forKey: CodingKeys.value)
}
}
extension SomeCodable {
enum CodingKeys: String, CodingKey {
case value = "key"
}
}
"""
)
}

func testWithSinglePathWithHelperInstance() throws {
assertMacroExpansion(
"""
Expand Down Expand Up @@ -488,6 +627,55 @@ final class CodedAtTests: XCTestCase {
)
}

func testWithOnePathWithHelperInstanceAndDefaultValueOnOptional() throws {
assertMacroExpansion(
"""
@Codable
@MemberInit
struct SomeCodable {
@Default(["some"])
@CodedBy(LossySequenceCoder<[String]>())
@CodedAt("key")
let value: [String]?
}
""",
expandedSource:
"""
struct SomeCodable {
let value: [String]?
init(value: [String]? = ["some"]) {
self.value = value
}
}
extension SomeCodable: Decodable {
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
self.value = try LossySequenceCoder<[String]>().decodeIfPresent(from: container, forKey: CodingKeys.value) ?? ["some"]
} catch {
self.value = ["some"]
}
}
}
extension SomeCodable: Encodable {
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try LossySequenceCoder<[String]>().encodeIfPresent(self.value, to: &container, atKey: CodingKeys.value)
}
}
extension SomeCodable {
enum CodingKeys: String, CodingKey {
case value = "key"
}
}
"""
)
}

func testWithNestedPath() throws {
assertMacroExpansion(
"""
Expand Down Expand Up @@ -591,6 +779,60 @@ final class CodedAtTests: XCTestCase {
)
}

func testWithNestedPathAndDefaultValueOnOptionalType() throws {
assertMacroExpansion(
"""
@Codable
@MemberInit
struct SomeCodable {
@Default("some")
@CodedAt("deeply", "nested", "key")
let value: String?
}
""",
expandedSource:
"""
struct SomeCodable {
let value: String?
init(value: String? = "some") {
self.value = value
}
}
extension SomeCodable: Decodable {
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let deeply_container = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
let nested_deeply_container = try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
do {
self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some"
} catch {
self.value = "some"
}
}
}
extension SomeCodable: Encodable {
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
try nested_deeply_container.encodeIfPresent(self.value, forKey: CodingKeys.value)
}
}
extension SomeCodable {
enum CodingKeys: String, CodingKey {
case value = "key"
case deeply = "deeply"
case nested = "nested"
}
}
"""
)
}

func testWithNestedPathWithHelperInstance() throws {
assertMacroExpansion(
"""
Expand Down Expand Up @@ -695,5 +937,61 @@ final class CodedAtTests: XCTestCase {
"""
)
}

func testWithNestedPathWithHelperInstanceAndDefaultValueOnOptional() throws
{
assertMacroExpansion(
"""
@Codable
@MemberInit
struct SomeCodable {
@Default(["some"])
@CodedBy(LossySequenceCoder<[String]>())
@CodedAt("deeply", "nested", "key")
let value: [String]?
}
""",
expandedSource:
"""
struct SomeCodable {
let value: [String]?
init(value: [String]? = ["some"]) {
self.value = value
}
}
extension SomeCodable: Decodable {
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let deeply_container = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
let nested_deeply_container = try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
do {
self.value = try LossySequenceCoder<[String]>().decodeIfPresent(from: nested_deeply_container, forKey: CodingKeys.value) ?? ["some"]
} catch {
self.value = ["some"]
}
}
}
extension SomeCodable: Encodable {
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
try LossySequenceCoder<[String]>().encodeIfPresent(self.value, to: &nested_deeply_container, atKey: CodingKeys.value)
}
}
extension SomeCodable {
enum CodingKeys: String, CodingKey {
case value = "key"
case deeply = "deeply"
case nested = "nested"
}
}
"""
)
}
}
#endif

0 comments on commit 4eb999c

Please sign in to comment.