Skip to content

Commit

Permalink
Merge pull request #4 from ForgeRock/SDKS-3395
Browse files Browse the repository at this point in the history
SDKS-3395 - Align naming and errors for Davinci
  • Loading branch information
spetrov authored Oct 16, 2024
2 parents 13d7f9c + 816ac8f commit 59ddf76
Show file tree
Hide file tree
Showing 19 changed files with 826 additions and 484 deletions.
67 changes: 37 additions & 30 deletions PingDavinci/PingDavinci/Constants.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//
//
// Constants.swift
// PingDavinci
//
Expand All @@ -10,33 +10,40 @@


enum Constants {
static let actionKey = "actionKey"
static let formData = "formData"
static let type = "type"
static let key = "key"
static let label = "label"
static let form = "form"
static let components = "components"
static let fields = "fields"
static let eventType = "eventType"
static let name = "name"
static let id = "id"
static let data = "data"
static let eventName = "eventName"
static let parameters = "parameters"
static let description = "description"
static let category = "category"
static let next = "next"
static let href = "href"
static let _links = "_links"
static let message = "message"
static let status = "status"
static let authorizeResponse = "authorizeResponse"
static let code = "code"
static let FAILED = "FAILED"

static let TEXT = "TEXT"
static let PASSWORD = "PASSWORD"
static let SUBMIT_BUTTON = "SUBMIT_BUTTON"
static let FLOW_BUTTON = "FLOW_BUTTON"
static let actionKey = "actionKey"
static let formData = "formData"
static let type = "type"
static let key = "key"
static let label = "label"
static let form = "form"
static let components = "components"
static let fields = "fields"
static let eventType = "eventType"
static let name = "name"
static let id = "id"
static let data = "data"
static let eventName = "eventName"
static let parameters = "parameters"
static let description = "description"
static let category = "category"
static let next = "next"
static let href = "href"
static let _links = "_links"
static let message = "message"
static let status = "status"
static let authorizeResponse = "authorizeResponse"
static let code = "code"
static let code_1999 = 1999
static let FAILED = "FAILED"
static let TEXT = "TEXT"
static let PASSWORD = "PASSWORD"
static let SUBMIT_BUTTON = "SUBMIT_BUTTON"
static let FLOW_BUTTON = "FLOW_BUTTON"
static let error = "error"
static let connectorId = "connectorId"
static let capabilityName = "capabilityName"
static let requestTimedOut = "requestTimedOut"
static let pingOneAuthenticationConnector = "pingOneAuthenticationConnector"
static let returnSuccessResponseRedirect = "returnSuccessResponseRedirect"
static let setSession = "setSession"
}
2 changes: 1 addition & 1 deletion PingDavinci/PingDavinci/collector/Collector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public protocol Collector: Action, Identifiable {
init(with json: [String: Any])
}

extension Connector {
extension ContinueNode {
public var collectors: [any Collector] {
return actions.compactMap { $0 as? (any Collector) }
}
Expand Down
14 changes: 7 additions & 7 deletions PingDavinci/PingDavinci/module/Connector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

import PingOrchestrate

/// Extension to get the id of a Connector.
extension Connector {
/// Extension to get the id of a ContinueNode.
extension ContinueNode {
public var id: String {
return (self as? DaVinciConnector)?.idValue ?? ""
}
Expand All @@ -32,11 +32,11 @@ extension Connector {


/// Class representing a DaVinciConnector.
///- property context: The FlowContext of the connector.
///- property workflow: The Workflow of the connector.
///- property input: The input JsonObject of the connector.
///- property collectors: The collectors of the connector.
class DaVinciConnector: Connector {
///- property context: The FlowContext of the ContinueNode.
///- property workflow: The Workflow of the ContinueNode.
///- property input: The input JsonObject of the ContinueNode.
///- property collectors: The collectors of the ContinueNode.
class DaVinciConnector: ContinueNode {

init(context: FlowContext, workflow: Workflow, input: [String: Any], collectors: Collectors) {
super.init(context: context, workflow: workflow, input: input, actions: collectors)
Expand Down
2 changes: 1 addition & 1 deletion PingDavinci/PingDavinci/module/Oidc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class OidcModule {
daVinciFlow.sharedContext.set(key: SharedContext.Keys.oidcClientConfigKey, value: config)
//Override the agent setting
config.updateAgent(DefaultAgent())
try? await config.oidcInitialize()
try await config.oidcInitialize()
}

// Starts the module.
Expand Down
103 changes: 64 additions & 39 deletions PingDavinci/PingDavinci/module/Transform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,58 +15,83 @@ import PingOrchestrate

/// Module for transforming the response from DaVinci to `Node`.
public class NodeTransformModule {

public static let config: Module<Void> = Module.of(setup: { setup in
setup.transform { flowContext, response in
switch response.status() {
case 400:
return failure(json: try response.json(data: response.data))
case 200:
return transform(context: flowContext, workflow: setup.workflow, json: try response.json(data: response.data))
default:
return ErrorNode(cause: ApiError.error(response.status(), response.body()))
}

public static let config: Module<Void> = Module.of(setup: { setup in
setup.transform { flowContext, response in
let status = response.status()

let json = try response.json(data: response.data)

let message = json[Constants.message] as? String ?? ""
let body = response.body()

// Check for 4XX errors that are unrecoverable
if (400..<500).contains(status) {
// Filter out client-side "timeout" related unrecoverable failures
if json[Constants.code] as? Int == Constants.code_1999 || json[Constants.code] as? String == Constants.requestTimedOut {
return FailureNode(cause: ApiError.error(status, json, body))
}
})

private static func failure(json: [String: Any]) -> FailureNode {
let message = json[Constants.message] as? String ?? ""
return FailureNode(input: json, message: message)
}

private static func transform(context: FlowContext, workflow: Workflow, json: [String: Any]) -> Node {
// If status is FAILED, return error
if let status = json[Constants.status] as? String, status == Constants.FAILED {
return ErrorNode(cause: ApiError.error(200, json.description))

// Filter our "PingOne Authentication Connector" unrecoverable failures
if let connectorId = json[Constants.connectorId] as? String, connectorId == Constants.pingOneAuthenticationConnector,
let capabilityName = json[Constants.capabilityName] as? String,
[Constants.returnSuccessResponseRedirect, Constants.setSession].contains(capabilityName) {
return FailureNode(cause: ApiError.error(status, json, body))
}

// If authorizeResponse is present, return success
if let _ = json[Constants.authorizeResponse] as? [String: Any] {
return SuccessNode(input: json, session: SessionResponse(json: json))
// If we're still here, we have a 4XX failure that should be recoverable
return ErrorNode(status: status, input: json, message: message)
}

// Handle success (2XX) responses
if status == 200 {
// Filter out 2XX errors with 'failure' status
if let failedStatus = json[Constants.status] as? String, failedStatus == Constants.FAILED {
return FailureNode(cause: ApiError.error(status, json, body))
}

var collectors: Collectors = []
if let _ = json[Constants.form] {
collectors.append(contentsOf: Form.parse(json: json))
// Filter out 2XX errors with error object
if let error = json[Constants.error] as? [String: Any], !error.isEmpty {
return FailureNode(cause: ApiError.error(status, json, body))
}

return DaVinciConnector(context: context, workflow: workflow, input: json, collectors: collectors)
return transform(context: flowContext, workflow: setup.workflow, json: json)
}

// 5XX errors are treated as unrecoverable failures
return FailureNode(cause: ApiError.error(status, json, body))
}
}

struct SessionResponse: Session {
public let json: [String: Any]

public init(json: [String: Any] = [:]) {
self.json = json
})

private static func transform(context: FlowContext, workflow: Workflow, json: [String: Any]) -> Node {
// If authorizeResponse is present, return success
if let _ = json[Constants.authorizeResponse] as? [String: Any] {
return SuccessNode(input: json, session: SessionResponse(json: json))
}

func value() -> String {
let authResponse = json[Constants.authorizeResponse] as? [String: Any]
return authResponse?[Constants.code] as? String ?? ""
var collectors: Collectors = []
if let _ = json[Constants.form] {
collectors.append(contentsOf: Form.parse(json: json))
}

return DaVinciConnector(context: context, workflow: workflow, input: json, collectors: collectors)
}
}

struct SessionResponse: Session {
public let json: [String: Any]

public init(json: [String: Any] = [:]) {
self.json = json
}

func value() -> String {
let authResponse = json[Constants.authorizeResponse] as? [String: Any]
return authResponse?[Constants.code] as? String ?? ""
}
}

public enum ApiError: Error {
case error(Int, String)
case error(Int, [String: Any], String)
}
Loading

0 comments on commit 59ddf76

Please sign in to comment.