Skip to content

Commit 380c3bc

Browse files
committed
feature: json mockable
1 parent bd4e33f commit 380c3bc

File tree

18 files changed

+701
-208
lines changed

18 files changed

+701
-208
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
# [0.1.0] - 2024-10-18
9+
10+
### Added
11+
12+
- JsonMockable macro
13+
814
## [0.0.4] - 2024-09-13
915

1016
### Added

Documentation/JsonMockable/README.md

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# `@JsonMockable`
2+
3+
The `@JsonMockable` macro is used to generate mockable data from a JSON file, making it easier to decode JSON into Swift models for testing purposes. This macro simplifies the process of decoding JSON data by providing automatic conformance to mockable behavior based on a JSON file, using customizable decoding strategies and bundles.
4+
5+
## Parameters
6+
* `keyDecodingStrategy`: JSONDecoder.KeyDecodingStrategy: Specifies the strategy to use when decoding keys in the JSON file. The available options are:
7+
8+
* `.useDefaultKeys`: Use the keys in the JSON as-is.
9+
10+
* `.convertFromSnakeCase`: Convert keys from snake\_case to camelCase when decoding.
11+
12+
* `bundle`: Bundle: Specifies the Bundle from which the JSON file will be loaded. This could be the app's main bundle or a test bundle, depending on where the mock data resides.
13+
14+
* `jsonFile`: String? _(optional)_: An optional string representing the name of the JSON file that contains mock data. If nil, the macro will look for a JSON file that matches the name of the type to which the macro is applied.
15+
16+
## Usage Example
17+
Assume you have a `User` struct that conforms to Decodable and you want to generate mockable data from a JSON file with the same name:
18+
19+
``` swift
20+
@JsonMockable(
21+
keyDecodingStrategy: .convertFromSnakeCase,
22+
bundle: .main
23+
)
24+
struct User: Decodable {
25+
...
26+
}
27+
```
28+
29+
In case the JSON file has different name from your class / struct:
30+
31+
``` swift
32+
@JsonMockable(
33+
keyDecodingStrategy: .convertFromSnakeCase,
34+
bundle: .main,
35+
jsonFile: "FILE_NAME"
36+
)
37+
struct User: Decodable {
38+
...
39+
}
40+
```
41+
## Methods
42+
`@JsonMockable` expose a method to your entity that you can use to create other
43+
44+
``` swift
45+
private static func getMock(bundle: Bundle, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy, fileName: String?) throws -> Self
46+
47+
```
48+
49+
### Usage
50+
``` swift
51+
@JsonMockable(
52+
keyDecodingStrategy: .convertFromSnakeCase,
53+
bundle: .main
54+
)
55+
struct User: Decodable {
56+
var example: String
57+
58+
static var mockA: Self {
59+
get throws {
60+
try getMock(bundle: .main, keyDecodingStrategy: .convertFromSnakeCase, fileName: "fileA")
61+
}
62+
}
63+
64+
static var mockB: Self {
65+
get throws {
66+
try getMock(bundle: .main, keyDecodingStrategy: .convertFromSnakeCase, fileName: "fileB")
67+
}
68+
}
69+
}
70+
71+
```

Documentation/README.md

+206
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# Documentation
2+
3+
## Table of Contents
4+
- [General Macros](#general-macros)
5+
- [Codable Macros](#codable-macros)
6+
- [JsonMockable](/Documentation/JsonMockable/README.md)
7+
8+
## General Macros
9+
10+
#### Wip Macro
11+
Mark work in progress code and convert it into an XCode warning.
12+
13+
wip(feature: String, todo: String)
14+
15+
Example:
16+
17+
#wip(feature: "New login workflow", todo: "API connection")
18+
19+
20+
#### Debug print
21+
Print while debugging safely. It will just add #if DEBUG.
22+
23+
debug_print(_ description: String)
24+
25+
26+
#### Default init
27+
Provide init func to a struct.
28+
Example:
29+
30+
@DefaultInit
31+
struct Vehicle {
32+
let wheels: Int
33+
let maxSpeed: Int
34+
let name: String
35+
}
36+
37+
will be expanded to
38+
39+
struct Vehicle {
40+
let wheels: Int
41+
let maxSpeed: Int
42+
let name: String
43+
44+
func init(wheels: Int, maxSpeed: Int, name: String) {
45+
self.wheels = wheels
46+
self.maxSpeed = maxSpeed
47+
self.name = name
48+
}
49+
}
50+
51+
## Codable Macros
52+
A set of macros for workinf easily with Codable, reducing the code needed.
53+
As a requirement, any of the macros will need to add the macro @CustomCodable on the object.
54+
You also need to add conformance to Codable in an extension of the object without any implementation.
55+
56+
#### Custom Codable Key - CustomCodableKey(String)
57+
Allows you to create a custom key for decoding.
58+
59+
Example:
60+
61+
@CustomCodable
62+
struct Vehicle {
63+
let wheels: Int
64+
@CustomCodableKey("speedAllowed")
65+
let maxSpeed: Int
66+
let name: String
67+
}
68+
69+
extension Vehicle: Codable {}
70+
71+
#### Custom Default Value - CustomDefault(Any)
72+
Allows you to add a default value in case there is no value founded while decoding
73+
74+
Example:
75+
76+
@CustomCodable
77+
struct Vehicle {
78+
let wheels: Int
79+
@CustomDefault(150)
80+
let maxSpeed: Int
81+
let name: String
82+
}
83+
84+
extension Vehicle: Codable {}
85+
86+
#### Custom Date - CustomDate(dateFormat: String, defaultValue: Date? = nil)
87+
Allows you to decode Strings into Dates with default values
88+
89+
Example:
90+
91+
@CustomCodable
92+
struct Vehicle {
93+
let wheels: Int
94+
@CustomDate(dateFormat: "YYYYY-mm-dd", defaultValue: Date())
95+
let designed: Date
96+
let maxSpeed: Int
97+
let name: String
98+
}
99+
100+
extension Vehicle: Codable {}
101+
102+
#### Custom Date - CustomDate(dateFormat: String, defaultValue: Date? = nil)
103+
Allows you to decode Strings into Dates with default values
104+
105+
Example:
106+
107+
@CustomCodable
108+
struct Vehicle {
109+
let wheels: Int
110+
@CustomDate(dateFormat: "YYYYY-mm-dd", defaultValue: Date())
111+
let designed: Date
112+
let maxSpeed: Int
113+
let name: String
114+
}
115+
116+
extension Vehicle: Codable {}
117+
118+
#### Custom Hashable - CustomHashable(parameters: [String])
119+
Allows you to add Hashable conformance providing the properties you want to use.
120+
121+
Example:
122+
123+
@CustomCodable
124+
CustomHashable(parameters: ["wheels", "name"])
125+
struct Vehicle {
126+
let wheels: Int
127+
let designed: Date
128+
let maxSpeed: Int
129+
let name: String
130+
}
131+
132+
extension Vehicle: Codable {}
133+
134+
will expand to:
135+
136+
extension Vehicle: Hashable {
137+
public func hash(into hasher: inout Hasher) {
138+
hasher.combine(wheels)
139+
hasher.combine(name)
140+
}
141+
}
142+
143+
#### Custom Equatable - CustomEquatable(parameters: [String])
144+
Allows you to add Equatable conformance providing the properties you want to use.
145+
146+
Example:
147+
148+
@CustomCodable
149+
CustomEquatable(parameters: ["wheels", "maxSpeed"])
150+
struct Vehicle {
151+
let wheels: Int
152+
let designed: Date
153+
let maxSpeed: Int
154+
let name: String
155+
}
156+
157+
extension Vehicle: Codable {}
158+
159+
will expand to:
160+
161+
extension Vehicle: Equatable {
162+
public static func == (lhs: Vehicle, rhs: Vehicle) -> Bool {
163+
lhs.wheels == rhs.wheel && lhs.maxSpeed == rhs.maxSpeed
164+
}
165+
}
166+
167+
#### Custom URL - CustomDate(dateFormat: String, defaultValue: Date? = nil)
168+
Allows you to decode Strings into optional URL
169+
170+
Example:
171+
172+
@CustomCodable
173+
struct Vehicle {
174+
let wheels: Int
175+
let designed: Date
176+
let maxSpeed: Int
177+
let name: String
178+
@CustomURL
179+
let website: URL?
180+
}
181+
182+
extension Vehicle: Codable {}
183+
184+
Of course you can combine all of them:
185+
186+
@CustomCodable
187+
@DefaultInit
188+
@CustomHashable(["wheels", "name"])
189+
@CustomEquatable(["wheels", "designed"])
190+
struct Vehicle {
191+
@CustomCodableKey("number_of_wheels")
192+
let wheels: Int
193+
194+
@CustomDate(dateFormat: "YYYYY-mm-dd", defaultValue: Date())
195+
let designed: Date
196+
197+
@CustomDefault(150)
198+
let maxSpeed: Int
199+
200+
let name: String
201+
202+
@CustomURL
203+
let website: URL?
204+
}
205+
206+
extension Vehicle: Codable {}

Package.swift

+3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ let package = Package(
4545
dependencies: [
4646
"SageSwiftKitMacros",
4747
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
48+
],
49+
resources: [
50+
.process("Mocks")
4851
]
4952
),
5053
]

0 commit comments

Comments
 (0)