Skip to content

Server Capability Listing #39

@i2h3

Description

@i2h3

Add a method to fetch the capabilities of a server, both without and with authentication (due to the difference in response for security reasons).

The API must be extendable for third-parties. Downstream projects must be abled to define their own capabilities which can be checked for equally.

Xcode Playground Code

import Foundation

// MARK: - Capability System

enum CapabilityError: Error {
    case keyNotFound
}

///
/// A representation of a server capability.
///
protocol Capability {
    ///
    /// Assert that the capability is available based on specific verification code.
    ///
    static func assert(_ object: [String: Any]) -> Bool

    ///
    /// Create a native Swift representation based on the given capabilities server response object.
    ///
    init(_ object: [String : Any]) throws
}

///
/// A container for the capabilities retrieved from a server.
///
struct CapabilitySet {
    ///
    /// The parsed JSON object but without final types.
    ///
    private let object: [String: Any]

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

    ///
    /// Attempt to extract a capability from the container.
    ///
    /// - Returns: The failure to find the given capability is not an error but expected run time variation depending on server configuration. Hence, the returned value is `nil` in case the capability was not found in the returned capability set.
    ///
    /// - Throws: An error is thrown in case a capability was found but could not be parsed correctly from the server response.
    ///
    func get(_ type: Capability.Type) throws -> (any Capability)? {
        guard type.assert(object) else {
            return nil
        }

        return try type.init(object)
    }
}

// MARK: - Capability Implementations

///
/// Whether or not the server is capable of trashing and restoring files or just deletes them directly.
///
struct Trash: Capability {
    static func assert(_ object: [String : Any]) -> Bool {
        object.keys.contains("undelete")
    }

    init(_ object: [String : Any]) {
        // Nothing to do with the argument in this case.
    }
}

///
/// This does not expose all the theming properties to keep the example on point.
///
struct Theming: Capability {
    let name: String
    let color: String

    init(_ object: [String : Any]) throws {
        guard let theming = object["theming"] as? [String: Any] else {
            throw CapabilityError.keyNotFound
        }

        guard let name = theming["name"] as? String else {
            throw CapabilityError.keyNotFound
        }

        guard let color = theming["color"] as? String else {
            throw CapabilityError.keyNotFound
        }

        self.name = name
        self.color = color
    }

    static func assert(_ object: [String : Any]) -> Bool {
        object.keys.contains("theming")
    }
}

// MARK: - Mock Server

struct Server {
    private let theming: Bool
    private let trash: Bool

    init(theming: Bool = false, trash: Bool = false) {
        self.theming = theming
        self.trash = trash
    }

    func fetchCapabilities() -> CapabilitySet {
        var object: [String: Any] = [:]

        if theming {
            object["theming"] = [
                "name": "Rainmaker",
                "url": "http://localhost:8081",
                "slogan": "Rainmaker Test Backend",
                "color": "#89C1FD",
                "color-text": "#000000",
                "color-element": "#89C1FD",
                "color-element-bright": "#89C1FD",
                "color-element-dark": "#89C1FD",
                "logo": "http://localhost:8081/core/img/logo/logo.svg?v=11",
                "background": "#2d73be",
                "background-text": "#ffffff",
                "background-plain": "1",
                "background-default": "1",
                "logoheader": "http://localhost:8081/core/img/logo/logo.svg?v=11",
                "favicon": "http://localhost:8081/core/img/logo/logo.svg?v=11"
            ]
        }

        if trash {
            object["undelete"] = "1"
        }

        return CapabilitySet(object: object)
    }
}

// MARK: - Example Usage

let server = Server(theming: true)
let capabilities = server.fetchCapabilities()

do {
    if let capability = try capabilities.get(Theming.self) {
        print("Theming supported on server 1: \(capability)")
    } else {
        print("Theming unsupported on server 1.")
    }
} catch {
    print("Error: \(error)")
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions