Skip to content

Commit

Permalink
feature: encode custom date
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr-T-Dev committed Jan 21, 2025
1 parent 2765523 commit 0bf1faa
Show file tree
Hide file tree
Showing 16 changed files with 328 additions and 155 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# [0.2.7] - 2025-01-21

### Added

- Decode custom codable dates

# [0.2.6] - 2025-01-03

### Added
Expand Down
5 changes: 4 additions & 1 deletion Sources/SageSwiftKitClient/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ struct SubObject: Codable {
}

@CustomCodable
struct PlayingObject {
struct PlayingObject: Codable {

@StringOrInt
var value: String?
Expand All @@ -33,6 +33,9 @@ struct PlayingObject {

var attachment: String?

@CustomDate(dateFormat: "aa-vv-cc")
var customDate: Date?

var identifier: Int
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// Copyright © 2025 Sage.
// All Rights Reserved.

import SwiftSyntax

extension VariableDeclSyntaxAdapter {
func getAttributeFor(macro: CodingMacro) -> AttributeSyntax? {
attributes.first { $0.adapter.name == macro.name }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,62 +6,17 @@ import Foundation
import SwiftSyntax
import SwiftSyntaxMacros

enum DecodingAttribute: Identifiable, Equatable {
case defaultValue(AttributeSyntax)
case date(AttributeSyntax)
case url(AttributeSyntax)
case stringOrInt(AttributeSyntax)
case stringOrDouble(AttributeSyntax)
case stringToDouble(AttributeSyntax)

var id: String {
switch self {
case .defaultValue:
return String(describing: CustomDefault.self)
case .date:
return String(describing: CustomDate.self)
case .url:
return String(describing: CustomURL.self)
case .stringOrInt:
return String(describing: StringOrInt.self)
case .stringOrDouble:
return String(describing: StringOrDouble.self)
case .stringToDouble:
return String(describing: StringToDouble.self)
}
}

var attribute: AttributeSyntax {
switch self {
case .defaultValue(let attributeSyntax):
return attributeSyntax
case .date(let attributeSyntax):
return attributeSyntax
case .url(let attributeSyntax):
return attributeSyntax
case .stringOrInt(let attributeSyntax):
return attributeSyntax
case .stringOrDouble(let attributeSyntax):
return attributeSyntax
case .stringToDouble(let attributeSyntax):
return attributeSyntax
}
}
}

extension Array where Element == DecodingAttribute {
func getAttribute(macro: PeerMacro.Type) -> DecodingAttribute? {
self.first(where: { $0.id == String(describing: macro) })
}
}

struct DecodeVariableBuild {
let variable: VariableDeclSyntax
let dateFormatter: String

init(variable: VariableDeclSyntax) {
init(variable: VariableDeclSyntax, dateFormatter: String) {
self.variable = variable
self.dateFormatter = dateFormatter
}

private var adapter: VariableDeclSyntaxAdapter { variable.adapter }

private var varName: String {
variable.adapter.identifier.text
}
Expand All @@ -74,58 +29,28 @@ struct DecodeVariableBuild {
variable.adapter.typeAnnotation.type.kind
}

private var variableDecodingAttributes: [DecodingAttribute] {
variable.adapter.attributes.compactMap { variableAttribute -> DecodingAttribute? in
if variableAttribute.adapter.name == String(describing: CustomDefault.self) {
return .defaultValue(variableAttribute)
}

if variableAttribute.adapter.name == String(describing: CustomDate.self) {
return .date(variableAttribute)
}

if variableAttribute.adapter.name == String(describing: CustomURL.self) {
return .url(variableAttribute)
}

if variableAttribute.adapter.name == String(describing: StringOrInt.self) {
return .stringOrInt(variableAttribute)
}

if variableAttribute.adapter.name == String(describing: StringOrDouble.self) {
return .stringOrDouble(variableAttribute)
}

if variableAttribute.adapter.name == String(describing: StringToDouble.self) {
return .stringToDouble(variableAttribute)
}

return nil
}
}

func build() -> CodeBlockItemSyntaxBuilder {
if let attribute = variableDecodingAttributes.getAttribute(macro: CustomDate.self)?.attribute {
if let attribute = adapter.getAttributeFor(macro: .customDate) {
return buildCustomDate(attribute: attribute)
}

if let attribute = variableDecodingAttributes.getAttribute(macro: CustomDefault.self)?.attribute {
if let attribute = adapter.getAttributeFor(macro: .customDefault) {
return buildCustomDefault(attribute: attribute)
}

if let attribute = variableDecodingAttributes.getAttribute(macro: CustomURL.self)?.attribute {
if let attribute = adapter.getAttributeFor(macro: .customURL) {
return buildCustomURL(attribute: attribute)
}

if let attribute = variableDecodingAttributes.getAttribute(macro: StringOrInt.self)?.attribute {
if let attribute = adapter.getAttributeFor(macro: .stringOrInt) {
return buildStringOrInt(attribute: attribute)
}

if let attribute = variableDecodingAttributes.getAttribute(macro: StringOrDouble.self)?.attribute {
if let attribute = adapter.getAttributeFor(macro: .stringOrDouble) {
return buildStringOrDouble(attribute: attribute)
}

if let attribute = variableDecodingAttributes.getAttribute(macro: StringToDouble.self)?.attribute {
if let attribute = adapter.getAttributeFor(macro: .stringToDouble) {
return buildStringToDouble(attribute: attribute)
}

Expand All @@ -135,8 +60,8 @@ struct DecodeVariableBuild {
func buildBasicDecode() -> CodeBlockItemSyntaxBuilder {
let optional = kind == .optionalType
return optional ?
.init(code: "self.\(varName) = try container.decodeIfPresent(\(type.dropLast()).self, forKey: .\(varName))") :
.init(code: "self.\(varName) = try container.decode(\(type).self, forKey: .\(varName))")
.code("self.\(varName) = try container.decodeIfPresent(\(type.dropLast()).self, forKey: .\(varName))") :
.code("self.\(varName) = try container.decode(\(type).self, forKey: .\(varName))")
}

func buildCustomDate(attribute: AttributeSyntax) -> CodeBlockItemSyntaxBuilder {
Expand All @@ -148,22 +73,18 @@ struct DecodeVariableBuild {

var elseBody: [CodeBlockItemSyntaxBuilder]?

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"

var body: [CodeBlockItemSyntaxBuilder] = [
.init(code: "let dateFormatter = DateFormatter()"),
.init(code: "dateFormatter.dateFormat = \(dateFormat.expression.description)"),
.init(code: "let date = dateFormatter.date(from: \(conditionalName))")
.code("\(dateFormatter).dateFormat = \(dateFormat.expression.description)"),
.code("let date = \(dateFormatter).date(from: \(conditionalName))")
]

if let defaultValue {
body.append(.init(code: "self.\(varName) = date ?? \(defaultValue.expression.description)"))
body.append(.code("self.\(varName) = date ?? \(defaultValue.expression.description)"))
elseBody = [
.init(code: "self.\(varName) = \(defaultValue.expression.description)")
.code("self.\(varName) = \(defaultValue.expression.description)")
]
} else {
body.append(.init(code: "self.\(varName) = date"))
body.append(.code("self.\(varName) = date"))
}

let ifBuilder = IfExprSyntaxBuilder(
Expand All @@ -172,7 +93,7 @@ struct DecodeVariableBuild {
elseBody: elseBody
)

return .init(otherBuilder: ifBuilder)
return .builder(ifBuilder)
}

func buildCustomDefault(attribute: AttributeSyntax) -> CodeBlockItemSyntaxBuilder {
Expand All @@ -182,7 +103,7 @@ struct DecodeVariableBuild {

let value: String = defaultValue.expression.description

return .init(code: "self.\(varName) = try container.decodeIfPresent(\(type).self, forKey: .\(varName)) ?? \(value)")
return .code("self.\(varName) = try container.decodeIfPresent(\(type).self, forKey: .\(varName)) ?? \(value)")
}

func buildStringOrInt(attribute: AttributeSyntax) -> CodeBlockItemSyntaxBuilder {
Expand All @@ -193,31 +114,31 @@ struct DecodeVariableBuild {
let conditionalName = "tmp"+varName.capitalized

let ifBody: [CodeBlockItemSyntaxBuilder] = [
.init(code: "\(varName) = \(conditionalName)")
.code("\(varName) = \(conditionalName)")
]

let elseIfBody: [CodeBlockItemSyntaxBuilder] = [
.init(code: "\(varName) = String(\(conditionalName))")
.code("\(varName) = String(\(conditionalName))")
]

let elseBody: [CodeBlockItemSyntaxBuilder] = [
.init(code: "\(varName) = nil")
.code("\(varName) = nil")
]

let elseIfBuilder = IfExprSyntaxBuilder(
condition: "if let \(conditionalName) = try? container.decode(Int.self, forKey: .\(varName))",
body: elseIfBody,
elseBody: elseBody
)
let elseIfCode = CodeBlockItemSyntaxBuilder.init(otherBuilder: elseIfBuilder)
let elseIfCode = CodeBlockItemSyntaxBuilder.builder( elseIfBuilder)

let ifBuilder = IfExprSyntaxBuilder(
condition: "if let \(conditionalName) = try? container.decode(String.self, forKey: .\(varName))",
body: ifBody,
elseBody: [elseIfCode]
)

return .init(otherBuilder: ifBuilder)
return .builder( ifBuilder)
}

func buildStringOrDouble(attribute: AttributeSyntax) -> CodeBlockItemSyntaxBuilder {
Expand All @@ -228,31 +149,31 @@ struct DecodeVariableBuild {
let conditionalName = "tmp"+varName.capitalized

let ifBody: [CodeBlockItemSyntaxBuilder] = [
.init(code: "\(varName) = \(conditionalName)")
.code("\(varName) = \(conditionalName)")
]

let elseIfBody: [CodeBlockItemSyntaxBuilder] = [
.init(code: "\(varName) = String(\(conditionalName))")
.code("\(varName) = String(\(conditionalName))")
]

let elseBody: [CodeBlockItemSyntaxBuilder] = [
.init(code: "\(varName) = nil")
.code("\(varName) = nil")
]

let elseIfBuilder = IfExprSyntaxBuilder(
condition: "if let \(conditionalName) = try? container.decode(Double.self, forKey: .\(varName))",
body: elseIfBody,
elseBody: elseBody
)
let elseIfCode = CodeBlockItemSyntaxBuilder.init(otherBuilder: elseIfBuilder)
let elseIfCode = CodeBlockItemSyntaxBuilder.builder( elseIfBuilder)

let ifBuilder = IfExprSyntaxBuilder(
condition: "if let \(conditionalName) = try? container.decode(String.self, forKey: .\(varName))",
body: ifBody,
elseBody: [elseIfCode]
)

return .init(otherBuilder: ifBuilder)
return .builder( ifBuilder)
}

func buildStringToDouble(attribute: AttributeSyntax) -> CodeBlockItemSyntaxBuilder {
Expand All @@ -264,11 +185,11 @@ struct DecodeVariableBuild {
let conditionalName = "tmp"+varName.capitalized

let ifBody: [CodeBlockItemSyntaxBuilder] = [
.init(code: "\(varName) = Double(\(conditionalName)) ?? 0")
.code("\(varName) = Double(\(conditionalName)) ?? 0")
]

let elseBody: [CodeBlockItemSyntaxBuilder] = [
.init(code: "\(varName) = 0")
.code("\(varName) = 0")
]

let ifBuilder = IfExprSyntaxBuilder(
Expand All @@ -277,17 +198,17 @@ struct DecodeVariableBuild {
elseBody: elseBody
)

return .init(otherBuilder: ifBuilder)
return .builder( ifBuilder)
}

func buildCustomURL(attribute: AttributeSyntax) -> CodeBlockItemSyntaxBuilder {

let body: [CodeBlockItemSyntaxBuilder] = [
.init(code: "self.\(varName) = URL(string: urlString)")
.code("self.\(varName) = URL(string: urlString)")
]

let elseBody: [CodeBlockItemSyntaxBuilder] = [
.init(code: "self.\(varName) = nil")
.code("self.\(varName) = nil")
]

let ifBuilder = IfExprSyntaxBuilder(
Expand All @@ -296,6 +217,6 @@ struct DecodeVariableBuild {
elseBody: elseBody
)

return .init(otherBuilder: ifBuilder)
return .builder( ifBuilder)
}
}
Loading

0 comments on commit 0bf1faa

Please sign in to comment.