Skip to content

Commit

Permalink
Add combine publishers for all objects and types (#73)
Browse files Browse the repository at this point in the history
* wip

* Update

* Update

* Need test cases

* updates

* Update

* Update authentication

* Update

* Fixed deleteAll

* Working publishers

* Fix Swift 5.2 closures and new cache for jazzy

* Add minimum catalyst support, nits, prepare for release

* Nit

* Update Sources/ParseSwift/Types/ParseFile+combine.swift

Co-authored-by: Tom Fox <[email protected]>

* Bring Xcode, SPM, and Cocoapods minimum deployments to the same level:

[.iOS(.v12), .macOS(.v10_13), .tvOS(.v12), .watchOS(.v5)]

Co-authored-by: Tom Fox <[email protected]>
  • Loading branch information
cbaker6 and TomWFox authored Jan 31, 2021
1 parent 48a871e commit fb61b78
Show file tree
Hide file tree
Showing 53 changed files with 4,996 additions and 1,163 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ jobs:
uses: actions/cache@v2
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-v2-${{ hashFiles('**/Gemfile.lock') }}
key: ${{ runner.os }}-gem-v3-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-v2
${{ runner.os }}-gem-v3
- name: Install Bundle
run: |
bundle config path vendor/bundle
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ jobs:
uses: actions/cache@v2
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-v2-${{ hashFiles('**/Gemfile.lock') }}
key: ${{ runner.os }}-gem-v3-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-v2
${{ runner.os }}-gem-v3
- name: Install Bundle
run: |
bundle config path vendor/bundle
Expand Down
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
# Parse-Swift Changelog

### main
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.2...main)
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.3...main)
* _Contributing to this repo? Add info about your change here to be included in next release_

### 1.1.3
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.2...1.1.3)

__New features__
- SwiftUI ready! ([#73](https://github.com/parse-community/Parse-Swift/pull/73)), thanks to [Corey Baker](https://github.com/cbaker6).

__Fixes__
- Fixes some issues with `ParseUser.logout` ([#73](https://github.com/parse-community/Parse-Swift/pull/73)), thanks to [Corey Baker](https://github.com/cbaker6).

### 1.1.2
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.1...1.1.2)

Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import PackageDescription

let package = Package(
name: "ParseSwift",
platforms: [.iOS(.v11), .macOS(.v10_13), .tvOS(.v11), .watchOS(.v4)],
platforms: [.iOS(.v12), .macOS(.v10_13), .tvOS(.v12), .watchOS(.v5)],
products: [
.library(
name: "ParseSwift",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,13 @@ do {
[scoreToFetch, score2ToFetch].deleteAll { result in
switch result {
case .success(let deletedScores):
deletedScores.forEach { error in
guard let error = error else {
print("Successfully deleted scores")
return
deletedScores.forEach { result in
switch result {
case .success:
print("Successfully deleted score")
case .failure(let error):
print("Error deleting: \(error)")
}
print("Error deleting: \(error)")
}
case .failure(let error):
assertionFailure("Error deleting: \(error)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,17 @@ User.current?.signup { result in
}
}

//: Logging out - synchronously.
do {
try User.logout()
print("Successfully logged out")
} catch let error {
print("Error logging out: \(error)")
}

//: Password Reset Request - synchronously.
do {
try User.verificationEmailRequest(email: "[email protected]")
try User.verificationEmail(email: "[email protected]")
print("Successfully requested verification email be sent")
} catch let error {
print("Error requesting verification email be sent: \(error)")
Expand Down
8 changes: 4 additions & 4 deletions ParseSwift.playground/contents.xcplayground
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
<page name='8 - Pointers'/>
<page name='9 - Files'/>
<page name='10 - Cloud Code'/>
<page name='11 - LiveQuery'/>
<page name='12 - Roles and Relations'/>
<page name='13 - Operations'/>
<page name='11 - LiveQuery'/>
<page name='12 - Roles and Relations'/>
<page name='13 - Operations'/>
<page name='14 - Config'/>
</pages>
</playground>
</playground>
8 changes: 4 additions & 4 deletions ParseSwift.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "ParseSwift"
s.version = "1.1.2"
s.version = "1.1.3"
s.summary = "Parse Pure Swift SDK"
s.homepage = "https://github.com/parse-community/Parse-Swift"
s.authors = {
Expand All @@ -10,10 +10,10 @@ Pod::Spec.new do |s|
:git => "#{s.homepage}.git",
:tag => "#{s.version}",
}
s.ios.deployment_target = "11.0"
s.ios.deployment_target = "12.0"
s.osx.deployment_target = "10.13"
s.tvos.deployment_target = "11.0"
s.watchos.deployment_target = "4.0"
s.tvos.deployment_target = "12.0"
s.watchos.deployment_target = "5.0"
s.swift_versions = ['5.1', '5.2', '5.3']
s.source_files = "Sources/ParseSwift/**/*.swift"
s.license = {
Expand Down
202 changes: 192 additions & 10 deletions ParseSwift.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Scripts/jazzy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ bundle exec jazzy \
--author_url http://parseplatform.org \
--github_url https://github.com/parse-community/Parse-Swift \
--root-url http://parseplatform.org/Parse-Swift/api/ \
--module-version 1.1.2 \
--module-version 1.1.3 \
--theme fullwidth \
--skip-undocumented \
--output ./docs/api \
Expand Down
45 changes: 26 additions & 19 deletions Sources/ParseSwift/API/API+Commands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -452,38 +452,41 @@ extension API.Command where T: ParseObject {
}

// MARK: Batch - Deleting
// swiftlint:disable:next line_length
static func batch(commands: [API.NonParseBodyCommand<NoBody, ParseError?>]) -> RESTBatchCommandNoBodyType<ParseError?> {
let commands = commands.compactMap { (command) -> API.NonParseBodyCommand<NoBody, ParseError?>? in
static func batch(commands: [API.NonParseBodyCommand<NoBody, NoBody>]) -> RESTBatchCommandNoBodyType<NoBody> {
let commands = commands.compactMap { (command) -> API.NonParseBodyCommand<NoBody, NoBody>? in
let path = ParseConfiguration.mountPath + command.path.urlComponent
return API.NonParseBodyCommand<NoBody, ParseError?>(
return API.NonParseBodyCommand<NoBody, NoBody>(
method: command.method,
path: .any(path), mapper: command.mapper)
}

let mapper = { (data: Data) -> [ParseError?] in
let mapper = { (data: Data) -> [(Result<Void, ParseError>)] in

let decodingType = [ParseError?].self
let decodingType = [BatchResponseItem<NoBody>].self
do {
let responses = try ParseCoding.jsonDecoder().decode(decodingType, from: data)
return responses.enumerated().map({ (object) -> ParseError? in
return responses.enumerated().map({ (object) -> (Result<Void, ParseError>) in
let response = responses[object.offset]
if let error = response {
return error
if response.success != nil {
return .success(())
} else {
return nil
guard let parseError = response.error else {
return .failure(ParseError(code: .unknownError, message: "unknown error"))
}

return .failure(parseError)
}
})
} catch {
guard (try? ParseCoding.jsonDecoder().decode(NoBody.self, from: data)) != nil else {
return [ParseError(code: .unknownError, message: "decoding error: \(error)")]
guard let parseError = error as? ParseError else {
return [(.failure(ParseError(code: .unknownError, message: "decoding error: \(error)")))]
}
return [nil]
return [(.failure(parseError))]
}
}

let batchCommand = BatchCommandNoBody(requests: commands)
return RESTBatchCommandNoBodyType<ParseError?>(method: .POST, path: .batch, body: batchCommand, mapper: mapper)
return RESTBatchCommandNoBodyType<NoBody>(method: .POST, path: .batch, body: batchCommand, mapper: mapper)
}
}

Expand Down Expand Up @@ -642,17 +645,21 @@ internal extension API {

internal extension API.NonParseBodyCommand {
// MARK: Deleting
// swiftlint:disable:next line_length
static func deleteCommand<T>(_ object: T) throws -> API.NonParseBodyCommand<NoBody, ParseError?> where T: ParseObject {
static func deleteCommand<T>(_ object: T) throws -> API.NonParseBodyCommand<NoBody, NoBody> where T: ParseObject {
guard object.isSaved else {
throw ParseError(code: .unknownError, message: "Cannot Delete an object without id")
}

return API.NonParseBodyCommand<NoBody, ParseError?>(
return API.NonParseBodyCommand<NoBody, NoBody>(
method: .DELETE,
path: object.endpoint
) { (data) -> ParseError? in
try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data)
) { (data) -> NoBody in
let error = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data)
if let error = error {
throw error
} else {
return NoBody()
}
}
}
} // swiftlint:disable:this file_length
4 changes: 2 additions & 2 deletions Sources/ParseSwift/API/API.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public struct API {
case logout
case file(fileName: String)
case passwordReset
case verificationEmailRequest
case verificationEmail
case functions(name: String)
case jobs(name: String)
case aggregate(className: String)
Expand Down Expand Up @@ -70,7 +70,7 @@ public struct API {
return "/files/\(fileName)"
case .passwordReset:
return "/requestPasswordReset"
case .verificationEmailRequest:
case .verificationEmail:
return "/verificationEmailRequest"
case .functions(name: let name):
return "/functions/\(name)"
Expand Down
4 changes: 2 additions & 2 deletions Sources/ParseSwift/API/BatchUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ typealias ParseObjectBatchResponse<T> = [(Result<T, ParseError>)]
// swiftlint:disable line_length
typealias RESTBatchCommandType<T> = API.Command<ParseObjectBatchCommand<T>, ParseObjectBatchResponse<T>> where T: ParseObject

typealias ParseObjectBatchCommandNoBody<T> = BatchCommandNoBody<NoBody, ParseError?>
typealias ParseObjectBatchResponseNoBody<NoBody> = [ParseError?]
typealias ParseObjectBatchCommandNoBody<T> = BatchCommandNoBody<NoBody, NoBody>
typealias ParseObjectBatchResponseNoBody<NoBody> = [(Result<Void, ParseError>)]
typealias RESTBatchCommandNoBodyType<T> = API.NonParseBodyCommand<ParseObjectBatchCommandNoBody<T>, ParseObjectBatchResponseNoBody<T>> where T: Encodable
/*
typealias ParseObjectBatchCommandEncodable<T> = BatchCommand<T, PointerType> where T: ParseType
Expand Down
73 changes: 73 additions & 0 deletions Sources/ParseSwift/Authentication/3rd Party/ParseApple.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
//

import Foundation
#if canImport(Combine)
import Combine
#endif

// swiftlint:disable line_length

Expand Down Expand Up @@ -90,6 +93,41 @@ public extension ParseApple {
callbackQueue: callbackQueue,
completion: completion)
}

#if canImport(Combine)

/**
Login a `ParseUser` *asynchronously* using Apple authentication. Publishes when complete.
- parameter user: The `user` from `ASAuthorizationAppleIDCredential`.
- parameter identityToken: The `identityToken` from `ASAuthorizationAppleIDCredential`.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- returns: A publisher that eventually produces a single value and then finishes or fails.
*/
@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
func loginPublisher(user: String,
identityToken: String,
options: API.Options = []) -> Future<AuthenticatedUser, ParseError> {
loginPublisher(authData: AuthenticationKeys.id.makeDictionary(user: user, identityToken: identityToken),
options: options)
}

@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
func loginPublisher(authData: [String: String]?,
options: API.Options = []) -> Future<AuthenticatedUser, ParseError> {
guard AuthenticationKeys.id.verifyMandatoryKeys(authData: authData),
let authData = authData else {
let error = ParseError(code: .unknownError,
message: "Should have authData in consisting of keys \"id\" and \"token\".")
return Future { promise in
promise(.failure(error))
}
}
return AuthenticatedUser.loginPublisher(Self.__type,
authData: authData,
options: options)
}

#endif
}

// MARK: Link
Expand Down Expand Up @@ -133,6 +171,41 @@ public extension ParseApple {
callbackQueue: callbackQueue,
completion: completion)
}

#if canImport(Combine)

/**
Link the *current* `ParseUser` *asynchronously* using Apple authentication. Publishes when complete.
- parameter user: The `user` from `ASAuthorizationAppleIDCredential`.
- parameter identityToken: The `identityToken` from `ASAuthorizationAppleIDCredential`.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- returns: A publisher that eventually produces a single value and then finishes or fails.
*/
@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
func linkPublisher(user: String,
identityToken: String,
options: API.Options = []) -> Future<AuthenticatedUser, ParseError> {
linkPublisher(authData: AuthenticationKeys.id.makeDictionary(user: user, identityToken: identityToken),
options: options)
}

@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
func linkPublisher(authData: [String: String]?,
options: API.Options = []) -> Future<AuthenticatedUser, ParseError> {
guard AuthenticationKeys.id.verifyMandatoryKeys(authData: authData),
let authData = authData else {
let error = ParseError(code: .unknownError,
message: "Should have authData in consisting of keys \"id\" and \"token\".")
return Future { promise in
promise(.failure(error))
}
}
return AuthenticatedUser.linkPublisher(Self.__type,
authData: authData,
options: options)
}

#endif
}

// MARK: 3rd Party Authentication - ParseApple
Expand Down
27 changes: 27 additions & 0 deletions Sources/ParseSwift/Authentication/Internal/ParseAnonymous.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
//

import Foundation
#if canImport(Combine)
import Combine
#endif

/**
Provides utility functions for working with Anonymously logged-in users.
Expand Down Expand Up @@ -68,6 +71,18 @@ public extension ParseAnonymous {
callbackQueue: callbackQueue,
completion: completion)
}

#if canImport(Combine)

@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
func loginPublisher(authData: [String: String]? = nil,
options: API.Options = []) -> Future<AuthenticatedUser, ParseError> {
AuthenticatedUser.loginPublisher(__type,
authData: AuthenticationKeys.id.makeDictionary(),
options: options)
}

#endif
}

// MARK: Link
Expand All @@ -81,6 +96,18 @@ public extension ParseAnonymous {
completion(.failure(ParseError(code: .unknownError, message: "Not supported")))
}
}

#if canImport(Combine)

@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
func linkPublisher(authData: [String: String]?,
options: API.Options) -> Future<AuthenticatedUser, ParseError> {
Future { promise in
promise(.failure(ParseError(code: .unknownError, message: "Not supported")))
}
}

#endif
}

// MARK: ParseAnonymous
Expand Down
Loading

0 comments on commit fb61b78

Please sign in to comment.