-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Updated for v9.0.0 * Updated CHANGELOG.md * Updated @testable import statement * Updated README.md * Removed superseded files * Conform SalesforceError to LocalizedError * Updated errorDescription for SalesforceError * Default API version to 51.0 * Made Record subscript public * Added documentation * Updated documentation * Made query methods generic * Generic retrieve method * Added non-generic version of ConnectedApp.retrieve * Updated tests for generic methods * Updated test * Updated README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update CHANGELOG.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Updated release date in change log * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md
- Loading branch information
Showing
111 changed files
with
3,038 additions
and
8,396 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* | ||
"Swiftly Salesforce: the Swift-est way to build iOS apps that connect to Salesforce" | ||
For more information and license see: https://www.github.com/mike4aday/SwiftlySalesforce | ||
Copyright (c) 2021. All rights reserved. | ||
*/ | ||
|
||
import Foundation | ||
|
||
struct Configuration { | ||
var consumerKey: String | ||
var callbackURL: URL | ||
var defaultAuthHost: String? | ||
} | ||
|
||
extension Configuration: Decodable { | ||
enum CodingKeys: String, CodingKey { | ||
case consumerKey = "ConsumerKey" | ||
case callbackURL = "CallbackURL" | ||
case defaultAuthHost = "DefaultAuthHost" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
"Swiftly Salesforce: the Swift-est way to build iOS apps that connect to Salesforce" | ||
For more information and license see: https://www.github.com/mike4aday/SwiftlySalesforce | ||
Copyright (c) 2021. All rights reserved. | ||
*/ | ||
|
||
import Foundation | ||
import Combine | ||
|
||
public extension ConnectedApp { | ||
|
||
/// Represents a call to an Apex class exposed as a REST service. | ||
/// | ||
/// You can expose your Apex class and methods so that external applications can access your code and your application through the REST architecture. This is done by defining your Apex class with the @RestResource annotation to expose it as a REST resource. Similarly, add annotations to your methods to expose them through REST. For example, you can add the @HttpGet annotation to your method to expose it as a REST resource that can be called by an HTTP GET request. | ||
/// - Parameters: | ||
/// - method: Name of the Apex method to call. | ||
/// - namespace: Managed package namespace, if any. Optional. | ||
/// - relativePath: Path to the Apex REST service, as defined in the `urlMapping` of the `@RestResource` annotation on the target class. | ||
/// - queryItems: Optional query items to include in the request. | ||
/// - headers: Optional `HTTP` headers to include in the request. | ||
/// - body: Request body for a `POST` , `PATCH` or `PUT` request. | ||
/// - validator: Validator to validate the server response. | ||
/// - decoder: JSON decoder to use to decode the results. | ||
/// - session: URL session for the request. | ||
/// - allowsLogin: If authentication is required and allowsLogin is true, the user will be prompted to authenticate via the Salesforce-hosted web login form. | ||
/// - Returns: Pubisher | ||
/// # Reference | ||
/// [Introduction to Apex REST](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_rest_intro.htm) | ||
func apex<T>( | ||
method: String? = nil, | ||
namespace: String? = nil, | ||
relativePath: String, | ||
queryItems: [URLQueryItem]? = nil, | ||
headers: [String:String]? = nil, | ||
body: Data? = nil, | ||
validator: Validator = .default, | ||
decoder: JSONDecoder = .salesforce, | ||
session: URLSession = .shared, | ||
allowsLogin: Bool = true | ||
) -> AnyPublisher<T, Error> where T: Decodable { | ||
|
||
let service = ApexService(method: method, namespace: namespace, relativePath: relativePath, queryItems: queryItems, headers: headers, body: body) | ||
return go(service: service, session: session, allowsLogin: allowsLogin, validator: validator, decoder: decoder) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/* | ||
"Swiftly Salesforce: the Swift-est way to build iOS apps that connect to Salesforce" | ||
For more information and license see: https://www.github.com/mike4aday/SwiftlySalesforce | ||
Copyright (c) 2021. All rights reserved. | ||
*/ | ||
|
||
import Foundation | ||
import Combine | ||
|
||
public extension ConnectedApp { | ||
|
||
/// Gets information about the current user | ||
/// - Parameters: | ||
/// - session: URL session for the request. | ||
/// - allowsLogin: If authentication is required and allowsLogin is true, the user will be prompted to authenticate via the Salesforce-hosted web login form. | ||
/// - Returns: Publisher. | ||
func identity(session: URLSession = .shared, allowsLogin: Bool = true) -> AnyPublisher<Identity, Error> { | ||
let service = IdentityService() | ||
let validator = Validator { output in | ||
if let httpResponse = output.response as? HTTPURLResponse, httpResponse.statusCode == 403 { | ||
throw SalesforceError.userAuthenticationRequired | ||
} | ||
else { | ||
return try Validator.default.validate(output) | ||
} | ||
} | ||
let decoder = JSONDecoder(dateDecodingStrategy: .iso8601) | ||
return go(service: service, session: session, allowsLogin: allowsLogin, validator: validator, decoder: decoder) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* | ||
"Swiftly Salesforce: the Swift-est way to build iOS apps that connect to Salesforce" | ||
For more information and license see: https://www.github.com/mike4aday/SwiftlySalesforce | ||
Copyright (c) 2021. All rights reserved. | ||
*/ | ||
|
||
import Foundation | ||
import Combine | ||
import AuthenticationServices | ||
|
||
internal extension ConnectedApp { | ||
|
||
init(configuration config: Configuration) { | ||
var mgr = CredentialManager(consumerKey: config.consumerKey, callbackURL: config.callbackURL) | ||
if let defaultHost = config.defaultAuthHost { | ||
mgr.defaultHost = defaultHost | ||
} | ||
self.init(credentialManager: mgr) | ||
} | ||
|
||
func go<Output>(service: Service, session: URLSession, credential: Credential, validator: Validator, decoder: JSONDecoder) -> AnyPublisher<Output, Error> where Output: Decodable { | ||
AnyPublisher<URLRequest, Error>.just(try service.buildRequest(with: credential)) | ||
.flatMap { session.dataTaskPublisher(for: $0).mapError { $0 as Error } } | ||
.validate(using: validator.validate) | ||
.map(\.data) | ||
.tryMap { data in | ||
if let output = data as? Output { | ||
return output | ||
} | ||
else { | ||
return try decoder.decode(Output.self, from: data) | ||
} | ||
} | ||
.eraseToAnyPublisher() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/* | ||
"Swiftly Salesforce: the Swift-est way to build iOS apps that connect to Salesforce" | ||
For more information and license see: https://www.github.com/mike4aday/SwiftlySalesforce | ||
Copyright (c) 2021. All rights reserved. | ||
*/ | ||
|
||
import Foundation | ||
import Combine | ||
|
||
public extension ConnectedApp { | ||
|
||
func limits(session: URLSession = .shared, allowsLogin: Bool = true) -> AnyPublisher<[String:Limit], Error> { | ||
go(service: LimitsService(), session: session, allowsLogin: allowsLogin) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
"Swiftly Salesforce: the Swift-est way to build iOS apps that connect to Salesforce" | ||
For more information and license see: https://www.github.com/mike4aday/SwiftlySalesforce | ||
Copyright (c) 2021. All rights reserved. | ||
*/ | ||
|
||
import Foundation | ||
import Combine | ||
|
||
public extension ConnectedApp { | ||
|
||
/// Performs a query | ||
/// - Parameters: | ||
/// - soql: A SOQL query. | ||
/// - batchSize: A numeric value that specifies the number of records returned for a query request. Child objects count toward the number of records for the batch size. For example, in relationship queries, multiple child objects are returned per parent row returned. The default is 2,000; the minimum is 200, and the maximum is 2,000. There is no guarantee that the requested batch size is the actual batch size. Changes are made as necessary to maximize performance. | ||
/// - session: URL session for the request. | ||
/// - allowsLogin: If authentication is required and allowsLogin is true, the user will be prompted to authenticate via the Salesforce-hosted web login form. | ||
/// - Returns: Publisher of `QueryResult` of `Decodable` instances. | ||
/// # Reference | ||
/// - [Query](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_query.htm) | ||
/// - [SOQL and SOSL Reference](https://developer.salesforce.com/docs/atlas.en-us.232.0.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_sosl_intro.htm) | ||
func query<T: Decodable>( | ||
soql: String, | ||
batchSize: Int = 2000, | ||
session: URLSession = .shared, | ||
allowsLogin: Bool = true | ||
) -> AnyPublisher<QueryResult<T>, Error> { | ||
|
||
let service = QueryService(.execute(soql: soql, batchSize: batchSize)) | ||
return go(service: service, session: session, allowsLogin: allowsLogin) | ||
} | ||
|
||
/// Retrieves the next page of query results. | ||
/// | ||
/// If the initial query returns only part of the results, the `QueryResult` will contain a value in `nextRecordsPath` which can be used as the `path` argument for this method. | ||
/// - Parameters: | ||
/// - path: The path to the next page of query results, as returned by the previous call to `query` or `nextResultPage`. | ||
/// - session: URL session for the request. | ||
/// - allowsLogin: If authentication is required and allowsLogin is true, the user will be prompted to authenticate via the Salesforce-hosted web login form. | ||
/// - Returns: Publisher of `QueryResult` of `Decodable` instances. | ||
/// # Reference | ||
/// [Execute a SOQL Query](https://developer.salesforce.com/docs/atlas.en-us.232.0.api_rest.meta/api_rest/dome_query.htm) | ||
/// | ||
func nextResultPage<T: Decodable>( | ||
at path: String, | ||
session: URLSession = .shared, | ||
allowsLogin: Bool = true | ||
) -> AnyPublisher<QueryResult<T>, Error> { | ||
|
||
let service = QueryService(.nextResultPage(at: path)) | ||
return go(service: service, session: session, allowsLogin: allowsLogin) | ||
} | ||
|
||
/// Queries all records of the specified type which are owned by the user. | ||
/// - Parameters: | ||
/// - type: Type of record (e.g. `Account`, `Contact` or `MyCustomObject__c`). | ||
/// - fields: Fields to retrieve. If nil, then all fields will be retrieved. | ||
/// - limit: The maximum number of rows to return. | ||
/// - batchSize: The batch size for a query determines the number of rows that are returned in the query results. | ||
/// - user: Specified user, or nil to use the last authenticated user. | ||
/// - session: URL session for the request. | ||
/// - allowsLogin: If authentication is required and allowsLogin is true, the user will be prompted to authenticate via the Salesforce-hosted web login form. | ||
/// - Returns: Publisher of `QueryResult` of `Decodable` instances. | ||
func myRecords<T: Decodable>( | ||
type: String, | ||
fields: [String]? = nil, | ||
limit: Int? = nil, | ||
batchSize: Int = 2000, | ||
user: UserIdentifier? = nil, | ||
session: URLSession = .shared, | ||
allowsLogin: Bool = true | ||
) -> AnyPublisher<QueryResult<T>, Error> { | ||
|
||
let allFields = "FIELDS(ALL)" | ||
|
||
var fieldSpec = "" | ||
if let fields = fields { | ||
fieldSpec = fields.joined(separator: ",") | ||
} | ||
else { | ||
fieldSpec = allFields | ||
} | ||
|
||
var limitSpec = "" | ||
if let limit = limit { | ||
limitSpec = "LIMIT \(limit)" | ||
} | ||
else if fieldSpec == allFields { | ||
limitSpec = "LIMIT 200" | ||
} | ||
|
||
return credentialManager.getCredential(for: user, allowsLogin: allowsLogin) | ||
.map { "SELECT \(fieldSpec) FROM \(type) WHERE OwnerId = '\($0.userID)' \(limitSpec)" } | ||
.flatMap { query(soql: $0, batchSize: batchSize, session: session, allowsLogin: allowsLogin) } | ||
.eraseToAnyPublisher() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
"Swiftly Salesforce: the Swift-est way to build iOS apps that connect to Salesforce" | ||
For more information and license see: https://www.github.com/mike4aday/SwiftlySalesforce | ||
Copyright (c) 2021. All rights reserved. | ||
*/ | ||
|
||
import Foundation | ||
import Combine | ||
|
||
public extension ConnectedApp { | ||
|
||
/// Retrieves a Salesforce record | ||
/// - Parameters: | ||
/// - type: Type of record (e.g. "Account", "Contact" or "MyCustomObject__c"). | ||
/// - id: Unique ID of the record; 15 or 18 characters. | ||
/// - fields: Fields to retrieve. If nil, then all fields will be retrieved. | ||
/// - session: URL session for the request. | ||
/// - allowsLogin: If authentication is required and allowsLogin is true, the user will be prompted to authenticate via the Salesforce-hosted web login form. | ||
/// - Returns: Publisher of a `Decodable` record | ||
/// # Reference | ||
/// [Working with Records](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/using_resources_working_with_records.htm) | ||
func retrieve<T: Decodable>(type: String, id: String, fields: [String]? = nil, session: URLSession = .shared, allowsLogin: Bool = true) -> AnyPublisher<T, Error> { | ||
go(service: SObjectsService(.read(type: type, id: id, fields: fields)), session: session, allowsLogin: allowsLogin) | ||
} | ||
|
||
/// Inserts a Salesforce record | ||
/// - Parameters: | ||
/// - type: Type of record (e.g. `Account`, `Contact` or `MyCustomObject__c`). | ||
/// - fields: Dictionary of fields names and values to insert. | ||
/// - session: URL session for the request. | ||
/// - allowsLogin: If authentication is required and allowsLogin is true, the user will be prompted to authenticate via the Salesforce-hosted web login form. | ||
/// - Returns: Publisher that emits the ID of the successfully-inserted record, or an error. | ||
func insert<T: Encodable>(type: String, fields: [String:T], session: URLSession = .shared, allowsLogin: Bool = true) -> AnyPublisher<String, Error> { | ||
AnyPublisher<SObjectsService, Error> | ||
.just(try SObjectsService(.create(type: type, fields: fields))) | ||
.flatMap { go(service: $0, session: session, allowsLogin: allowsLogin) } | ||
.map { (output: CreateSObjectResult) -> String in | ||
output.id | ||
} | ||
.eraseToAnyPublisher() | ||
} | ||
|
||
/// Updates a Salesforce record | ||
/// - Parameters: | ||
/// - type: Type of record (e.g. `Account`, `Contact` or `MyCustomObject__c`). | ||
/// - id: Unique ID of the record; 15 or 18 characters. | ||
/// - fields: Dictionary of fields names and values to update. | ||
/// - session: URL session for the request. | ||
/// - allowsLogin: If authentication is required and allowsLogin is true, the user will be prompted to authenticate via the Salesforce-hosted web login form. | ||
/// - Returns: Publisher. | ||
func update<T: Encodable>(type: String, id: String, fields: [String:T], session: URLSession = .shared, allowsLogin: Bool = true) -> AnyPublisher<Void, Error> { | ||
AnyPublisher<SObjectsService, Error> | ||
.just(try SObjectsService(.update(type: type, id: id, fields: fields))) | ||
.flatMap { go(service: $0, session: session, allowsLogin: allowsLogin) } | ||
.map { (_: Data) in return } | ||
.eraseToAnyPublisher() | ||
} | ||
|
||
/// Deletes a Salesforce record | ||
/// - Parameters: | ||
/// - type: Type of record (e.g. `Account`, `Contact` or `MyCustomObject__c`). | ||
/// - id: Unique ID of the record; 15 or 18 characters. | ||
/// - session: URL session for the request. | ||
/// - allowsLogin: If authentication is required and allowsLogin is true, the user will be prompted to authenticate via the Salesforce-hosted web login form. | ||
/// - Returns: Publisher. | ||
func delete(type: String, id: String, session: URLSession = .shared, allowsLogin: Bool = true) -> AnyPublisher<Void, Error> { | ||
go(service: SObjectsService(.delete(type: type, id: id)), session: session, allowsLogin: allowsLogin) | ||
.map { (_: Data) in return } | ||
.eraseToAnyPublisher() | ||
} | ||
|
||
/// Retrieves metadata about a Salesforce object | ||
/// - Parameters: | ||
/// - type: Type of record (e.g. `Account`, `Contact` or `MyCustomObject__c`). | ||
/// - session: URL session for the request. | ||
/// - allowsLogin: If authentication is required and allowsLogin is true, the user will be prompted to authenticate via the Salesforce-hosted web login form. | ||
/// - Returns: Publisher of metadata about the object. | ||
/// # Reference | ||
/// [sObject Describe](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_sobject_describe.htm) | ||
func describe(type: String, session: URLSession = .shared, allowsLogin: Bool = true) -> AnyPublisher<ObjectDescription, Error> { | ||
go(service: SObjectsService(.describe(type: type)), session: session, allowsLogin: allowsLogin) | ||
} | ||
|
||
/// Describes all accessible objects in the user's Salesforce org | ||
/// - Parameters: | ||
/// - session: URL session for the request. | ||
/// - allowsLogin: If authentication is required and allowsLogin is true, the user will be prompted to authenticate via the Salesforce-hosted web login form. | ||
/// - Returns: Publisher of an array of object metadata. | ||
func describeAll(session: URLSession = .shared, allowsLogin: Bool = true) -> AnyPublisher<[ObjectDescription], Error> { | ||
struct DescribeAllResult: Decodable { | ||
var sobjects: [ObjectDescription] | ||
} | ||
return go(service: SObjectsService(.describeAll), session: session, allowsLogin: allowsLogin) | ||
.map { (result: DescribeAllResult) -> [ObjectDescription] in | ||
return result.sobjects | ||
} | ||
.eraseToAnyPublisher() | ||
} | ||
} | ||
|
||
fileprivate struct CreateSObjectResult: Decodable { | ||
var id: String | ||
var errors: [SalesforceError] | ||
var success: Bool | ||
} |
Oops, something went wrong.