Skip to content

Complete overhaul #6

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

Merged
merged 2 commits into from
Sep 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
osx_image: xcode9
language: objective-c
before_script: brew update && brew upgrade swiftlint
stage: Build
env:
matrix:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ PlaygroundPage.current.needsIndefiniteExecution = true

initializeParse()

ParseSwift.initialize(applicationId: "applicationId",
clientKey: "clientKey",
masterKey: "masterKey",
serverURL: URL(string: "http://localhost:1337/1")!)

struct GameScore: ParseSwift.ObjectType {
//: Those are required for Object
var objectId: String?
Expand All @@ -28,7 +33,7 @@ struct GameScore: ParseSwift.ObjectType {

var score = GameScore(score: 10)

guard let score = try? score.sync.save() else { fatalError() }
guard let score = try? score.save() else { fatalError() }
assert(score.objectId != nil)
assert(score.createdAt != nil)
assert(score.updatedAt != nil)
Expand All @@ -37,19 +42,18 @@ assert(score.score == 10)
// Need to make it a var as Value Types
var changedScore = score
changedScore.score = 200
guard let savedScore = try? changedScore.sync.save() else { fatalError() }
guard let savedScore = try? changedScore.save() else { fatalError() }
assert(score.score == 10)
assert(score.objectId == changedScore.objectId)

// TODO: Add support for sync saveAll
let score2 = GameScore(score: 3)
guard let results = try? GameScore.saveAllSync(score, score2) else { fatalError() }
guard let results = try? GameScore.saveAll(score, score2) else { fatalError() }
results.forEach { (result) in
let (_, error) = result
assert(error == nil, "error should be nil")
}

guard let otherResults = try? [score, score2].saveAllSync() else { fatalError() }
guard let otherResults = try? [score, score2].saveAll() else { fatalError() }
otherResults.forEach { (result) in
let (_, error) = result
assert(error == nil, "error should be nil")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ struct GameScore: ParseSwift.ObjectType {

var score = GameScore()
score.score = 200

score.save { _ in
var query = GameScore.query("score" > 100, "createdAt" < Date().addingTimeInterval(-300))
query.limit(2).find { (scores) in
print(scores)
}
try score.save()

let afterDate = Date().addingTimeInterval(-300)
var query = GameScore.query("score" > 100, "createdAt" > afterDate)
let results = try query.limit(2).find(options: [])
assert(results.count >= 1)
results.forEach { (score) in
guard let createdAt = score.createdAt else { fatalError() }
assert(createdAt.timeIntervalSince1970 > afterDate.timeIntervalSince1970, "date should be ok")
}

//: [Next](@next)
Original file line number Diff line number Diff line change
Expand Up @@ -46,31 +46,23 @@ struct User: ParseSwift.UserType {
// }
//}

User.signup(username: "hello10", password: "world") { (response) in
guard case .success(var user) = response else { return }
print(user)
}

User.login(username: "hello", password: "world") { (response) in
guard case .success(var user) = response else { return }
do {
let user = try User.signup(username: "hello10", password: "world")
var loggedIn = try User.login(username: "hello", password: "workd")
var acl = user.ACL
acl?.publicRead = false
acl?.publicWrite = true
user.ACL = acl
user.save { response in
switch response {
case .success:
assert(true)
case .error:
assert(false)
default: break
}
}
loggedIn.ACL = acl
try loggedIn.save()
} catch let e {
e
e.localizedDescription
fatalError("\(e.localizedDescription)")
}

var acl = ACL()
acl.publicRead = true
acl.setReadAccess(userId: "dsas", value: true)
acl.setWriteAccess(userId: "dsas", value: true)
//var acl = ACL()
//acl.publicRead = true
//acl.setReadAccess(userId: "dsas", value: true)
//acl.setWriteAccess(userId: "dsas", value: true)

//: [Next](@next)
64 changes: 35 additions & 29 deletions ParseSwift.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

140 changes: 140 additions & 0 deletions Sources/ParseSwift/API/API+Commands.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//
// API+Commands.swift
// ParseSwift (iOS)
//
// Created by Florent Vilmart on 17-09-24.
// Copyright © 2017 Parse. All rights reserved.
//

import Foundation

internal extension API {
internal struct Command<T, U>: Encodable where T: Encodable {
typealias ReturnType = U
let method: API.Method
let path: API.Endpoint
let body: T?
let mapper: ((Data) throws -> U)
let params: [String: String?]?

internal var data: Data? {
return try? getJSONEncoder().encode(body)
}

init(method: API.Method,
path: API.Endpoint,
params: [String: String]? = nil,
body: T? = nil,
mapper: @escaping ((Data) throws -> U)) {
self.method = method
self.path = path
self.body = body
self.mapper = mapper
self.params = params
}

public func execute(options: API.Options) throws -> U {
let params = self.params?.getQueryItems()
let headers = API.getHeaders(options: options)
let url = ParseConfiguration.serverURL.appendingPathComponent(path.urlComponent)

var components = URLComponents(url: url, resolvingAgainstBaseURL: false)!
components.queryItems = params

var urlRequest = URLRequest(url: components.url!)
urlRequest.allHTTPHeaderFields = headers
if let body = data {
urlRequest.httpBody = body
}
urlRequest.httpMethod = method.rawValue
let responseData = try URLSession.shared.syncDataTask(with: urlRequest)
do {
return try mapper(responseData)
} catch _ {
throw try getDecoder().decode(ParseError.self, from: responseData)
}
}

enum CodingKeys: String, CodingKey { // swiftlint:disable:this nesting
case method, body, path
}
}
}

internal extension API.Command {
// MARK: Saving
internal static func saveCommand<T>(_ object: T) -> API.Command<T, T> where T: ObjectType {
if object.isSaved {
return updateCommand(object)
}
return createCommand(object)
}

// MARK: Saving - private
private static func createCommand<T>(_ object: T) -> API.Command<T, T> where T: ObjectType {
let mapper = { (data) -> T in
try getDecoder().decode(SaveResponse.self, from: data).apply(object)
}
return API.Command<T, T>(method: .POST,
path: object.endpoint,
body: object,
mapper: mapper)
}

private static func updateCommand<T>(_ object: T) -> API.Command<T, T> where T: ObjectType {
let mapper = { (data: Data) -> T in
try getDecoder().decode(UpdateResponse.self, from: data).apply(object)
}
return API.Command<T, T>(method: .PUT,
path: object.endpoint,
body: object,
mapper: mapper)
}

// MARK: Fetching
internal static func fetchCommand<T>(_ object: T) throws -> API.Command<T, T> where T: ObjectType {
guard object.isSaved else {
throw ParseError(code: .unknownError, message: "Cannot Fetch an object without id")
}
return API.Command<T, T>(method: .GET,
path: object.endpoint) { (data) -> T in
try getDecoder().decode(T.self, from: data)
}
}
}

extension API.Command where T: ObjectType {

internal var data: Data? {
guard let body = body else { return nil }
return try? body.getEncoder().encode(body)
}

static func batch(commands: [API.Command<T, T>]) -> RESTBatchCommandType<T> {
let commands = commands.flatMap { (command) -> API.Command<T, T>? in
let path = ParseConfiguration.mountPath + command.path.urlComponent
guard let body = command.body else {
return nil
}
return API.Command<T, T>(method: command.method, path: .any(path),
body: body, mapper: command.mapper)
}
let bodies = commands.flatMap { (command) -> T? in
return command.body
}
let mapper = { (data: Data) -> [(T, ParseError?)] in
let decodingType = [BatchResponseItem<SaveOrUpdateResponse>].self
let responses = try getDecoder().decode(decodingType, from: data)
return bodies.enumerated().map({ (object) -> (T, ParseError?) in
let response = responses[object.0]
if let success = response.success {
return (success.apply(object.1), nil)
} else {
return (object.1, response.error)
}
})
}
let batchCommand = BatchCommand(requests: commands)
return RESTBatchCommandType<T>(method: .POST, path: .batch, body: batchCommand, mapper: mapper)
}
}
79 changes: 41 additions & 38 deletions Sources/ParseSwift/API.swift → Sources/ParseSwift/API/API.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// API.swift
// Parse (iOS)
// ParseSwift
//
// Created by Florent Vilmart on 17-08-19.
// Copyright © 2017 Parse. All rights reserved.
Expand All @@ -10,11 +10,11 @@ import Foundation

public struct API {

public enum Method: String, Encodable {
internal enum Method: String, Encodable {
case GET, POST, PUT, DELETE
}

public enum Endpoint: Encodable {
internal enum Endpoint: Encodable {
case batch
case objects(className: String)
case object(className: String, objectId: String)
Expand Down Expand Up @@ -48,57 +48,60 @@ public struct API {
}
}

public struct Option: OptionSet {
public let rawValue: UInt
public init(rawValue: UInt) {
self.rawValue = rawValue
public typealias Options = Set<API.Option>

public enum Option: Hashable {
case useMasterKey
case sessionToken(String)
case installationId(String)

// use HashValue so we can use in a sets
public var hashValue: Int {
switch self {
case .useMasterKey:
return 1
case .sessionToken:
return 2
case .installationId:
return 3
}
}

public static func == (lhs: API.Option, rhs: API.Option) -> Bool {
return lhs.hashValue == rhs.hashValue
}
static let useMasterKey = Option(rawValue: 1 << 0)
}

private static func getHeaders(useMasterKey: Bool = false) -> [String: String] {
internal static func getHeaders(options: API.Options) -> [String: String] {
var headers: [String: String] = ["X-Parse-Application-Id": ParseConfiguration.applicationId,
"Content-Type": "application/json"]
if let clientKey = ParseConfiguration.clientKey {
headers["X-Parse-Client-Key"] = clientKey
}
if useMasterKey,
let masterKey = ParseConfiguration.masterKey {
headers["X-Parse-Master-Key"] = masterKey
}

if let token = CurrentUserInfo.currentSessionToken {
headers["X-Parse-Session-Token"] = token
}

options.forEach { (option) in
switch option {
case .useMasterKey:
headers["X-Parse-Master-Key"] = ParseConfiguration.masterKey
case .sessionToken(let sessionToken):
headers["X-Parse-Session-Token"] = sessionToken
case .installationId(let installationId):
headers["X-Parse-Installation-Id"] = installationId
}
}

return headers
}
}

public typealias Response = (Result<Data>) -> Void

internal static func request(method: Method,
path: Endpoint,
params: [URLQueryItem]? = nil,
body: Data? = nil,
options: Option,
callback: Response? = nil) -> URLSessionDataTask {

let headers = getHeaders(useMasterKey: options.contains(.useMasterKey))
let url = ParseConfiguration.serverURL.appendingPathComponent(path.urlComponent)

var components = URLComponents(url: url, resolvingAgainstBaseURL: false)!
components.queryItems = params

var urlRequest = URLRequest(url: components.url!)
urlRequest.allHTTPHeaderFields = headers
if let body = body {
urlRequest.httpBody = body
}
urlRequest.httpMethod = method.rawValue
let task = URLSession.shared.dataTask(with: urlRequest) { (data, _, error) in
callback?(Result(data, error))
internal extension Dictionary where Key == String, Value == String? {
func getQueryItems() -> [URLQueryItem] {
return map { (key, value) -> URLQueryItem in
return URLQueryItem(name: key, value: value)
}
task.resume()
return task
}
}
Loading