Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create HTTP Post #16

Merged
merged 12 commits into from
Jan 16, 2018
182 changes: 141 additions & 41 deletions Frisbee.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions Sources/Frisbee/Entities/FrisbeeEncodable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

protocol FrisbeeEncodable {
func encode<T: Encodable>(_ value: T) throws -> Data
}
7 changes: 7 additions & 0 deletions Sources/Frisbee/Entities/FrisbeeJSONEncoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

class FrisbeeJSONEncoder: FrisbeeEncodable {
func encode<T: Encodable>(_ value: T) throws -> Data {
return try JSONEncoder().encode(value)
}
}
7 changes: 7 additions & 0 deletions Sources/Frisbee/Entities/FrisbeeJSONSerializable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

class FrisbeeJSONSerializable: FrisbeeSerializable {
func object(with data: Data, options opt: JSONSerialization.ReadingOptions = []) throws -> Any {
return try JSONSerialization.jsonObject(with: data, options: opt)
}
}
5 changes: 5 additions & 0 deletions Sources/Frisbee/Entities/FrisbeeSerializable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

protocol FrisbeeSerializable {
func object(with data: Data, options opt: JSONSerialization.ReadingOptions) throws -> Any
}
8 changes: 4 additions & 4 deletions Sources/Frisbee/Entities/Result.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
public enum Result<Entity> {
case success(Entity)
public enum Result<T> {
case success(T)
case fail(FrisbeeError)
}

extension Result {
public var data: Entity? {
public var data: T? {
guard case let .success(data) = self else {
return nil
}
Expand All @@ -30,7 +30,7 @@ extension Result: Equatable {
}
}

extension Result where Entity: Equatable {
extension Result where T: Equatable {
public static func == (lhs: Result, rhs: Result) -> Bool {
switch (lhs, rhs) {
case let (.success(lhs), .success(rhs)):
Expand Down
8 changes: 8 additions & 0 deletions Sources/Frisbee/Factories/BodyBuildableFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
struct BodyBuildableFactory {

static func make() -> BodyBuildable {
return BodyBuilder(encoder: FrisbeeEncodableFactory.make(),
serializer: FrisbeeJSONSerializableFactroy.make())
}

}
7 changes: 7 additions & 0 deletions Sources/Frisbee/Factories/FrisbeeEncodableFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class FrisbeeEncodableFactory {

static func make() -> FrisbeeEncodable {
return FrisbeeJSONEncoder()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class FrisbeeJSONSerializableFactroy {

static func make() -> FrisbeeSerializable {
return FrisbeeJSONSerializable()
}

}
2 changes: 1 addition & 1 deletion Sources/Frisbee/Factories/ResultGeneratorFactory.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
struct ResultGeneratorFactory {

static func make<Entity: Decodable>() -> ResultGenerator<Entity> {
static func make<T: Decodable>() -> ResultGenerator<T> {
return ResultGenerator(decoder: FrisbeeDecodableFacotry.make())
}

Expand Down
2 changes: 2 additions & 0 deletions Sources/Frisbee/Factories/URLRequestFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ struct URLRequestFactory {
static func make(_ httpMethod: HTTPMethod, _ url: URL) -> URLRequest {
var request = URLRequest(url: url)
request.httpMethod = httpMethod.rawValue
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept")

return request
}
Expand Down
5 changes: 5 additions & 0 deletions Sources/Frisbee/Interactors/BodyBuildable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

protocol BodyBuildable {
func build<T: Encodable>(withBody body: T) throws -> [String: Any]
}
23 changes: 23 additions & 0 deletions Sources/Frisbee/Interactors/BodyBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Foundation

struct BodyBuilder: BodyBuildable {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create tests for this struct

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bodybuilder-porra


private let encoder: FrisbeeEncodable
private let serializer: FrisbeeSerializable

init(encoder: FrisbeeEncodable, serializer: FrisbeeSerializable) {
self.encoder = encoder
self.serializer = serializer
}

func build<T: Encodable>(withBody body: T) throws -> [String: Any] {
var json: [String: Any] = [:]

let data = try encoder.encode(body)
let jsonObject = try serializer.object(with: data, options: [])
if let jsonDictionary = jsonObject as? [String: Any] { json = jsonDictionary }

return json
}

}
14 changes: 14 additions & 0 deletions Sources/Frisbee/Interactors/DataTaskRunner.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Foundation

final class DataTaskRunner {

static func run<T: Decodable>(with urlSession: URLSession, request: URLRequest,
onComplete: @escaping OnComplete<T>) {
let task = urlSession.dataTask(with: request) { data, _, error in
onComplete(ResultGeneratorFactory.make().generate(data: data, error: error))
}

task.resume()
}

}
4 changes: 2 additions & 2 deletions Sources/Frisbee/Interactors/QueryItemBuilder.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import Foundation

struct QueryItemBuilder<Entity: Encodable> {
struct QueryItemBuilder<T: Encodable> {

static func build(withEntity entity: Entity) throws -> [URLQueryItem] {
static func build(withEntity entity: T) throws -> [URLQueryItem] {
var json: [String: Any] = [:]

let data = try JSONEncoder().encode(entity)
Expand Down
8 changes: 4 additions & 4 deletions Sources/Frisbee/Interactors/ResultGenerator.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import Foundation

struct ResultGenerator<Entity: Decodable> {
struct ResultGenerator<T: Decodable> {

private let decoder: FrisbeeDecodable

init(decoder: FrisbeeDecodable) {
self.decoder = decoder
}

func generate(data: Data?, error: Error?) -> Result<Entity> {
func generate(data: Data?, error: Error?) -> Result<T> {
if let data = data {
let result: Result<Entity>
let result: Result<T>
do {
let entityDecoded = try decoder.decode(Entity.self, from: data)
let entityDecoded = try decoder.decode(T.self, from: data)
result = Result.success(entityDecoded)
} catch {
let noDataError = FrisbeeError.noData
Expand Down
4 changes: 2 additions & 2 deletions Sources/Frisbee/Interactors/URLWithQueryBuildable.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

protocol URLWithQueryBuildable {
func build<Query: Encodable>(withUrl url: String, query: Query) throws -> URL
func build<Query: Encodable>(withUrl url: URL, query: Query) throws -> URL
func build<T: Encodable>(withUrl url: String, query: T) throws -> URL
func build<T: Encodable>(withUrl url: URL, query: T) throws -> URL
}
4 changes: 2 additions & 2 deletions Sources/Frisbee/Interactors/URLWithQueryBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import Foundation

struct URLWithQueryBuilder: URLWithQueryBuildable {

func build<Query: Encodable>(withUrl url: String, query: Query) throws -> URL {
func build<T: Encodable>(withUrl url: String, query: T) throws -> URL {
guard let actualUrl = URL(string: url) else {
throw FrisbeeError.invalidUrl
}
return try build(withUrl: actualUrl, query: query)
}

func build<Query: Encodable>(withUrl url: URL, query: Query) throws -> URL {
func build<T: Encodable>(withUrl url: URL, query: T) throws -> URL {
var url = url
var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true)
urlComponents?.queryItems = try QueryItemBuilder.build(withEntity: query)
Expand Down
10 changes: 4 additions & 6 deletions Sources/Frisbee/Requestables/Getable.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import Foundation

public protocol Getable {
func get<Entity: Decodable>(url: String, onComplete: @escaping (Result<Entity>) -> Void)
func get<Entity: Decodable>(url: URL, onComplete: @escaping (Result<Entity>) -> Void)
func get<Entity: Decodable, Query: Encodable>(url: String, query: Query,
onComplete: @escaping (Result<Entity>) -> Void)
func get<Entity: Decodable, Query: Encodable>(url: URL, query: Query,
onComplete: @escaping (Result<Entity>) -> Void)
func get<T: Decodable>(url: String, onComplete: @escaping OnComplete<T>)
func get<T: Decodable>(url: URL, onComplete: @escaping OnComplete<T>)
func get<T: Decodable, U: Encodable>(url: String, query: U, onComplete: @escaping OnComplete<T>)
func get<T: Decodable, U: Encodable>(url: URL, query: U, onComplete: @escaping OnComplete<T>)
}
18 changes: 6 additions & 12 deletions Sources/Frisbee/Requestables/NetworkGet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,25 @@ public class NetworkGet: Getable {
self.urlSession = urlSession
}

public func get<Entity: Decodable>(url: String, onComplete: @escaping (Result<Entity>) -> Void) {
public func get<T: Decodable>(url: String, onComplete: @escaping OnComplete<T>) {
guard let url = URL(string: url) else {
return onComplete(.fail(FrisbeeError.invalidUrl))
}
makeRequest(url: url, onComplete: onComplete)
}

public func get<Entity: Decodable>(url: URL, onComplete: @escaping (Result<Entity>) -> Void) {
public func get<T: Decodable>(url: URL, onComplete: @escaping OnComplete<T>) {
makeRequest(url: url, onComplete: onComplete)
}

public func get<Entity: Decodable, Query: Encodable>(url: String, query: Query,
onComplete: @escaping (Result<Entity>) -> Void) {
public func get<T: Decodable, U: Encodable>(url: String, query: U, onComplete: @escaping OnComplete<T>) {
guard let url = URL(string: url) else {
return onComplete(.fail(FrisbeeError.invalidUrl))
}
get(url: url, query: query, onComplete: onComplete)
}

public func get<Entity: Decodable, Query: Encodable>(url: URL, query: Query,
onComplete: @escaping (Result<Entity>) -> Void) {
public func get<T: Decodable, U: Encodable>(url: URL, query: U, onComplete: @escaping OnComplete<T>) {
do {
let url = try queryBuilder.build(withUrl: url, query: query)
makeRequest(url: url, onComplete: onComplete)
Expand All @@ -50,14 +48,10 @@ public class NetworkGet: Getable {
}
}

private func makeRequest<Entity: Decodable>(url: URL, onComplete: @escaping (Result<Entity>) -> Void) {
private func makeRequest<T: Decodable>(url: URL, onComplete: @escaping (Result<T>) -> Void) {
let request = URLRequestFactory.make(.GET, url)

let task = urlSession.dataTask(with: request) { data, _, error in
onComplete(ResultGeneratorFactory.make().generate(data: data, error: error))
}

task.resume()
DataTaskRunner.run(with: urlSession, request: request, onComplete: onComplete)
}

}
60 changes: 60 additions & 0 deletions Sources/Frisbee/Requestables/NetworkPost.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Foundation

public final class NetworkPost: Postable {

let urlSession: URLSession
private let bodyBuilder: BodyBuildable

public convenience init() {
self.init(urlSession: URLSessionFactory.make(), bodyBuilder: BodyBuildableFactory.make())
}

public convenience init(urlSession: URLSession) {
self.init(urlSession: urlSession, bodyBuilder: BodyBuildableFactory.make())
}

init(urlSession: URLSession, bodyBuilder: BodyBuildable) {
self.urlSession = urlSession
self.bodyBuilder = bodyBuilder
}

public func post<T: Decodable>(url: String, onComplete: @escaping OnComplete<T>) {
guard let url = URL(string: url) else {
return onComplete(.fail(FrisbeeError.invalidUrl))
}
makeRequest(url: url, onComplete: onComplete)
}

public func post<T: Decodable>(url: URL, onComplete: @escaping OnComplete<T>) {
makeRequest(url: url, onComplete: onComplete)
}

public func post<T: Decodable, U: Encodable>(url: String, body: U, onComplete: @escaping OnComplete<T>) {
guard let url = URL(string: url) else {
return onComplete(.fail(FrisbeeError.invalidUrl))
}
makeRequest(url: url, body: body, onComplete: onComplete)
}

public func post<T: Decodable, U: Encodable>(url: URL, body: U, onComplete: @escaping OnComplete<T>) {
makeRequest(url: url, body: body, onComplete: onComplete)
}

private func makeRequest<T: Decodable>(url: URL, onComplete: @escaping OnComplete<T>) {
let request = URLRequestFactory.make(.POST, url)
DataTaskRunner.run(with: urlSession, request: request, onComplete: onComplete)
}

private func makeRequest<T: Decodable, U: Encodable>(url: URL, body: U, onComplete: @escaping OnComplete<T>) {
var request = URLRequestFactory.make(.POST, url)
do {
let bodyObject = try bodyBuilder.build(withBody: body)
request.httpBody = try JSONSerialization.data(withJSONObject: bodyObject, options: [])
} catch {
return onComplete(.fail(FrisbeeError(error)))
}

DataTaskRunner.run(with: urlSession, request: request, onComplete: onComplete)
}

}
1 change: 1 addition & 0 deletions Sources/Frisbee/Requestables/OnComplete.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public typealias OnComplete<T: Decodable> = (Result<T>) -> Void
8 changes: 8 additions & 0 deletions Sources/Frisbee/Requestables/Postable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

protocol Postable {
func post<T: Decodable>(url: String, onComplete: @escaping OnComplete<T>)
func post<T: Decodable>(url: URL, onComplete: @escaping OnComplete<T>)
func post<T: Decodable, U: Encodable>(url: String, body: U, onComplete: @escaping OnComplete<T>)
func post<T: Decodable, U: Encodable>(url: URL, body: U, onComplete: @escaping OnComplete<T>)
}
31 changes: 31 additions & 0 deletions Tests/FrisbeeTests/Interactors/BodyBuilderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import XCTest
@testable import Frisbee

final class BodyBuilderTests: XCTestCase {

override func setUp() {
super.setUp()
}

func testBuildWithBodyWhenEncoderThrowsAnErrorThenThrows() {
let builder = BodyBuilder(encoder: FrisbeeThrowErrorFakeEncoder(),
serializer: FrisbeeJSONSerializableFactroy.make())

XCTAssertThrowsError(try builder.build(withBody: Fake(fake: "")))
}

func testBuildWithBodyWhenSerializerThrowsAnErrorThenThrows() {
let builder = BodyBuilder(encoder: FrisbeeEncodableFactory.make(),
serializer: FrisbeeThrowErrorFakeSerializer())

XCTAssertThrowsError(try builder.build(withBody: Fake(fake: "")))
}

static let allTests = [
("testBuildWithBodyWhenEncoderThrowsAnErrorThenThrows",
testBuildWithBodyWhenEncoderThrowsAnErrorThenThrows),
("testBuildWithBodyWhenSerializerThrowsAnErrorThenThrows",
testBuildWithBodyWhenSerializerThrowsAnErrorThenThrows)
]

}
15 changes: 1 addition & 14 deletions Tests/FrisbeeTests/Interactors/ResultGeneratorTests.swift
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
import XCTest
@testable import Frisbee

struct Fake: Codable { let fake: String }

class FrisbeeStubDecodable: FrisbeeDecodable {
var error: FrisbeeError!

func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
throw error
}

}

final class ResultGeneratorTests: XCTestCase {

private let someError = NSError(domain: "Some error", code: 33, userInfo: nil)
private let fakeString = "Fake Fake"

func testGenerateResultWhenEncoderTrhowAnerrorThenGenerateFailResult() {
let frisbeDecoder = FrisbeeStubDecodable()
frisbeDecoder.error = .invalidEntity
let data = try? JSONEncoder().encode(Fake(fake: fakeString))
let resultGenerator = ResultGenerator<Fake>(decoder: frisbeDecoder)
let resultGenerator = ResultGenerator<Fake>(decoder: FrisbeeThrowErrorFakeDecodable())

let result = resultGenerator.generate(data: data, error: nil)

Expand Down
Loading