From 48f1ae409c9c8ef1b460ec5ece9218613e46d945 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Veljko=20Tekelerovi=C4=87?=
Date: Sat, 28 May 2022 03:06:28 +0200
Subject: [PATCH] Fridge 0.9
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 b2b6dbe0a6103b8f5805dc77702e20a6e15ec6fc
commit 7196555823defa8c2dd9465cae194de93fbf6d7b
---
.../Fridge basics.playground/Contents.swift | 54 ++++++++++
.../contents.xcplayground | 4 +
Guides/Usage.md | 23 +++-
Package.swift | 2 +-
README.md | 101 ++++++++----------
Sources/Fridge/Errors.swift | 3 +
Sources/Fridge/Fridge.swift | 7 ++
Sources/Fridge/Grabber.swift | 64 +++++++----
8 files changed, 176 insertions(+), 82 deletions(-)
create mode 100644 Guides/Examples/Fridge basics.playground/Contents.swift
create mode 100644 Guides/Examples/Fridge basics.playground/contents.xcplayground
diff --git a/Guides/Examples/Fridge basics.playground/Contents.swift b/Guides/Examples/Fridge basics.playground/Contents.swift
new file mode 100644
index 0000000..5ff730c
--- /dev/null
+++ b/Guides/Examples/Fridge basics.playground/Contents.swift
@@ -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")
+ }
+}
diff --git a/Guides/Examples/Fridge basics.playground/contents.xcplayground b/Guides/Examples/Fridge basics.playground/contents.xcplayground
new file mode 100644
index 0000000..ebe9554
--- /dev/null
+++ b/Guides/Examples/Fridge basics.playground/contents.xcplayground
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/Guides/Usage.md b/Guides/Usage.md
index b2339e7..9aedf75 100644
--- a/Guides/Usage.md
+++ b/Guides/Usage.md
@@ -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
@@ -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.
diff --git a/Package.swift b/Package.swift
index 6a4b236..9585806 100644
--- a/Package.swift
+++ b/Package.swift
@@ -35,7 +35,7 @@ let package = Package(
.product(name: "bsoncoder", package: "BSONCoder")
],
exclude: [
- "../../Guides/",
+ "../../Guides",
"../../README.md"
]
),
diff --git a/README.md b/README.md
index 67a5c75..e9ca4dd 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,8 @@
- Fridge
- Lightweight, asnyc and extreeemely simple to use fetch or store mechanism.
-
- Let your fancy struct
(s) raise and shine again, while focusing on 💬 🥊🤖⭐️🗝 other important stuff.
+ Fridge - extremely simple async/await
fetch-and-store implementation you'll ever see !
+ Let your fancy struct
(s) raise and shine again, allowing you to focus on 💬 🥊🤖⭐️🗝 stuff.
@@ -43,61 +42,65 @@
-
# 💠 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
@@ -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`
@@ -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.
---
-
-
- Fridge - perhaps the silliest Swift5 async/await
fetch-and-store implementation you'll ever see !
-
-
-
-**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`
+
+ Fridge
- Lightweight, fast and extreeemely simple to use fetch or store mechanism.
-
diff --git a/Sources/Fridge/Errors.swift b/Sources/Fridge/Errors.swift
index 9f08c2c..d42abf6 100644
--- a/Sources/Fridge/Errors.swift
+++ b/Sources/Fridge/Errors.swift
@@ -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 {
diff --git a/Sources/Fridge/Fridge.swift b/Sources/Fridge/Fridge.swift
index 4c7aef9..e38b782 100644
--- a/Sources/Fridge/Fridge.swift
+++ b/Sources/Fridge/Fridge.swift
@@ -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📡(_ 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
diff --git a/Sources/Fridge/Grabber.swift b/Sources/Fridge/Grabber.swift
index 592e269..cb87f4e 100644
--- a/Sources/Fridge/Grabber.swift
+++ b/Sources/Fridge/Grabber.swift
@@ -31,34 +31,54 @@ import Foundation
@available(iOS 15.0, *)
final internal class Grabber {
func grab(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(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(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
}
}