Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ SWON uses the amazingly written [cJSON][credits-cjson] parser (C) under the hood

### Things to do

- Handle `RawRepresentable` automatically.
- Do not suppress errors inside an optional field (silently made nil).
- Add thorough tests add meaningful examples.
- Add thorough tests and meaningful examples.
- Clean up some messy code.

## License
Expand Down
45 changes: 45 additions & 0 deletions Sources/SWON/Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2026 Davide De Rosa
//
// SPDX-License-Identifier: MIT

extension Array: SWONDecodable, SWONEncodable where Element: SWONDecodable & SWONEncodable {
public init(fromSWON root: swon_t) throws {
var list: [Element] = []
var array = swon_t()
guard swon_get_array(&array, root) == SWONResultValid else {
throw SWONError.required("Array")
}
let size = swon_get_array_size(array)
for i in 0..<size {
var item = swon_t()
let result = swon_get_array_item(&item, array, Int32(i))
guard result == SWONResultValid else { throw SWONError.invalid("Array") }
let el = try Element(fromSWON: item)
list.append(el)
}
self = list
}

public func toSWON() throws -> swon_t {
let list = Array(self)
var root = swon_t()
guard swon_create_array(&root) else { throw SWONError.invalid("Array") }
for el in list {
let item = try el.toSWON()
guard swon_array_add_item(&root, item) else {
throw SWONError.invalid("Array: Element")
}
}
return root
}
}

extension Set: SWONDecodable, SWONEncodable where Element: SWONDecodable & SWONEncodable {
public init(fromSWON root: swon_t) throws {
self = try Set(Array(fromSWON: root))
}

public func toSWON() throws -> swon_t {
try Array(self).toSWON()
}
}
11 changes: 11 additions & 0 deletions Sources/SWON/Protocols.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-FileCopyrightText: 2026 Davide De Rosa
//
// SPDX-License-Identifier: MIT

public protocol SWONEncodable {
func toSWON() throws -> swon_t
}

public protocol SWONDecodable {
init(fromSWON root: swon_t) throws
}
4 changes: 4 additions & 0 deletions Sources/SWON/SWON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
named(toSWON),
named(toJSON)
)
@attached(
extension,
conformances: SWONEncodable, SWONDecodable
)
public macro SWON() = #externalMacro(
module: "SWONMacros",
type: "SWONCompoundMacro"
Expand Down
2 changes: 1 addition & 1 deletion Sources/SWONMacros/Decoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ struct SWONDecodeMacro: MemberMacro {
// context.diagnose(
// Diagnostic(
// node: Syntax(node),
// message: SWONMessage(message: "Assigning \(field) of type \(type)")
// message: SWONMessage(message: "Assigning \(field) of type \(type) (decoded: \(type.decodedType(context: context)))")
// )
// )
assignments.append("""
Expand Down
39 changes: 38 additions & 1 deletion Sources/SWONMacros/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ struct SWONMacrosPlugin: CompilerPlugin {
]
}

public struct SWONCompoundMacro: MemberMacro {
public struct SWONCompoundMacro: MemberMacro, ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
Expand All @@ -35,4 +35,41 @@ public struct SWONCompoundMacro: MemberMacro {
)
return result
}

public static func expansion(
of node: AttributeSyntax,
attachedTo declaration: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [ExtensionDeclSyntax] {
// Extract the type name (struct/class/enum identifier)
guard let named = declaration.asProtocol(NamedDeclSyntax.self) else {
return []
}
let typeName = named.name.text

// Optional: avoid duplicate conformances if already declared
let alreadyConforms: Set<String> =
declaration.inheritanceClause?
.inheritedTypes
.compactMap { $0.type.as(IdentifierTypeSyntax.self)?.name.text }
.reduce(into: Set<String>()) { $0.insert($1) }
?? []
let needsEncodable = !alreadyConforms.contains("SWONEncodable")
let needsDecodable = !alreadyConforms.contains("SWONDecodable")
guard needsEncodable || needsDecodable else {
return []
}

// Build the conformance list dynamically
var conformances: [String] = []
if needsEncodable { conformances.append("SWONEncodable") }
if needsDecodable { conformances.append("SWONDecodable") }
let conformanceList = conformances.joined(separator: ", ")
let ext = try ExtensionDeclSyntax(
"extension \(raw: typeName): \(raw: conformanceList) {}"
)
return [ext]
}
}
2 changes: 1 addition & 1 deletion Sources/SWON_C/swon.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ swon_t swon_get_map_next(swon_t src) {

bool swon_create_array(swon_t *dst) {
dst->impl = cJSON_CreateArray();
return dst->impl;
return dst->impl != NULL;
}

bool swon_array_add_item(swon_t *dst, swon_t item) {
Expand Down
3 changes: 3 additions & 0 deletions Tests/SWONTests/ComplexStruct.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ struct ComplexStruct: Equatable, Codable {
let optionalStringToSubStructArray: [String: [SubStruct]]?

let uint16: UInt16?

// Set
let someSet: Set<Color>?
}

@SWON
Expand Down
3 changes: 2 additions & 1 deletion Tests/SWONTests/Resources/populated.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,6 @@
]
}]
},
"uint16": 60000
"uint16": 60000,
"someSet": ["blue", "red", "green"]
}
11 changes: 3 additions & 8 deletions Tests/SWONTests/SWONTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,6 @@ struct SWONTests {

@Test
func encodingAssociatedEnums() throws {
@SWON
struct LocalContainer {
let enums: [AssociatedEnum]
init(enums: [AssociatedEnum]) {
self.enums = enums
}
}
let sub = try SubStruct(fromJSON: """
{
"favoriteColor": "red",
Expand Down Expand Up @@ -81,7 +74,8 @@ struct SWONTests {
"nestedColors": [
["green", "blue"],
["red", "red", "green"]
]
],
"colorSet": ["red", "green"]
}
""")
print(try sut.toJSON())
Expand Down Expand Up @@ -116,6 +110,7 @@ struct LocalStruct {
let another: AnotherStruct?
let nestedStrings: [[String]]
let nestedColors: [[Color]]
let colorSet: Set<Color>
}

@SWON
Expand Down