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

[OPEN DEV] Add Swiftlint to BraintreeApplePay #1366

Merged
merged 7 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
18 changes: 18 additions & 0 deletions .github/workflows/swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Lint
on: [pull_request]
concurrency:
group: lint-${{ github.event.number }}
cancel-in-progress: true
jobs:
swiftlint:
name: SwiftLint
runs-on: macOS-14-xlarge
steps:
- name: Check out repository
uses: actions/checkout@v3
- name: Use Xcode 15.0.1
run: sudo xcode-select -switch /Applications/Xcode_15.0.1.app
- name: Install SwiftLint
run: brew install swiftlint
- name: Run SwiftLint
run: swiftlint --strict
107 changes: 107 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Reference: https://github.com/realm/SwiftLint
# Required Swiftlint Version
# swiftlint_version: 0.39.2

# Paths to include in lint
included:
- Sources/BraintreeCore
- Sources/BraintreeApplePay

excluded:
- Sources/BraintreeCore/BTAPIPinnedCertificates.swift

disabled_rules:
- todo
- type_name # tests will have have the format <SUT>_Tests
- xctfail_message
- blanket_disable_command
- non_optional_string_data_conversion
- attributes
- multiline_function_chains

opt_in_rules:
- array_init
- closure_end_indentation
- closure_spacing
- collection_alignment
- colon # promote to error
- convenience_type
- discouraged_object_literal
- empty_collection_literal
- empty_count
- empty_string
- enum_case_associated_values_count
- fatal_error_message
- first_where
- force_unwrapping
- implicitly_unwrapped_optional
- indentation_width
- last_where
- legacy_random
- literal_expression_end_indentation
- multiline_arguments
- multiline_function_chains
- multiline_literal_brackets
- multiline_parameters
- multiline_parameters_brackets
- operator_usage_whitespace
- overridden_super_call
- pattern_matching_keywords
- prefer_self_type_over_type_of_self
- redundant_nil_coalescing
- redundant_type_annotation
- strict_fileprivate
- toggle_bool
- trailing_closure
- unneeded_parentheses_in_closure_argument
- vertical_whitespace_closing_braces
- yoda_condition

custom_rules:
array_constructor:
name: "Array/Dictionary initializer"
regex: '[let,var] .+ = (\[.+\]\(\))'
capture_group: 1
message: "Use explicit type annotation when initializing empty arrays and dictionaries"
severity: warning
space_after_main_type:
name: "No space after main type"
regex: '(class|struct|extension)((?-s)\s.*\{$\n)(?!^\s*$)'
message: "Empty line required after main declarations"
severity: warning

force_cast: warning
force_try: warning
function_body_length:
warning: 60

legacy_hashing: error

identifier_name:
excluded:
- i
- id
- x
- y
- z

indentation_width:
indentation_width: 4

line_length:
warning: 140
ignores_urls: true
ignores_comments: true

multiline_arguments:
first_argument_location: next_line
only_enforce_after_first_closure_on_first_line: true

private_over_fileprivate:
validate_extensions: true

trailing_whitespace:
ignores_empty_lines: true

vertical_whitespace:
max_empty_lines: 2
21 changes: 21 additions & 0 deletions Braintree.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2237,6 +2237,7 @@
570B9385285397520041BAFE /* Frameworks */,
570B9386285397520041BAFE /* Headers */,
570B93A8285397520041BAFE /* Resources */,
BE676C532C417B8F000A6579 /* Swiftlint */,
);
buildRules = (
);
Expand Down Expand Up @@ -3154,6 +3155,26 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
BE676C532C417B8F000A6579 /* Swiftlint */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = Swiftlint;
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "#!/bin/sh\n\nif test -d \"/opt/homebrew/bin/\"; then\n PATH=\"/opt/homebrew/bin/:${PATH}\"\nfi\n\nexport PATH\n\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
showEnvVarsInLog = 0;
};
CDAB67F3BC5BE564FCFFD6BC /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
Expand Down
4 changes: 2 additions & 2 deletions Sources/BraintreeApplePay/BTApplePayClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import BraintreeCore
/// - Throws: An `Error` describing the failure
public func makePaymentRequest() async throws -> PKPaymentRequest {
try await withCheckedThrowingContinuation { continuation in
makePaymentRequest() { paymentRequest, error in
makePaymentRequest { paymentRequest, error in
if let error {
continuation.resume(throwing: error)
} else if let paymentRequest {
Expand Down Expand Up @@ -102,7 +102,7 @@ import BraintreeCore
return
}

guard let applePayNonce: BTApplePayCardNonce = BTApplePayCardNonce(json: body["applePayCards"][0]) else {
guard let applePayNonce = BTApplePayCardNonce(json: body["applePayCards"][0]) else {
self.notifyFailure(with: BTApplePayError.failedToCreateNonce, completion: completion)
return
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/BraintreeApplePay/BTApplePayError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ public enum BTApplePayError: Int, Error, CustomNSError, LocalizedError, Equatabl
case .unknown:
return ""
case .unsupported:
// swiftlint:disable line_length
return "Apple Pay is not enabled for this merchant. Please ensure that Apple Pay is enabled in the control panel and then try saving an Apple Pay payment method again."
// swiftlint:enable line_length
case .noApplePayCardsReturned:
return "No Apple Pay Card data was returned. Please contact support."
case .failedToCreateNonce:
Expand Down
8 changes: 7 additions & 1 deletion Sources/BraintreeCore/Analytics/BTAnalyticsService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ class BTAnalyticsService: Equatable {

// MARK: - Internal Properties

// swiftlint:disable force_unwrapping
/// The FPTI URL to post all analytic events.
static let url = URL(string: "https://api.paypal.com")!
// swiftlint:enable force_unwrapping

/// The HTTP client for communication with the analytics service endpoint. Exposed for testing.
var http: BTHTTP?
Expand Down Expand Up @@ -131,7 +133,11 @@ class BTAnalyticsService: Equatable {
if await !BTAnalyticsService.events.isEmpty {
do {
let configuration = try await apiClient.fetchConfiguration()
let postParameters = await createAnalyticsEvent(config: configuration, sessionID: apiClient.metadata.sessionID, events: Self.events.allValues)
let postParameters = await createAnalyticsEvent(
config: configuration,
sessionID: apiClient.metadata.sessionID,
events: Self.events.allValues
)
http?.post("v1/tracking/batch/events", parameters: postParameters) { _, _, _ in }
await Self.events.removeAll()
} catch {
Expand Down
11 changes: 7 additions & 4 deletions Sources/BraintreeCore/Analytics/FPTIBatchData.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import UIKit

// swiftlint:disable nesting
/// The POST body for a batch upload of FPTI events
struct FPTIBatchData: Codable {

let events: [EventsContainer] // Single-element "events" array required by FPTI formatting

init(metadata: Metadata, events fptiEvents: [Event]?) {
self.events = [EventsContainer(
metadata: metadata,
fptiEvents: fptiEvents ?? []
)]
self.events = [
EventsContainer(
metadata: metadata,
fptiEvents: fptiEvents ?? []
)
]
}

struct EventsContainer: Codable {
Expand Down
8 changes: 3 additions & 5 deletions Sources/BraintreeCore/Authorization/BTClientToken.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Foundation
/// An authorization string used to initialize the Braintree SDK
@_documentation(visibility: private)
@objcMembers public class BTClientToken: NSObject, NSCoding, NSCopying, ClientAuthorization {

// NEXT_MAJOR_VERSION (v7): properties exposed for Objective-C interoperability + Drop-in access.
// Once the entire SDK is in Swift, determine if we want public properties to be internal and
// what we can make internal without breaking the Drop-in.
Expand Down Expand Up @@ -38,8 +38,7 @@ import Foundation
// Client token must be decoded first because the other values are retrieved from it
self.json = try Self.decodeClientToken(clientToken)

guard let authorizationFingerprint = json["authorizationFingerprint"].asString(),
!authorizationFingerprint.isEmpty else {
guard let authorizationFingerprint = json["authorizationFingerprint"].asString(), !authorizationFingerprint.isEmpty else {
throw BTClientTokenError.invalidAuthorizationFingerprint
}

Expand Down Expand Up @@ -113,8 +112,7 @@ import Foundation
// MARK: - NSObject override

public override func isEqual(_ object: Any?) -> Bool {
guard object is BTClientToken,
let otherToken = object as? BTClientToken else {
guard object is BTClientToken, let otherToken = object as? BTClientToken else {
return false
}

Expand Down
2 changes: 2 additions & 0 deletions Sources/BraintreeCore/Authorization/BTClientTokenError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public enum BTClientTokenError: Error, CustomNSError, LocalizedError, Equatable
}
}

// swiftlint:disable line_length
public var errorDescription: String? {
switch self {
case .invalidAuthorizationFingerprint:
Expand All @@ -51,4 +52,5 @@ public enum BTClientTokenError: Error, CustomNSError, LocalizedError, Equatable
return "Failed to decode client token. \(description)"
}
}
// swiftlint:enable line_length
}
6 changes: 3 additions & 3 deletions Sources/BraintreeCore/Authorization/TokenizationKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ class TokenizationKey: ClientAuthorization {
guard tokenizationKey.range(of: pattern, options: .regularExpression) != nil else { return nil }

let tokenizationKeyParts = tokenizationKey.split(separator: "_", maxSplits: 3)
let environment: String = String(tokenizationKeyParts[0])
let merchantID: String = String(tokenizationKeyParts[2])
let environment = String(tokenizationKeyParts[0])
let merchantID = String(tokenizationKeyParts[2])

var components: URLComponents = URLComponents()
var components = URLComponents()
components.scheme = environment == "development" ? "http" : "https"

guard let host = host(for: environment) else { return nil }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,3 @@ public enum TokenizationKeyError: Int, Error, CustomNSError, LocalizedError, Equ
}
}
}

26 changes: 20 additions & 6 deletions Sources/BraintreeCore/BTAPIClient.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation

// swiftlint:disable type_body_length file_length
/// This class acts as the entry point for accessing the Braintree APIs via common HTTP methods performed on API endpoints.
/// - Note: It also manages authentication via tokenization key and provides access to a merchant's gateway configuration.
@objcMembers public class BTAPIClient: NSObject, BTHTTPNetworkTiming {
Expand Down Expand Up @@ -46,7 +47,7 @@ import Foundation
switch authorizationType {
case .tokenizationKey:
do {
self.authorization = try TokenizationKey(authorization)
self.authorization = try TokenizationKey(authorization)
} catch {
return nil
}
Expand All @@ -68,7 +69,7 @@ import Foundation
http?.networkTimingDelegate = self

// Kickoff the background request to fetch the config
fetchOrReturnRemoteConfiguration { configuration, error in
fetchOrReturnRemoteConfiguration { _, _ in
// No-op
}
}
Expand Down Expand Up @@ -148,7 +149,7 @@ import Foundation
"session_id": metadata.sessionID
]

get("v1/payment_methods", parameters: parameters) { body, response, error in
get("v1/payment_methods", parameters: parameters) { body, _, error in
if let error {
completion(nil, error)
return
Expand All @@ -158,7 +159,10 @@ import Foundation

body?["paymentMethods"].asArray()?.forEach { paymentInfo in
let type: String? = paymentInfo["type"].asString()
let paymentMethodNonce: BTPaymentMethodNonce? = BTPaymentMethodNonceParser.shared.parseJSON(paymentInfo, withParsingBlockForType: type)
let paymentMethodNonce: BTPaymentMethodNonce? = BTPaymentMethodNonceParser.shared.parseJSON(
paymentInfo,
withParsingBlockForType: type
)

if let paymentMethodNonce {
paymentMethodNonces.append(paymentMethodNonce)
Expand Down Expand Up @@ -269,7 +273,13 @@ import Foundation
}

let postParameters = BTAPIRequest(requestBody: parameters, metadata: metadata, httpType: httpType)
http(for: httpType)?.post(path, configuration: configuration, parameters: postParameters, headers: headers, completion: completion)
http(for: httpType)?.post(
path,
configuration: configuration,
parameters: postParameters,
headers: headers,
completion: completion
)
}
}

Expand Down Expand Up @@ -339,7 +349,11 @@ import Foundation
let pattern: String = "([a-zA-Z0-9]+)_[a-zA-Z0-9]+_([a-zA-Z0-9_]+)"
guard let regularExpression = try? NSRegularExpression(pattern: pattern) else { return nil }

let tokenizationKeyMatch: NSTextCheckingResult? = regularExpression.firstMatch(in: authorization, options: [], range: NSRange(location: 0, length: authorization.count))
let tokenizationKeyMatch: NSTextCheckingResult? = regularExpression.firstMatch(
in: authorization,
options: [],
range: NSRange(location: 0, length: authorization.count)
)

return tokenizationKeyMatch != nil ? .tokenizationKey : .clientToken
}
Expand Down
12 changes: 5 additions & 7 deletions Sources/BraintreeCore/BTAppContextSwitcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import UIKit

// MARK: - Private Properties

private var appContextSwitchClients = [BTAppContextSwitchClient.Type]()
private var appContextSwitchClients: [BTAppContextSwitchClient.Type] = []

// MARK: - Public Methods

/// Determine whether the return URL can be handled.
Expand All @@ -35,11 +35,9 @@ import UIKit
/// - Returns: `true` when the SDK has handled the URL successfully
@objc(handleOpenURL:)
@discardableResult public func handleOpen(_ url: URL) -> Bool {
for appContextSwitchClient in appContextSwitchClients {
if appContextSwitchClient.canHandleReturnURL(url) {
appContextSwitchClient.handleReturnURL(url)
return true
}
for appContextSwitchClient in appContextSwitchClients where appContextSwitchClient.canHandleReturnURL(url) {
appContextSwitchClient.handleReturnURL(url)
return true
}
return false
}
Expand Down
Loading
Loading