Skip to content

Commit

Permalink
Fridge 0.9
Browse files Browse the repository at this point in the history
Added push method (#27)
  - Introduced push method to the main interface
  - Added simple Playground example (#34)

Other:
  - Another (quick) README update (#30)
  - Removed do/catch blocks from the Grabber

Closes #27
Closes #34
Fixes #30

Squashed commits:
commit b2b6dbe
commit 7196555
  • Loading branch information
vexy committed May 28, 2022
1 parent 14e8f2e commit 48f1ae4
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 82 deletions.
54 changes: 54 additions & 0 deletions Guides/Examples/Fridge basics.playground/Contents.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import UIKit
import Fridge

//MARK: Grab example

let todoEndpoint = URL(string: "https://jsonplaceholder.typicode.com/todos/")!
struct ToDo: Decodable {
var id: Int
var title: String
var completed: Bool
}

async {
print("--> Grabbing all TODO objects...")
do {
let results: [ToDo] = try await Fridge.grab🔮(from: todoEndpoint)
// print all the results
for item in results {
print("ID:\(item.id) - \(item.title)")
}
print("|\nSuccessfully grabbed \(results.count) ToDO objects\n-----")
} catch {
print("Grab failed.")
}
}

//MARK: - Push example

struct Comment: Codable, CustomDebugStringConvertible {
let postId: Int
let id: Int
let name: String
let email: String
let body: String

var debugDescription: String {
return "[Comment] (ID:\(id)) \(name) - \(email). Body: \(body)"
}
}

async {
print("--> Posting new comment...")
do {

let newComment = Comment(postId: 12, id: 100, name: "New comment", email: "some@email.com", body: "Body of the new comment")

let response: Comment = try await Fridge.push📡(newComment, to: "https://jsonplaceholder.typicode.com/comments/")

print("New comment successfuly pushed.\nReturned (response) object:")
print(response)
} catch {
print("Push failed")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='ios' buildActiveScheme='true' executeOnSourceChanges='true' importAppTypes='true'>
<timeline fileName='timeline.xctimeline'/>
</playground>
23 changes: 18 additions & 5 deletions Guides/Usage.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@

# 👨‍🚀 Fridge Usage Guide
In short, following guide describes **how to**:
Following guide describes **how to**:
- [fetch data from network](#network-fetching)
- [load/store an object](#data-handling)
- [handle errors](#error-handling)

Checkout architecture overview in separate [diagram](Guides/Fridge.diagram.md) file.
For more information, you can always check provided, `in-code` documentation.
Checkout _architecture overview_ in separate [diagram](Guides/Fridge.diagram.md) file.
Find out different wasy to easily use`Fridge` in provided [playground file](Guides/Examples/Fridge-basics).
For deeper information, you can check `in-code` documentation.

---

## Network fetching
With Fridge, network fetching is performed in just 3 steps:
1. Make sure your desired `struct` conforms to `Decodable`
1. Conform your desired `struct` to `Decodable`
2. Define `URL` endpoint where your model resides
2. Await for Fridge to `grab🔮(...)` the object

Expand Down Expand Up @@ -96,4 +97,16 @@ If your store identifier cannot be found `Fridge` will throw an Error. Check the
## ⛔️ Error Handling
Each of the Fridge method, is internally marked with `throws` keyword. Here's what you can deal with:

// list all errors here
```Swift
enum FridgeErrors: Error {
case grabFailed
case pushFailed
case decodingFailed
}
```

You can expect `FridgeErrors` on following calls:
- `grab🔮`

_NOTE:_
Fridge error model is not yet final. You may expect different error message or structures before `v1.0` release.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ let package = Package(
.product(name: "bsoncoder", package: "BSONCoder")
],
exclude: [
"../../Guides/",
"../../Guides",
"../../README.md"
]
),
Expand Down
101 changes: 47 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
</h1>

<p align="center">
<code>Fridge</code> - <b>Lightweight</b>, <b>asnyc</b> and <i>extreeemely simple</i> to use fetch or store mechanism.</b>
<br>
Let your fancy <code>struct</code>(s) raise and shine again, while focusing on 💬 🥊🤖⭐️🗝 <i>other important stuff</i>.<br><br>
<b>Fridge</b> - extremely simple <code>async/await</code> fetch-and-store implementation you'll ever see !<br>
Let your fancy <code>struct</code>(s) raise and shine again, allowing you to focus on 💬 🥊🤖⭐️🗝 <i>stuff</i>.<br><br>
</p>
<br><br>

Expand Down Expand Up @@ -43,61 +42,65 @@
</a>
<img src="https://img.shields.io/github/languages/code-size/vexy/fridge?color=g">
</p>
<br><br>

# 💠 Library interface
`Fridge` is a freezing device ❄️. It has to keep things cool enough, exposing following icy interface:
|Method name|TLDR|
|:-|:-|
|`Fridge.grab🔮(from: URL)`|Grabs your model from the network endpoint (_iOS 15+ only_)|
|`Fridge.freeze🧊(object, identifier)`|Safely "freezes" your `struct` to persistant store|
|`Fridge.unfreeze🪅🎉(identifier)`|"Unfreezes", your previously frozen, `struct` giving you control of it|
|`Fridge.isFrozen🔬(identifier)`|Quickly check if your `Fridge` have been loaded|

### Async/Await networking
`Fridge` is supported by `async/await` and is here to reduce the pain of:
- _fetching your stuff from the network_,
`Fridge` is a freezing device ❄️. It has to keep things cool enough, exposing following icy interface.

### Async/Await networking
- `Fridge.grab🔮(from: URL)` -> Grabs your model from the network endpoint (_iOS 15+ only_)
- `Fridge.push📡(object, to)` -> Pushes (sends) your model to designated network endpoint (_iOS 15+ only_)

Fridge is supported by `async/await` and is here to reduce the pain of:
- fetching _your stuff_ from the network,
- parsing or decoding (JSON) data,
- _storing and retreiving stuff_ to the persistant storage
- doing endless _error checking_
- doing boring _error checking_
- and yeah... good old **closures**.

And yeah... good old **closures**. You can even say goodbye to closures and CoreData if you want!
With Fridge, you can even _say goodbye to closures and CoreData_ if you want!

### Persistant storage
`Fridge` is built on Foundation principles and uses `BSON` as internal storage mechanism.
All you have to do is to conform your struct to `Encodable` and you're ready to go, Fridge will take care of the rest.
- `Fridge.freeze🧊(object, identifier)` -> Safely "freezes" your `struct` to persistant store
- `Fridge.unfreeze🪅🎉(identifier)` -> "Unfreezes" (previously frozen), `struct` giving you control of it
- `Fridge.isFrozen🔬(identifier)` -> Quickly check if your `Fridge` have been loaded with certain `struct`

Fridge storage mechanics are built on Foundation principles and use `BSON` as internal storage mechanism. All you have to do is to conform your struct to `Encodable` and you're ready to go, Fridge will take care of the rest.
Checkout internal documentation for more information.

### _"Talking is cheap. Show me the code."_ - Linus Torvalds
# Code examples
> _Talking is cheap. Show me the code._ - Linus Torvalds
```Swift
// there's a catch here (!)
struct GHRepo {
// Make your fancy struct conform to Decodable
struct GHRepo: Decodable {
var name: String
var repositoryURL: URL
}

// Call grab🔮 method...
let myRepo: GHRepo = try await Fridge.grab🔮(from: URL("https://github.com/vexy/")!)
// then, just

// Then, at your will
print(myRepo)
print(myRepo.name)
```

// ..and that's it ! (khm 🧟‍♂️)
```

### _"RTFM isn't a joke..."_
Aside from discovering how to remove that _shocking build error_ from the 👆 code example, in the **[Docs](Guides/Usage.md)** you'll quickly figure out how to:
# Documentation
> _RTFM isn't a joke..._ 🥴
In the **[Docs](Guides/Usage.md)** you'll quickly figure out how to:
- *easily fetch object* from the network,
- *persistently store* your objects,
- *load them back* into your app,
- *catch nasty errors* along the way
- (**COMMING SOON**) _fine grain_ fetch or store process
- dirtly little secrets about the Fridge
- all other dirtly little secrets about the Fridge

Oh yeah, `Fridge` can ofer way way more, _but it's alllll in the **Docs**_. Even the [architecture](Guides/Fridge.diagram.md) diagrams... ∰ 🥴
Check [usage examples](Guides/Examples) or entire [Guides](Guides/) collection for more goodies.
`Xcode Playground` file can be found [here](Guides/Examples/Fridge-basics).
For a bigger picture overview, feel free to check [architecture](Guides/Fridge.diagram.md) diagrams... ∰

# Installation
Using `Swift Package Manager` is by far the sexiest way to install silly `Fridge`.
Update both `dependencies` and `targets` section in your `Package.swift`, to look something like this.
Using `Swift Package Manager` is by far the sexiest way to install `Fridge`.
Update both `dependencies` and `targets` section in your `Package.swift`, to look something like this:

```Swift
// swift-tools-version:5.3
Expand All @@ -121,7 +124,7 @@ url: "https://github.com/vexy/Fridge.git"
branch: "main"
```

# Minimum versions required
## Minimum versions required
For `Fridge` to work in full capacity, following Swift & iOS configuration is _recommended_:
- Xcode `13.1+`
- Swift `5.5`
Expand All @@ -144,37 +147,27 @@ That may be helpfull for `Xcode 12.x`, assuming `Swift 5.x` is installed.

Checkout official [Swift.org](https://www.swift.org/) website, for supporting earlier than minimums and other info.

# Documentation & Examples
Documentation can be found in [Guides](Guides/) folder.
You can find [usage examples](Guides/Usage.md) or standalone sample projects. (TODO: ADD LINK HERE)

# External dependencies
## External dependencies
Fridge uses [BSONCoder v0.9](https://github.com/vexy/bsoncoder) - Copyright by [Vexy](https://github.com/vexy).
Check original library licencing information under licencing section in README file.

# Contribution
If you like `Fridge`, feel free to fire a [pull request](https://github.com/vexy/Fridge/pulls).
The prefered way is to branch off the `main` branch, complete feature or a fix and then merge to `development`.
After the pull request has been approved, your change will be merged to `main`.
# Contribution guidelines
If you like Fridge, feel free to fire a [pull request](https://github.com/vexy/Fridge/pulls).
The prefered way is to branch off the `main` branch, complete feature or a fix and then merge to `development`. After the pull request has been approved, your change will be merged to `main`.

Don't be affraid to start any [discussions](https://github.com/vexy/Fridge/discussions) if you think so.
[Issues](https://github.com/vexy/Fridge/issues) section is a good way to start, if you stumble upon the way.

---

<p align="center">
<b>Fridge</b> - perhaps the silliest <code>Swift5 async/await</code> fetch-and-store implementation you'll ever see !<br>
</p>


**FRIDGE IS UNDER ACTIVE DEVELOPMENT ALMOST REACHING v1.0**
Fridge **BETA** release : *v0.8.9 ( UTC2022-05-20 )*
(**FRIDGE IS UNDER ACTIVE DEVELOPMENT ALMOST REACHING v1.0**)
Fridge **BETA** release : *v0.9 ( UTC2022-05-28 )*

Copyright © 2016 Veljko Tekelerović | MIT license
**PGP:** `6302D860 B74CBD34 6482DBA2 518766D0 8213DBC0`
**PGP:** `6302 D860 B74C BD34 6482 DBA2 5187 66D0 8213 DBC0`


<p align="center">
<code>Fridge</code> - <b>Lightweight</b>, <b>fast</b> and extreeemely <b>simple to use fetch or store mechanism.</b><br>
<a href="https://stackexchange.com/users/215166"><img src="https://stackexchange.com/users/flair/215166.png?theme=clean" width="208" height="58" alt="profile for Vexy on Stack Exchange, a network of free, community-driven Q&amp;A sites" title="profile for Vexy on Stack Exchange, a network of free, community-driven Q&amp;A sites">
</a>
</p>
<br>
3 changes: 3 additions & 0 deletions Sources/Fridge/Errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
import Foundation

enum FridgeErrors: Error {
//add case grabFailed(reason: Error) or similar
case grabFailed
case pushFailed
case decodingFailed
}

enum FreezingErrors: Error {
Expand Down
7 changes: 7 additions & 0 deletions Sources/Fridge/Fridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ extension Fridge {
let theObject: D = try await grb.grab(using: urlRequest)
return theObject
}

/// Tries to push an object to a given URL, returning the structured response
public static func push📡<E: Encodable, D: Decodable>(_ object: E, to: String) async throws -> D {
let pusher = Grabber()
let pushResponseObject: D = try await pusher.push(object: object, urlString: to)
return pushResponseObject
}
}

//MARK: - Object persistent storage
Expand Down
64 changes: 42 additions & 22 deletions Sources/Fridge/Grabber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,34 +31,54 @@ import Foundation
@available(iOS 15.0, *)
final internal class Grabber {
func grab<D: Decodable>(from url: URL) async throws -> D {
do {
let (data, _) = try await URLSession.shared.data(from: url)

// try to serialize the data
guard let decodedObject = try? JSONDecoder().decode(D.self, from: data) else {
throw FridgeErrors.grabFailed
}

// return grabbed object
return decodedObject
} catch {
guard let rawData = try? await URLSession.shared.data(from: url).0 else {
throw FridgeErrors.grabFailed
}
guard let decodedObject = try? JSONDecoder().decode(D.self, from: rawData) else {
throw FridgeErrors.decodingFailed
}

// return decoded object
return decodedObject
}

func grab<D: Decodable>(using urlRequest: URLRequest) async throws -> D {
do {
let (data, _) = try await URLSession.shared.data(for: urlRequest)

// serialize returned data
guard let decodedObject = try? JSONDecoder().decode(D.self, from: data) else {
throw FridgeErrors.grabFailed
}

// finally returned real object
return decodedObject
} catch {
guard let rawData = try? await URLSession.shared.data(for: urlRequest).0 else {
throw FridgeErrors.grabFailed
}
guard let decodedObject = try? JSONDecoder().decode(D.self, from: rawData) else {
throw FridgeErrors.grabFailed
}

// return decoded object
return decodedObject
}


func push<E: Encodable, D: Decodable>(object: E, urlString: String) async throws -> D {
//construct a JSON based URLRequest
guard let urlObject = URL(string: urlString) else { throw FridgeErrors.decodingFailed }
var request = URLRequest(url: urlObject)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Fridge.grab", forHTTPHeaderField: "User-Agent")

// serialize given object and attach it to request body
guard let serializedObject = try? JSONEncoder().encode(object.self) else {
throw FridgeErrors.decodingFailed
}
request.httpBody = serializedObject

//execute request and wait for response
guard let responseRawData = try? await URLSession.shared.data(for: request).0 else {
throw FridgeErrors.pushFailed
}

// try to decode the data into given respose object
guard let decodedResponse = try? JSONDecoder().decode(D.self, from: responseRawData) else {
throw FridgeErrors.decodingFailed
}

return decodedResponse
}
}

0 comments on commit 48f1ae4

Please sign in to comment.