diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Actions/Expectaction.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Actions/Expectaction.swift index d3669a3290..13b03761de 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Actions/Expectaction.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Actions/Expectaction.swift @@ -29,11 +29,50 @@ struct Item: Codable, Sendable { let expect: String? let selector: String? let parent: String? + let failSilently: Bool? } -internal struct ExpectationAction: Action { +internal final class ExpectationAction: Action { let id: String let actionType: ActionType let expectations: [Item] let dataSource: DataSource? + let actions: [Action]? + + enum CodingKeys: String, CodingKey { + case id, actionType, expectations, dataSource, actions + } + + init(id: String, actionType: ActionType, expectations: [Item], dataSource: DataSource?, actions: [Action]?) { + self.id = id + self.actionType = actionType + self.expectations = expectations + self.dataSource = dataSource + self.actions = actions + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.actionType = try container.decode(ActionType.self, forKey: .actionType) + self.expectations = try container.decode([Item].self, forKey: .expectations) + self.dataSource = try container.decodeIfPresent(DataSource.self, forKey: .dataSource) + let actionsList = try container.decodeIfPresent([[String: Any]].self, forKey: .actions) + if let actionsList = actionsList { + self.actions = try Step.parse(actionsList) + } else { + self.actions = nil + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(actionType, forKey: .actionType) + try container.encode(expectations, forKey: .expectations) + try container.encode(dataSource, forKey: .dataSource) + + var actionsContainer = container.nestedUnkeyedContainer(forKey: .actions) + try actions?.encode(to: &actionsContainer) + } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Step.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Step.swift index d1ffc96754..97b13dd9a9 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Step.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/Step.swift @@ -58,25 +58,7 @@ struct Step: Codable, Sendable { try container.encode(optOutType, forKey: .optOutType) var actionsContainer = container.nestedUnkeyedContainer(forKey: .actions) - for action in actions { - if let navigateAction = action as? NavigateAction { - try actionsContainer.encode(navigateAction) - } else if let extractAction = action as? ExtractAction { - try actionsContainer.encode(extractAction) - } else if let fillFormAction = action as? FillFormAction { - try actionsContainer.encode(fillFormAction) - } else if let getCaptchaInfoAction = action as? GetCaptchaInfoAction { - try actionsContainer.encode(getCaptchaInfoAction) - } else if let solveCaptchaInfoAction = action as? SolveCaptchaAction { - try actionsContainer.encode(solveCaptchaInfoAction) - } else if let emailConfirmationAction = action as? EmailConfirmationAction { - try actionsContainer.encode(emailConfirmationAction) - } else if let clickAction = action as? ClickAction { - try actionsContainer.encode(clickAction) - } else if let expectactionAction = action as? ExpectationAction { - try actionsContainer.encode(expectactionAction) - } - } + try actions.encode(to: &actionsContainer) } static func parse(_ actions: [[String: Any]]) throws -> [Action] { @@ -120,3 +102,27 @@ struct Step: Codable, Sendable { return actionList } } + +extension Array where Element == Action { + func encode(to container: inout any UnkeyedEncodingContainer) throws { + for action in self { + if let navigateAction = action as? NavigateAction { + try container.encode(navigateAction) + } else if let extractAction = action as? ExtractAction { + try container.encode(extractAction) + } else if let fillFormAction = action as? FillFormAction { + try container.encode(fillFormAction) + } else if let getCaptchaInfoAction = action as? GetCaptchaInfoAction { + try container.encode(getCaptchaInfoAction) + } else if let solveCaptchaInfoAction = action as? SolveCaptchaAction { + try container.encode(solveCaptchaInfoAction) + } else if let emailConfirmationAction = action as? EmailConfirmationAction { + try container.encode(emailConfirmationAction) + } else if let clickAction = action as? ClickAction { + try container.encode(clickAction) + } else if let expectactionAction = action as? ExpectationAction { + try container.encode(expectactionAction) + } + } + } +} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/CodableExtension.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/CodableExtension.swift index 46cc1e4a56..4106124961 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/CodableExtension.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Utils/CodableExtension.swift @@ -59,6 +59,16 @@ extension KeyedDecodingContainer { return try decode(type, forKey: key) } + func decodeIfPresent(_ type: [[String: Any]].Type, forKey key: K) throws -> [[String: Any]]? { + guard contains(key) else { + return nil + } + guard try decodeNil(forKey: key) == false else { + return nil + } + return try decode(type, forKey: key) + } + func decode(_ type: [Any].Type, forKey key: K) throws -> [Any] { var container = try self.nestedUnkeyedContainer(forKey: key) return try container.decode(type) diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerOperationActionTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerOperationActionTests.swift index 330619c12d..cc14f56b56 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerOperationActionTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerOperationActionTests.swift @@ -331,7 +331,7 @@ final class DataBrokerOperationActionTests: XCTestCase { } func testWhenRunningActionWithoutExtractedProfile_thenExecuteIsCalledWithProfileData() async { - let expectationAction = ExpectationAction(id: "1", actionType: .expectation, expectations: [Item](), dataSource: nil) + let expectationAction = ExpectationAction(id: "1", actionType: .expectation, expectations: [Item](), dataSource: nil, actions: nil) let sut = OptOutJob( privacyConfig: PrivacyConfigurationManagingMock(), prefs: ContentScopeProperties.mock, @@ -415,7 +415,7 @@ final class DataBrokerOperationActionTests: XCTestCase { func testWhenExpectationActionRuns_thenStageIsSetToSubmit() async { let mockStageCalculator = MockStageDurationCalculator() - let expectationAction = ExpectationAction(id: "1", actionType: .expectation, expectations: [Item](), dataSource: nil) + let expectationAction = ExpectationAction(id: "1", actionType: .expectation, expectations: [Item](), dataSource: nil, actions: nil) let sut = OptOutJob( privacyConfig: PrivacyConfigurationManagingMock(), prefs: ContentScopeProperties.mock,