Skip to content

Allow generic return types for ParseCloud, hint, and explain #92

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 15 commits into from
Mar 15, 2021
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
2 changes: 1 addition & 1 deletion .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ coverage:
status:
patch:
default:
target: auto
target: 72
changes: false
project:
default:
Expand Down
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
# Parse-Swift Changelog

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

### 1.1.7
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.6...1.1.7)
### 1.2.0
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.6...1.2.0)

__Breaking changes__
- Allows return types to be specified for `ParseCloud`, query `hint`, and `explain` (see playgrounds for examples). Changed functionality of synchronous `query.first()`. It use to return nil if no values are found. Now it will throw an error if none are found. ([#92](https://github.com/parse-community/Parse-Swift/pull/92)), thanks to [Corey Baker](https://github.com/cbaker6).

__New features__
- Add transaction support to batch saveAll and deleteAll ([#89](https://github.com/parse-community/Parse-Swift/pull/89)), thanks to [Corey Baker](https://github.com/cbaker6).
- Add modifiers to containsString, hasPrefix, hasSuffix ([#85](https://github.com/parse-community/Parse-Swift/pull/85)), thanks to [Corey Baker](https://github.com/cbaker6).

__Improvements__
- Better error reporting when decode errors occur ([#92](https://github.com/parse-community/Parse-Swift/pull/92)), thanks to [Corey Baker](https://github.com/cbaker6).
- Can use a variadic version of exclude. Added examples of select and exclude query in playgrounds ([#88](https://github.com/parse-community/Parse-Swift/pull/88)), thanks to [Corey Baker](https://github.com/cbaker6).

### 1.1.6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ initializeParse()

//: Create your own value typed `ParseCloud` type.
struct Cloud: ParseCloud {

//: Return type of your Cloud Function
typealias ReturnType = String

//: These are required for Object
var functionJobName: String

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ struct GameScore: ParseObject {
var ACL: ParseACL?
var location: ParseGeoPoint?
//: Your own properties
var score: Int
var score: Int?

//: A custom initializer.
init(score: Int) {
Expand Down Expand Up @@ -126,7 +126,6 @@ query3.find { results in
switch results {
case .success(let scores):

assert(scores.count >= 1)
scores.forEach { (score) in
print("""
Someone has a score of \"\(score.score)\" with no geopoint \(String(describing: score.location))
Expand Down Expand Up @@ -164,7 +163,6 @@ query7.find { results in
switch results {
case .success(let scores):

assert(scores.count >= 1)
scores.forEach { (score) in
print("""
Someone has a score of \"\(score.score)\" with geopoint using OR \(String(describing: score.location))
Expand All @@ -177,11 +175,19 @@ query7.find { results in
}

//: Explain the previous query.
let explain = try query2.find(explain: true)
let explain: AnyDecodable = try query2.first(explain: true)
print(explain)

let hint = try query2.find(explain: false, hint: "objectId")
print(hint)
//: Hint of the previous query (asynchronous)
query2.find(explain: false,
hint: "_id_") { (result: Result<[GameScore], ParseError>) in
switch result {
case .success(let scores):
print(scores)
case .failure(let error):
print(error.localizedDescription)
}
}

PlaygroundPage.current.finishExecution()
//: [Next](@next)
2 changes: 1 addition & 1 deletion 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.7"
s.version = "1.2.0"
s.summary = "Parse Pure Swift SDK"
s.homepage = "https://github.com/parse-community/Parse-Swift"
s.authors = {
Expand Down
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.7 \
--module-version 1.2.0 \
--theme fullwidth \
--skip-undocumented \
--output ./docs/api \
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/API/API+Commands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ internal extension API {
var urlRequest = URLRequest(url: urlComponents)
urlRequest.allHTTPHeaderFields = headers
if let urlBody = body {
if (urlBody as? ParseCloud) != nil {
if (urlBody as? CloudType) != nil {
guard let bodyData = try? ParseCoding.parseEncoder().encode(urlBody, skipKeys: .cloud) else {
return .failure(ParseError(code: .unknownError,
message: "couldn't encode body \(urlBody)"))
Expand Down
8 changes: 4 additions & 4 deletions Sources/ParseSwift/API/Responses.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,13 @@ internal struct FileUploadResponse: Decodable {
}

// MARK: AnyResultResponse
internal struct AnyResultResponse: Codable {
let result: AnyCodable?
internal struct AnyResultResponse<U: Decodable>: Decodable {
let result: U
}

// MARK: AnyResultsResponse
internal struct AnyResultsResponse: Codable {
let results: AnyCodable?
internal struct AnyResultsResponse<U: Decodable>: Decodable {
let results: [U]
}

// MARK: ConfigResponse
Expand Down
10 changes: 9 additions & 1 deletion Sources/ParseSwift/API/URLSession+extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,17 @@ extension URLSession {
return .failure(error)
}
guard let parseError = error as? ParseError else {
guard JSONSerialization.isValidJSONObject(responseData) == true,
let json = try? JSONSerialization
.data(withJSONObject: responseData,
options: .prettyPrinted) else {
return .failure(ParseError(code: .unknownError,
// swiftlint:disable:next line_length
message: "Error decoding parse-server response: \(String(describing: urlResponse)) with error: \(error.localizedDescription) Format: \(String(describing: String(data: responseData, encoding: .utf8)))"))
}
return .failure(ParseError(code: .unknownError,
// swiftlint:disable:next line_length
message: "Error decoding parse-server response: \(String(describing: urlResponse)) with error: \(error.localizedDescription)"))
message: "Error decoding parse-server response: \(String(describing: urlResponse)) with error: \(error.localizedDescription) Format: \(String(describing: String(data: json, encoding: .utf8)))"))
}
return .failure(parseError)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/Coding/ParseCoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ extension ParseCoding {
message: "An invalid date string was provided when decoding dates."
)
}
} catch let error {
} catch {
let container = try decoder.container(keyedBy: DateEncodingKeys.self)

if
Expand Down
11 changes: 6 additions & 5 deletions Sources/ParseSwift/Objects/ParseInstallation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -395,13 +395,14 @@ extension ParseInstallation {
completion(result)
}
}
} catch let error as ParseError {
callbackQueue.async {
completion(.failure(error))
}
} catch {
callbackQueue.async {
completion(.failure(ParseError(code: .unknownError, message: error.localizedDescription)))
if let error = error as? ParseError {
completion(.failure(error))
} else {
completion(.failure(ParseError(code: .unknownError,
message: error.localizedDescription)))
}
}
}
}
Expand Down
13 changes: 7 additions & 6 deletions Sources/ParseSwift/Objects/ParseObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -539,13 +539,14 @@ extension ParseObject {
completion(result)
}
}
} catch let error as ParseError {
callbackQueue.async {
completion(.failure(error))
}
} catch {
callbackQueue.async {
completion(.failure(ParseError(code: .unknownError, message: error.localizedDescription)))
if let error = error as? ParseError {
completion(.failure(error))
} else {
completion(.failure(ParseError(code: .unknownError,
message: error.localizedDescription)))
}
}
}
}
Expand Down Expand Up @@ -646,7 +647,7 @@ extension ParseObject {
}

internal func saveCommand() -> API.Command<Self, Self> {
return API.Command<Self, Self>.saveCommand(self)
API.Command<Self, Self>.saveCommand(self)
}

// swiftlint:disable:next function_body_length
Expand Down
11 changes: 6 additions & 5 deletions Sources/ParseSwift/Objects/ParseUser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -706,13 +706,14 @@ extension ParseUser {
}
}
}
} catch let error as ParseError {
callbackQueue.async {
completion(.failure(error))
}
} catch {
callbackQueue.async {
completion(.failure(ParseError(code: .unknownError, message: error.localizedDescription)))
if let error = error as? ParseError {
completion(.failure(error))
} else {
completion(.failure(ParseError(code: .unknownError,
message: error.localizedDescription)))
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/ParseConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation

enum ParseConstants {
static let parseVersion = "1.1.7"
static let parseVersion = "1.2.0"
static let hashingKey = "parseSwift"
static let fileManagementDirectory = "parse/"
static let fileManagementPrivateDocumentsDirectory = "Private Documents/"
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/Protocols/Queryable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public protocol Queryable {
associatedtype ResultType

func find(options: API.Options) throws -> [ResultType]
func first(options: API.Options) throws -> ResultType?
func first(options: API.Options) throws -> ResultType
func count(options: API.Options) throws -> Int
func find(options: API.Options, callbackQueue: DispatchQueue,
completion: @escaping (Result<[ResultType], ParseError>) -> Void)
Expand Down
4 changes: 2 additions & 2 deletions Sources/ParseSwift/Storage/ParseKeyValueStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ public protocol ParseKeyValueStore {
/// It works by encoding / decoding all values just like a real `Codable` store would
/// but it stores all values as `Data` blobs in memory.
struct InMemoryKeyValueStore: ParseKeyValueStore {
var decoder = JSONDecoder()
var encoder = JSONEncoder()
var decoder = ParseCoding.jsonDecoder()
var encoder = ParseCoding.jsonEncoder()
var storage = [String: Data]()

mutating func delete(valueFor key: String) throws {
Expand Down
4 changes: 2 additions & 2 deletions Sources/ParseSwift/Types/ParseCloud+combine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public extension ParseCloud {
- 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.
*/
func runFunctionPublisher(options: API.Options = []) -> Future<AnyCodable, ParseError> {
func runFunctionPublisher(options: API.Options = []) -> Future<ReturnType, ParseError> {
Future { promise in
self.runFunction(options: options,
completion: promise)
Expand All @@ -37,7 +37,7 @@ public extension ParseCloud {
- 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.
*/
func startJobPublisher(options: API.Options = []) -> Future<AnyCodable, ParseError> {
func startJobPublisher(options: API.Options = []) -> Future<ReturnType, ParseError> {
Future { promise in
self.startJob(options: options,
completion: promise)
Expand Down
50 changes: 21 additions & 29 deletions Sources/ParseSwift/Types/ParseCloud.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@

import Foundation

public protocol CloudType: Decodable, CustomDebugStringConvertible { }

/**
Objects that conform to the `ParseCloud` protocol are able to call Parse Cloud Functions and Jobs.
An object should be instantiated for each function and job type. When conforming to
`ParseCloud`, any properties added will be passed as parameters to your Cloud Function or Job.
*/
public protocol ParseCloud: ParseType, Decodable, CustomDebugStringConvertible {
public protocol ParseCloud: ParseType, CloudType {

associatedtype ReturnType: Decodable
/**
The name of the function or job.
*/
Expand All @@ -27,10 +31,10 @@ extension ParseCloud {
/**
Calls a Cloud Code function *synchronously* and returns a result of it's execution.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- returns: Returns a JSON response of `AnyCodable` type.
- returns: Returns a `Decodable` type.
- throws: An error of type `ParseError`.
*/
public func runFunction(options: API.Options = []) throws -> AnyCodable {
public func runFunction(options: API.Options = []) throws -> ReturnType {
try runFunctionCommand().execute(options: options, callbackQueue: .main)
}

Expand All @@ -39,11 +43,11 @@ extension ParseCloud {
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- parameter callbackQueue: The queue to return to after completion. Default value of .main.
- parameter completion: A block that will be called when logging out, completes or fails.
It should have the following argument signature: `(Result<AnyCodable, ParseError>)`.
It should have the following argument signature: `(Result<ReturnType, ParseError>)`.
*/
public func runFunction(options: API.Options = [],
callbackQueue: DispatchQueue = .main,
completion: @escaping (Result<AnyCodable, ParseError>) -> Void) {
completion: @escaping (Result<ReturnType, ParseError>) -> Void) {
runFunctionCommand()
.executeAsync(options: options, callbackQueue: callbackQueue) { result in
callbackQueue.async {
Expand All @@ -52,19 +56,13 @@ extension ParseCloud {
}
}

internal func runFunctionCommand() -> API.Command<Self, AnyCodable> {
internal func runFunctionCommand() -> API.Command<Self, ReturnType> {

return API.Command(method: .POST,
path: .functions(name: functionJobName),
body: self) { (data) -> AnyCodable in
let response = try ParseCoding.jsonDecoder().decode(AnyResultResponse.self, from: data)
guard let result = response.result else {
if let error = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) {
throw error
}
return AnyCodable()
}
return result
body: self) { (data) -> ReturnType in
let response = try ParseCoding.jsonDecoder().decode(AnyResultResponse<ReturnType>.self, from: data)
return response.result
}
}
}
Expand All @@ -74,9 +72,9 @@ extension ParseCloud {
/**
Starts a Cloud Code job *synchronously* and returns a result with the jobStatusId of the job.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- returns: Returns a JSON response of `AnyCodable` type.
- returns: Returns a `Decodable` type.
*/
public func startJob(options: API.Options = []) throws -> AnyCodable {
public func startJob(options: API.Options = []) throws -> ReturnType {
try startJobCommand().execute(options: options, callbackQueue: .main)
}

Expand All @@ -85,11 +83,11 @@ extension ParseCloud {
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- parameter callbackQueue: The queue to return to after completion. Default value of .main.
- parameter completion: A block that will be called when logging out, completes or fails.
It should have the following argument signature: `(Result<AnyCodable, ParseError>)`.
It should have the following argument signature: `(Result<ReturnType, ParseError>)`.
*/
public func startJob(options: API.Options = [],
callbackQueue: DispatchQueue = .main,
completion: @escaping (Result<AnyCodable, ParseError>) -> Void) {
completion: @escaping (Result<ReturnType, ParseError>) -> Void) {
startJobCommand()
.executeAsync(options: options, callbackQueue: callbackQueue) { result in
callbackQueue.async {
Expand All @@ -98,18 +96,12 @@ extension ParseCloud {
}
}

internal func startJobCommand() -> API.Command<Self, AnyCodable> {
internal func startJobCommand() -> API.Command<Self, ReturnType> {
return API.Command(method: .POST,
path: .jobs(name: functionJobName),
body: self) { (data) -> AnyCodable in
let response = try ParseCoding.jsonDecoder().decode(AnyResultResponse.self, from: data)
guard let result = response.result else {
if let error = try? ParseCoding.jsonDecoder().decode(ParseError.self, from: data) {
throw error
}
return AnyCodable()
}
return result
body: self) { (data) -> ReturnType in
let response = try ParseCoding.jsonDecoder().decode(AnyResultResponse<ReturnType>.self, from: data)
return response.result
}
}
}
Expand Down
Loading