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

Async API+Command #15

Merged
merged 48 commits into from
Aug 4, 2020
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
cef5ec1
Initial async
cbaker6 Jul 23, 2020
d67da84
Add saveAsync and fetchAsync to object. Bump project to iOS 13 target…
cbaker6 Jul 24, 2020
01abe7e
Make sync execute wrap async
cbaker6 Jul 24, 2020
40e900f
Update filename in comments
cbaker6 Jul 24, 2020
4156e8d
Remove import Combine
cbaker6 Jul 24, 2020
6227b05
Add everything except for test cases
cbaker6 Jul 25, 2020
35f6dd4
Report error in fetchAsync
cbaker6 Jul 25, 2020
94a9f55
Update file comments
cbaker6 Jul 25, 2020
59902fc
Fix force unwrapping in API. It looks like there will already be a me…
cbaker6 Jul 25, 2020
1bd2191
Update URLResponse filename
cbaker6 Jul 25, 2020
25a66c9
Update copyright on new files to Parse Community
cbaker6 Jul 26, 2020
34ad07b
Prepare for merge:
cbaker6 Jul 27, 2020
873dbec
Update copyright year in files
cbaker6 Jul 27, 2020
8f9e6ed
Additional changes
cbaker6 Jul 27, 2020
41bb74d
Add callbackQueue to return to for async calls
cbaker6 Jul 28, 2020
76cd86a
Async logout, cleanup callback queue, add async to query protocol
cbaker6 Jul 28, 2020
ff5d3d7
Clean up
cbaker6 Jul 28, 2020
6c598c7
Switch async responses to Result enum
cbaker6 Jul 28, 2020
2bc3556
Fix force unwrap in API
cbaker6 Jul 28, 2020
8a8b386
minor var name update in API
cbaker6 Jul 28, 2020
0f381d3
Make batch save/update act similar to regular save/update
cbaker6 Jul 29, 2020
8740c18
Differentiate between server and ParseError
cbaker6 Jul 29, 2020
691efe7
Clean up
cbaker6 Jul 29, 2020
2f94b9f
merge with main and add new testcases
cbaker6 Jul 29, 2020
6cee7ac
Check for thrown error in test cases instead...
cbaker6 Jul 30, 2020
86ce687
merge master
cbaker6 Aug 1, 2020
50c1e63
Add URLSession to tv and watchOS targets
cbaker6 Aug 1, 2020
a138840
update min codecov
cbaker6 Aug 1, 2020
20e8f8e
Review fixes
cbaker6 Aug 2, 2020
116147e
More fixes
cbaker6 Aug 2, 2020
250d436
Merge branch 'master'
cbaker6 Aug 2, 2020
cc377b8
Fix actions badge in readme
cbaker6 Aug 2, 2020
32268a0
update action name
cbaker6 Aug 2, 2020
fcb7710
update workflow names
cbaker6 Aug 2, 2020
6cbb51d
update workflow names
cbaker6 Aug 2, 2020
f6331c2
Improving test cases by using TestDecoder for dates. Add FetchResponse
cbaker6 Aug 3, 2020
4a01706
update badges and action name
cbaker6 Aug 3, 2020
6d1c100
update action workflow names
cbaker6 Aug 3, 2020
d5fe359
Test Xcode env var
cbaker6 Aug 3, 2020
d5fb7ef
fix env var
cbaker6 Aug 3, 2020
c0e41dd
try again on env var
cbaker6 Aug 3, 2020
d5d2fe9
test old Xcode ver. shouldn't build
cbaker6 Aug 3, 2020
5d04430
test ci env var
cbaker6 Aug 3, 2020
f07601e
Leave current ver for CI for now
cbaker6 Aug 3, 2020
2dd89d0
remove OS dependency in actions. Fix actions badge link
cbaker6 Aug 3, 2020
395a855
update URLSession extension
cbaker6 Aug 3, 2020
329b77f
switch to apply to
cbaker6 Aug 3, 2020
7fa683f
actions: pipefail
cbaker6 Aug 4, 2020
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
2 changes: 1 addition & 1 deletion .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ coverage:
changes: false
project:
default:
target: 30
target: 45
comment:
require_changes: true
47 changes: 34 additions & 13 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
@@ -1,61 +1,77 @@
name: Swift
name: build

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

env:
CI_XCODE_VER: '/Applications/Xcode_11.6.app/Contents/Developer'

jobs:
build-test-ios:
swift-test-ios:

runs-on: macos-latest

steps:
- uses: actions/checkout@v2
- name: Build
run: xcodebuild -workspace Parse.xcworkspace -scheme ParseSwift\ \(iOS\) -destination platform\=iOS\ Simulator,OS\=13.6,name\=iPhone\ 11\ Pro\ Max build test | xcpretty

build-macos:
- name: Build-Test
run: xcodebuild -workspace Parse.xcworkspace -scheme ParseSwift\ \(iOS\) -destination platform\=iOS\ Simulator,name\=iPhone\ 11\ Pro\ Max build test | xcpretty
env:
DEVELOPER_DIR: ${{ env.CI_XCODE_VER }}

swift-macos:

runs-on: macos-latest

steps:
- uses: actions/checkout@v2
- name: Build
run: xcodebuild -target ParseSwift\ \(macOS\) | xcpretty
env:
DEVELOPER_DIR: ${{ env.CI_XCODE_VER }}

build-tvos:
swift-tvos:

runs-on: macos-latest

steps:
- uses: actions/checkout@v2
- name: Build
run: xcodebuild -target ParseSwift\ \(tvOS\) | xcpretty
env:
DEVELOPER_DIR: ${{ env.CI_XCODE_VER }}

build-watchos:
swift-watchos:

runs-on: macos-latest

steps:
- uses: actions/checkout@v2
- name: Build
run: xcodebuild -target ParseSwift\ \(watchOS\) | xcpretty
env:
DEVELOPER_DIR: ${{ env.CI_XCODE_VER }}

build-test-swift:

runs-on: macos-latest

steps:
- uses: actions/checkout@v2
- name: Build
run: swift build -v
- name: Run tests
env:
DEVELOPER_DIR: ${{ env.CI_XCODE_VER }}
- name: Test
run: swift test --enable-code-coverage -v
- name: Prepare Codecov Files
env:
DEVELOPER_DIR: ${{ env.CI_XCODE_VER }}
- name: Prepare codecov
run: xcrun llvm-cov export -format="lcov" .build/debug/ParseSwiftPackageTests.xctest/Contents/MacOS/ParseSwiftPackageTests -instr-profile .build/debug/codecov/default.profdata > info.lcov
- name: Codecov
env:
DEVELOPER_DIR: ${{ env.CI_XCODE_VER }}
- name: Send codecov
run: bash <(curl https://codecov.io/bash)

deploy_docs:
Expand All @@ -81,6 +97,8 @@ jobs:

- name: Create Jazzy Docs
run: ./Scripts/jazzy.sh
env:
DEVELOPER_DIR: ${{ env.CI_XCODE_VER }}

- name: Deploy Jazzy Docs
if: github.ref == 'refs/heads/master'
Expand All @@ -97,7 +115,8 @@ jobs:
- uses: actions/checkout@v2
- name: CocoaPods
run: pod lib lint

env:
DEVELOPER_DIR: ${{ env.CI_XCODE_VER }}
carthage:

needs: build-test-swift
Expand All @@ -107,3 +126,5 @@ jobs:
- uses: actions/checkout@v2
- name: Carthage
run: carthage build --no-skip-current
env:
DEVELOPER_DIR: ${{ env.CI_XCODE_VER }}
62 changes: 31 additions & 31 deletions ParseSwift.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
</p>

<p align="center">
<a href="https://github.com/parse-community/Parse-Swift"><img alt="Dependencies" src="https://img.shields.io/badge/dependencies-0-yellowgreen.svg"></a>
<a href="https://github.com/parse-community/Parse-Swift/actions?query=workflow%3ASwift"><img alt="Build status" src="https://github.com/parse-community/Parse-Swift/workflows/Build%20Status/badge.svg?branch=master"></a>
<a href="https://github.com/parse-community/Parse-Swift"><img alt="Swift 5.0" src="https://img.shields.io/badge/swift-5.0-brightgreen.svg"></a>
<a href="https://github.com/parse-community/Parse-Swift/actions?query=workflow%3ASwift+branch%3Amaster"><img alt="Build status" src="https://github.com/parse-community/Parse-Swift/workflows/build/badge.svg?branch=master"></a>
<a href="https://codecov.io/gh/parse-community/Parse-Swift/branches"><img alt="Code coverage" src="https://codecov.io/gh/parse-community/Parse-Swift/branch/master/graph/badge.svg"></a>
<a href="https://github.com/parse-community/Parse-Swift"><img alt="Dependencies" src="https://img.shields.io/badge/dependencies-0-yellowgreen.svg"></a>
<a href="https://community.parseplatform.org/"><img alt="Join the conversation" src="https://img.shields.io/discourse/https/community.parseplatform.org/topics.svg"></a>
</p>
<br>
Expand Down
93 changes: 67 additions & 26 deletions Sources/ParseSwift/API/API+Commands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
// ParseSwift (iOS)
//
// Created by Florent Vilmart on 17-09-24.
// Copyright © 2017 Parse. All rights reserved.
// Copyright © 2020 Parse Community. All rights reserved.
//

import Foundation

internal extension API {

struct Command<T, U>: Encodable where T: Encodable {
typealias ReturnType = U // swiftlint:disable:this nesting
let method: API.Method
Expand All @@ -34,27 +35,53 @@ internal extension API {
}

public func execute(options: API.Options) throws -> U {
var responseResult: Result<U, ParseError>?

let group = DispatchGroup()
group.enter()
self.executeAsync(options: options, callbackQueue: nil) { result in
responseResult = result
group.leave()
}
group.wait()

guard let response = responseResult else {
throw ParseError(code: .unknownError,
message: "couldn't unrwrap server response")
}
return try response.get()
}

public func executeAsync(options: API.Options, callbackQueue: DispatchQueue?,
cbaker6 marked this conversation as resolved.
Show resolved Hide resolved
completion: @escaping(Result<U, ParseError>) -> Void) {
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)!
guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let urlComponents = components.url else {
completion(.failure(ParseError(code: .unknownError,
message: "couldn't unrwrap url components for \(url)")))
return
}
components.queryItems = params

var urlRequest = URLRequest(url: components.url!)
var urlRequest = URLRequest(url: urlComponents)
urlRequest.allHTTPHeaderFields = headers
if let body = data {
urlRequest.httpBody = body
}
urlRequest.httpMethod = method.rawValue
let responseData = try URLSession.shared.syncDataTask(with: urlRequest).get()
do {
return try mapper(responseData)
} catch {
do {
throw try getDecoder().decode(ParseError.self, from: responseData)
} catch {
throw ParseError(code: .unknownError, message: "cannot decode response: \(error)")

URLSession.shared.dataTask(with: urlRequest, callbackQueue: callbackQueue, mapper: mapper) { result in
switch result {

case .success(let decoded):

completion(.success(decoded))

case .failure(let error):
completion(.failure(error))
}
}
}
Expand All @@ -77,7 +104,7 @@ internal extension API.Command {
// 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)
try getDecoder().decode(SaveResponse.self, from: data).apply(to: object)
}
return API.Command<T, T>(method: .POST,
path: object.endpoint,
Expand All @@ -87,7 +114,7 @@ internal extension API.Command {

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)
try getDecoder().decode(UpdateResponse.self, from: data).apply(to: object)
}
return API.Command<T, T>(method: .PUT,
path: object.endpoint,
Expand All @@ -102,7 +129,7 @@ internal extension API.Command {
}
return API.Command<T, T>(method: .GET,
path: object.endpoint) { (data) -> T in
try getDecoder().decode(T.self, from: data)
try getDecoder().decode(FetchResponse.self, from: data).apply(to: object)
}
}
}
Expand All @@ -123,20 +150,34 @@ extension API.Command where T: ObjectType {
return API.Command<T, T>(method: command.method, path: .any(path),
body: body, mapper: command.mapper)
}
let bodies = commands.compactMap { (command) -> T? in
return command.body
let bodies = commands.compactMap { (command) -> (body: T, command: API.Method)? in
guard let body = command.body else {
return nil
}
return (body: body, command: command.method)
}
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 mapper = { (data: Data) -> [Result<T, ParseError>] in
let decodingType = [BatchResponseItem<WriteResponse>].self
do {
let responses = try getDecoder().decode(decodingType, from: data)
return bodies.enumerated().map({ (object) -> (Result<T, ParseError>) in
let response = responses[object.offset]
if let success = response.success {
return .success(success.apply(to: object.element.body, method: object.element.command))
} else {
guard let parseError = response.error else {
return .failure(ParseError(code: .unknownError, message: "unknown error"))
}

return .failure(parseError)
}
})
} catch {
guard let parseError = error as? ParseError else {
return [(.failure(ParseError(code: .unknownError, message: "decoding error: \(error)")))]
}
})
return [(.failure(parseError))]
}
}
let batchCommand = BatchCommand(requests: commands)
return RESTBatchCommandType<T>(method: .POST, path: .batch, body: batchCommand, mapper: mapper)
Expand Down
36 changes: 22 additions & 14 deletions Sources/ParseSwift/API/BatchUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,31 @@
// ParseSwift
//
// Created by Florent Vilmart on 17-08-19.
// Copyright © 2017 Parse. All rights reserved.
// Copyright © 2020 Parse Community. All rights reserved.
//

import Foundation

typealias ParseObjectBatchCommand<T> = BatchCommand<T, T> where T: ObjectType
typealias ParseObjectBatchResponse<T> = [(T, ParseError?)]
typealias ParseObjectBatchResponse<T> = [(Result<T, ParseError>)]
// swiftlint:disable line_length
typealias RESTBatchCommandType<T> = API.Command<ParseObjectBatchCommand<T>, ParseObjectBatchResponse<T>> where T: ObjectType
// swiftlint:enable line_length

public struct BatchCommand<T, U>: Encodable where T: Encodable {
internal struct BatchCommand<T, U>: Encodable where T: Encodable {
let requests: [API.Command<T, U>]
}

public struct BatchResponseItem<T>: Decodable where T: Decodable {
internal struct BatchResponseItem<T>: Codable where T: Codable {
let success: T?
let error: ParseError?
}

struct SaveOrUpdateResponse: Decodable {
internal struct WriteResponse: Codable {
var objectId: String?
var createdAt: Date?
var updatedAt: Date?

var isCreate: Bool {
return objectId != nil && createdAt != nil
}

func asSaveResponse() -> SaveResponse {
guard let objectId = objectId, let createdAt = createdAt else {
fatalError("Cannot create a SaveResponse without objectId")
Expand All @@ -46,11 +42,23 @@ struct SaveOrUpdateResponse: Decodable {
return UpdateResponse(updatedAt: updatedAt)
}

func apply<T>(_ object: T) -> T where T: ObjectType {
if isCreate {
return asSaveResponse().apply(object)
} else {
return asUpdateResponse().apply(object)
func asFetchResponse() -> FetchResponse {
cbaker6 marked this conversation as resolved.
Show resolved Hide resolved
guard let createdAt = createdAt, let updatedAt = updatedAt else {
fatalError("Cannot create a SaveResponse without objectId")
}
return FetchResponse(createdAt: createdAt, updatedAt: updatedAt)
}

func apply<T>(to object: T, method: API.Method) -> T where T: ObjectType {
switch method {
case .POST:
return asSaveResponse().apply(to: object)
case .PUT:
return asUpdateResponse().apply(to: object)
case .GET:
return asFetchResponse().apply(to: object)
default:
cbaker6 marked this conversation as resolved.
Show resolved Hide resolved
fatalError("There is no configured way to apply for method: \(method)")
}
}
}
18 changes: 15 additions & 3 deletions Sources/ParseSwift/API/Responses.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// ParseSwift
//
// Created by Florent Vilmart on 17-08-20.
// Copyright © 2017 Parse. All rights reserved.
// Copyright © 2020 Parse Community. All rights reserved.
//

import Foundation
Expand All @@ -15,7 +15,7 @@ internal struct SaveResponse: Decodable {
return createdAt
}

func apply<T>(_ object: T) -> T where T: ObjectType {
func apply<T>(to object: T) -> T where T: ObjectType {
var object = object
object.objectId = objectId
object.createdAt = createdAt
Expand All @@ -27,9 +27,21 @@ internal struct SaveResponse: Decodable {
internal struct UpdateResponse: Decodable {
var updatedAt: Date

func apply<T>(_ object: T) -> T where T: ObjectType {
func apply<T>(to object: T) -> T where T: ObjectType {
var object = object
object.updatedAt = updatedAt
return object
}
}

internal struct FetchResponse: Decodable {
cbaker6 marked this conversation as resolved.
Show resolved Hide resolved
var createdAt: Date
var updatedAt: Date

func apply<T>(to object: T) -> T where T: ObjectType {
var object = object
object.createdAt = createdAt
object.updatedAt = updatedAt
return object
}
}
Loading