ConfigurationTransformer

open class ConfigurationTransformer : ConfigurationTransforming

ConfigurationTransformer transforms a [String: Any] configuration to typed objects and returns them in a [OnboardingInfoKey: Any]. It uses other ConfigurationTransforming instances for the transformation, making it a recursive transformer which can be used to process complex, deep configuration hierarchies. The transformation process iterates through the keys of the configuration Dictionary and looks up the transformers using that key (in the transormer dictionary). When a transformer is found under the specified key, the corresponding sub-configuration is passed to it for processing. All transformers’ result is merged internally together, and the final result is a flat Dictionary of [OnboardingInfoKey: Any]. The SDK ships other transformers to help the transforming of any configuration and of course it can be used with custom transformers as well.

An example: Given the following sample structure:

    let configuration : [String: Any] = [
        "someURL": "https://www.sap.com",
        "someString": "An arbitrary string",
        "simpleStructure": [
            "firstStringField": "first string value",
            "secondURLField": "https://www.sap.com"
        ],
        "complexStructure": [
            "some.oauth.params.key": [
                "authorizationEndpointURL": "https://someoauthservice.sap.hana.ondemand.com/oauth2/api/v1/authorize",
                "clientID": "66962837-fd94-4dcd-acf7-ade549224e78",
                "redirectURL": "https://someoauthservice.int.sap.hana.ondemand.com",
                "tokenEndpointURL": "https://someoauthservice.int.sap.hana.ondemand.com/oauth2/api/v1/token"
            ],
            "some.saml.params.key": [
                "authenticationEndpoint": "https://somesamlservice.int.sap.hana.ondemand.com/SAMLAuthLauncher",
                "finishEndpoint": "https://somesamlservice.int.sap.hana.ondemand.com/SAMLAuthLauncher?finishEndpointParam=someUnusedValue"
            ],
            "DiscoveryServiceConfigForCertificate": [
                "com.sap.mobilesecure.certificateService.attributesEndpoint": "http://some.dummy.url",
                "com.sap.mobilesecure.certificateService.requestEndpoint": "http://some.dummy.url",
                "com.sap.mobilesecure.certificateService.retireEndpoint": "http://some.dummy.url",
                "com.sap.mobilesecure.certificateService.publicKeyPinSet": [],
                "com.sap.mobilesecure.certificateService.authType": [
                    "type":"oauth",
                    "authorizationEndpoint": "http://some.dummy.url",
                    "tokenEndpoint": "http://some.dummy.url",
                    "client_id": "1234",
                    "redirect_uri": "http://some.dummy.url"
                ]
            ]
        ]
    ]

This configuration must be converted to a flat [OnboardingInfoKey: Any] dictionary where the values are appropriate types (struct, URL, String, etc). So as a result we have to get the following object:

    let transformedDict : [OnboardingInfoKey: Any] = [
        .requiredURL: URL,
        .requiredString String,
        .simpleStructure: SimpleStructure,
        .oauth2Parameters: OAuth2AuthenticationParameters,
        .samlParameters: SAMLAuthenticationParameters,
        .oauth2ParamsForCertificate: OAuth2AuthenticationParameters,
        .discoveryServiceParametersForCertificate: SAPcpmsUserIdentityConfigurationParameters
    ]

To transform the source configuration dictionary to the expected result, the ConfigurationTransformer must be used with appropriate transformers mirroring the structure:

    // let's describe the given configuration with a transformation where we specify the possible keys, values as:
    //    source: typedtransformer( destination )
    let configurationTransformers: [String: ConfigurationTransforming] = [
        "someURL": BaseTransformer(targetKey: .requiredURL, transformer: transformToURL),
        "someString": BaseTransformer(targetKey: .requiredString, transformer: transformToString),
        "simpleStructure": BaseTransformer(targetKey: .simpleStructure, transformer: SimpleStructure.init(customConfig:)),
        "complexStructure": DictionaryConfigurationTransformer(transformers: [
            "some.oauth.params.key": BaseTransformer(targetKey: .oauth2Parameters, transformer: OAuth2AuthenticationParameters.init(cloudPlatformConfig:)),
            "some.saml.params.key": BaseTransformer(targetKey: .samlParameters, transformer: SAMLAuthenticationParameters.init(cloudPlatformConfig:)),
            "DiscoveryServiceConfigForCertificate": CustomDiscoveryServiceConfigurationTransformer(key: .discoveryServiceParametersForCertificate, oauthKey: .oauth2ParamsForCertificate)
        ])
    ]

and use the transformer and the transformed data:

    let configurationTransformer = ConfigurationTransformer(transformers: configurationTransformers)
    do {
        let transformedDictionary = try configurationTransformer(config: configuration)
        if let simpleStructure = transformedDictionary[.simpleStructure] as? SimpleStructure {
            // do something
        }
    }
    catch {
        print("some error happened \(error)")
    }

Of course as a prerequisite the OnboardingInfoKeys must be declared, and the used transformers must exists.

    // specifying onboarding info keys: declared by the onboarding steps
    public extension OnboardingInfoKey {
        public static let requiredURL = OnboardingInfoKey("requiredURLKey")
        public static let requiredString = OnboardingInfoKey("requiredStringKey")
        public static let simpleStructure = OnboardingInfoKey("simpleStructureKey")
        public static let oauth2Parameters = OnboardingInfoKey("oauth2ParametersKey")
        public static let samlParameters = OnboardingInfoKey("samlParametersKey")
        public static let oauth2ParamsForCertificate = OnboardingInfoKey("oauth2ParametersForCertificateKey")
        public static let discoveryServiceParametersForCertificate = OnboardingInfoKey("discoveryServiceParametersForCertificateKey")
    }

    // declaring a custom structure
    public struct SimpleStructure {
        let firstStringField: String
        let secondURLField: URL
    }

    // writing a transformer for the structure which can be used directly or by the BaseTransformer with ConfigurationTransformer
    public extension SimpleStructure {
        public init(customConfig config: Any) throws  {
            guard let simpleConfig = config as? [String: String] else {
                throw OnboardingError.invalidArgument("not a dictionary", config, source: "SimpleStructure")
            }
            guard let first = simpleConfig["firstStringField"], let secondStr = simpleConfig["secondURLField"] else {
                throw OnboardingError.missingArgument("first or second parameter missing while parsing SimpleStructure", source: "SimpleStructure")
            }
            let second = try transformToURL(secondStr)
            self.init(firstStringField: first, secondURLField: second)
        }
    }

    open class CustomDiscoveryServiceConfigurationTransformer: ConfigurationTransforming {
        let key : OnboardingInfoKey
        let oauthKey : OnboardingInfoKey
        public init(key : OnboardingInfoKey, oauthKey: OnboardingInfoKey) {
            self.key = key
            self.oauthKey = oauthKey
        }

        open func transform(config: Any) throws -> [OnboardingInfoKey: Any] {
            guard let dict = config as? [String:Any] else {
                throw OnboardingError.invalidArgument("not a dictionary", config, source: "CustomDiscoveryServiceConfigurationTransformer")
            }
            guard let params = SAPcpmsUserIdentityConfigurationParameters(certificateDiscoveryConfiguration: dict) else {
                throw  OnboardingError.invalidArgument("Cannot transform DiscoveryServiceConfigurationParser: Invalid structure", dict, source: "CustomDiscoveryServiceConfigurationTransformer")
            }
            guard let oauthParams = OAuth2AuthenticationParameters(certificateDiscoveryConfiguration: dict) else {
                throw  OnboardingError.missingArgument("Cannot transform DiscoveryServiceConfigurationParser: Invalid structure", source: "CustomDiscoveryServiceConfigurationTransformer")
            }

            return [key: params, oauthKey: oauthParams]// as [OnboardingInfoKey: Any]
        }
    }

In the SDK the different onboarding steps declare their required keys they use for accessign information in the info field of the OnboardingContext. Several default transformers are available as public funcs or default initializers. Several structures which is an input parameter for a step in the Foundation framework implements a convenience initializer with discoveryServiceConfig: prefix which parse the default configuration declared by the SDK.

For example:
  • BaseTransformer can be used with transformers resulting a single object, see a list of examples there.
  • DiscoveryServiceCertificateConfigurationTransformer
  • the transformers used to transform the source dictionary. The keys are used to match the key entries in the source dictionary and the tranformers are used to tranform the data can be found under that key

    Declaration

    Swift

    open var transformationMap: [String : ConfigurationTransforming]
  • Initializes a new instance of the class.

    Declaration

    Swift

    public init(transformationMap: [String : ConfigurationTransforming])

    Parameters

    transformers

    the transformers used to transform the source dictionary. The keys are used to match the key entries in the source dictionary and the tranformers are used to tranform the data can be found under that key

  • tranforms the source data which must be a [String: Any] dictionary using the associated transformers and returns with a [OnboardingInfoKey: Any] dictionary containing the transformed objects under the keys the transformers associated

    Throws

    OnboardingError.invalidArgument or errors thrown by tranformers

    Declaration

    Swift

    open func transform(config: Any) throws -> [OnboardingInfoKey : Any]

    Parameters

    config

    the source configuration to transform must be [String: Any] compatible

    Return Value

    [OnboardingInfoKey: Any] dictionary of the transformed data where the keys and values are associated by the transformers