Skip to content

Commit

Permalink
feature: json mockable
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr-T-Dev committed Oct 21, 2024
1 parent bd4e33f commit 380c3bc
Show file tree
Hide file tree
Showing 18 changed files with 701 additions and 208 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.1.0] - 2024-10-18

### Added

- JsonMockable macro

## [0.0.4] - 2024-09-13

### Added
Expand Down
71 changes: 71 additions & 0 deletions Documentation/JsonMockable/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# `@JsonMockable`

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.

## Parameters
* `keyDecodingStrategy`: JSONDecoder.KeyDecodingStrategy: Specifies the strategy to use when decoding keys in the JSON file. The available options are:

* `.useDefaultKeys`: Use the keys in the JSON as-is.

* `.convertFromSnakeCase`: Convert keys from snake\_case to camelCase when decoding.

* `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.

* `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.

## Usage Example
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:

``` swift
@JsonMockable(
keyDecodingStrategy: .convertFromSnakeCase,
bundle: .main
)
struct User: Decodable {
...
}
```

In case the JSON file has different name from your class / struct:

``` swift
@JsonMockable(
keyDecodingStrategy: .convertFromSnakeCase,
bundle: .main,
jsonFile: "FILE_NAME"
)
struct User: Decodable {
...
}
```
## Methods
`@JsonMockable` expose a method to your entity that you can use to create other

``` swift
private static func getMock(bundle: Bundle, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy, fileName: String?) throws -> Self

```

### Usage
``` swift
@JsonMockable(
keyDecodingStrategy: .convertFromSnakeCase,
bundle: .main
)
struct User: Decodable {
var example: String

static var mockA: Self {
get throws {
try getMock(bundle: .main, keyDecodingStrategy: .convertFromSnakeCase, fileName: "fileA")
}
}

static var mockB: Self {
get throws {
try getMock(bundle: .main, keyDecodingStrategy: .convertFromSnakeCase, fileName: "fileB")
}
}
}

```
206 changes: 206 additions & 0 deletions Documentation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# Documentation

## Table of Contents
- [General Macros](#general-macros)
- [Codable Macros](#codable-macros)
- [JsonMockable](/Documentation/JsonMockable/README.md)

## General Macros

#### Wip Macro
Mark work in progress code and convert it into an XCode warning.

wip(feature: String, todo: String)

Example:

#wip(feature: "New login workflow", todo: "API connection")


#### Debug print
Print while debugging safely. It will just add #if DEBUG.

debug_print(_ description: String)


#### Default init
Provide init func to a struct.
Example:

@DefaultInit
struct Vehicle {
let wheels: Int
let maxSpeed: Int
let name: String
}

will be expanded to

struct Vehicle {
let wheels: Int
let maxSpeed: Int
let name: String
func init(wheels: Int, maxSpeed: Int, name: String) {
self.wheels = wheels
self.maxSpeed = maxSpeed
self.name = name
}
}

## Codable Macros
A set of macros for workinf easily with Codable, reducing the code needed.
As a requirement, any of the macros will need to add the macro @CustomCodable on the object.
You also need to add conformance to Codable in an extension of the object without any implementation.

#### Custom Codable Key - CustomCodableKey(String)
Allows you to create a custom key for decoding.

Example:

@CustomCodable
struct Vehicle {
let wheels: Int
@CustomCodableKey("speedAllowed")
let maxSpeed: Int
let name: String
}

extension Vehicle: Codable {}

#### Custom Default Value - CustomDefault(Any)
Allows you to add a default value in case there is no value founded while decoding

Example:

@CustomCodable
struct Vehicle {
let wheels: Int
@CustomDefault(150)
let maxSpeed: Int
let name: String
}

extension Vehicle: Codable {}

#### Custom Date - CustomDate(dateFormat: String, defaultValue: Date? = nil)
Allows you to decode Strings into Dates with default values

Example:

@CustomCodable
struct Vehicle {
let wheels: Int
@CustomDate(dateFormat: "YYYYY-mm-dd", defaultValue: Date())
let designed: Date
let maxSpeed: Int
let name: String
}

extension Vehicle: Codable {}

#### Custom Date - CustomDate(dateFormat: String, defaultValue: Date? = nil)
Allows you to decode Strings into Dates with default values

Example:

@CustomCodable
struct Vehicle {
let wheels: Int
@CustomDate(dateFormat: "YYYYY-mm-dd", defaultValue: Date())
let designed: Date
let maxSpeed: Int
let name: String
}

extension Vehicle: Codable {}

#### Custom Hashable - CustomHashable(parameters: [String])
Allows you to add Hashable conformance providing the properties you want to use.

Example:

@CustomCodable
CustomHashable(parameters: ["wheels", "name"])
struct Vehicle {
let wheels: Int
let designed: Date
let maxSpeed: Int
let name: String
}

extension Vehicle: Codable {}

will expand to:

extension Vehicle: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(wheels)
hasher.combine(name)
}
}

#### Custom Equatable - CustomEquatable(parameters: [String])
Allows you to add Equatable conformance providing the properties you want to use.

Example:

@CustomCodable
CustomEquatable(parameters: ["wheels", "maxSpeed"])
struct Vehicle {
let wheels: Int
let designed: Date
let maxSpeed: Int
let name: String
}

extension Vehicle: Codable {}

will expand to:

extension Vehicle: Equatable {
public static func == (lhs: Vehicle, rhs: Vehicle) -> Bool {
lhs.wheels == rhs.wheel && lhs.maxSpeed == rhs.maxSpeed
}
}

#### Custom URL - CustomDate(dateFormat: String, defaultValue: Date? = nil)
Allows you to decode Strings into optional URL

Example:

@CustomCodable
struct Vehicle {
let wheels: Int
let designed: Date
let maxSpeed: Int
let name: String
@CustomURL
let website: URL?
}

extension Vehicle: Codable {}

Of course you can combine all of them:

@CustomCodable
@DefaultInit
@CustomHashable(["wheels", "name"])
@CustomEquatable(["wheels", "designed"])
struct Vehicle {
@CustomCodableKey("number_of_wheels")
let wheels: Int
@CustomDate(dateFormat: "YYYYY-mm-dd", defaultValue: Date())
let designed: Date
@CustomDefault(150)
let maxSpeed: Int
let name: String
@CustomURL
let website: URL?
}
extension Vehicle: Codable {}
3 changes: 3 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ let package = Package(
dependencies: [
"SageSwiftKitMacros",
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
],
resources: [
.process("Mocks")
]
),
]
Expand Down
Loading

0 comments on commit 380c3bc

Please sign in to comment.