Swiftpack.co -  Apodini/Apodini as Swift Package
Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
Apodini/Apodini
Apodini - A declarative, composable server-side Swift framework
.package(url: "https://github.com/Apodini/Apodini.git", from: "0.2.0")

Apodini

DOI codecov jazzy Build and Test

A declarative, composable framework to build web services using Swift.

Getting Started

Installation

Apodini uses the Swift Package Manager:

Add it as a project-dependency:

dependencies: [
    .package(url: "https://github.com/Apodini/Apodini.git", .branch("develop"))
]

Add the base package and all exporters you want to use to your target:

targets: [
    .target(
        name: "Your Target",
        dependencies: [
            .product(name: "Apodini", package: "Apodini"),
            .product(name: "ApodiniREST", package: "Apodini"),
            .product(name: "ApodiniOpenAPI", package: "Apodini")
        ])
]‚

Hello World

Getting started is really easy:

import Apodini
import ApodiniREST

struct Greeter: Handler {
    @Parameter var country: String?

    func handle() -> String {
        "Hello, \(country ?? "World")!"
    }
}

struct HelloWorld: WebService {
    var configuration: Configuration {
        REST()
    }

    var content: some Component {
        Greeter()
    }
}

HelloWorld.main()

// http://localhost:8080/v1 -> Hello, World!
// http://localhost:8080/v1?country=Italy -> Hello, Italy!

Apodini knows enough about your service to automatically generate OpenAPI docs. Just add the respective exporter:

import ApodiniOpenAPI
...
struct HelloWorld: WebService {
    var configuration: Configuration {
        REST { 
            OpenAPI()
        }
    }
    ...
}

// JSON definition: http://localhost:8080/openapi
// Swagger UI: http://localhost:8080/openapi-ui

With Bindings we can re-use Handlers in different contexts:

struct Greeter: Handler {
    @Binding var country: String?

    func handle() -> String {
        "Hello, \(country ?? "World")!"
    }
}

struct HelloWorld: WebService {
    var configuration: Configuration {
        REST { 
            OpenAPI()
        }
    }

    var content: some Component {
        Greeter(country: nil)
            .description("Say 'Hello' to the World.")
        Group("country") {
            CountrySubsystem()
        }
    }
}

struct CountrySubsystem: Component {
    @PathParameter var country: String
    
    var content: some Component {
        Group($country) {
            Greeter(country: Binding<String?>($country))
                .description("Say 'Hello' to a country.")
        }
    }
}

// http://localhost:8080/v1 -> Hello, World!
// http://localhost:8080/v1/country/Italy -> Hello, Italy!

Apodini allows the developer to specify CLI-arguments that are passed to the WebService. The arguments can for example be used in Configuration:

struct HelloWorld: WebService {
    @Flag(help: "Generate an OpenAPI documentation of the WebService.")
    var generateOpenAPIDocs = false
    
    var configuration: Configuration {
        if(generateOpenAPIDocs) {
            REST { 
                OpenAPI()
            }
        } else {
            REST()
        }
    }
}

For further information on how to specify CLI-arguments see https://github.com/apple/swift-argument-parser

Documentation

The framework is in early alpha phase. You can inspect the current development manifestos describing the framework in the documentation folder

You can find a generated technical documentation for the different Swift types at https://apodini.github.io/Apodini

Contributing

Contributions to this project are welcome. Please make sure to read the contribution guidelines first.

License

This project is licensed under the MIT License. See License for more information.

GitHub

link
Stars: 24
Last commit: 1 hour ago

Ad: Job Offers

iOS Software Engineer @ Perry Street Software
Perry Street Software is Jack’d and SCRUFF. We are two of the world’s largest gay, bi, trans and queer social dating apps on iOS and Android. Our brands reach more than 20 million members worldwide so members can connect, meet and express themselves on a platform that prioritizes privacy and security. We invest heavily into SwiftUI and using Swift Packages to modularize the codebase.

Release Notes

0.2.0 - White-tipped Swift
2 days ago

SemVer Major

  • Generic Handler Delegation: Environment Injection + Variable ObservedObject + Guard/ResponseTransformer Replacement @theMomax (#286)

    Migration Guide

    The environment based initializer for ObservedObject (e.g. @ObservedObject(\Some.keyPath) var x) was removed. The same functionality can now be achieved using @Environment(\Some.keyPath) var x.

    Environment now by default observes its wrapped value if it is an ObservableObject just as ObservedObject did before. Previously, Environment would not subscribe to the ObservableObject and not cause evaluations of the Handler. Now, they do. The new initializer @Environment(\Some.keyPath, observed: false) var x can be used to go back to the old functionality.

    Refer to the Wiki for a detailed guide on how the new system around handling unsolicited events works.

  • Renew ExporterConfiguration and CLI arguments of the WebService @philippzagar (#292)

    Migration Guide

    Renewal of Exporter Configuration

    The ExporterConfiguration struct that defines the to be exported interfaces (eg. RESTInterfaceExporter, OpenAPIInterfaceExporter) was removed. The functionality to define and configure these exporters was overhauled. Prior to this release, exporters were defined like this:

    struct ExampleWebService: WebService {
        var configuration: Configuration {
            ExporterConfiguration()
                .exporter(RESTInterfaceExporter.self)
                .exporter(OpenAPIInterfaceExporter.self)
        }
    }
    

    Furthermore, the exporter itself could be configured via its own configuration struct (was only available for a very limited number of exporters and exporter configurations):

    var configuration: Configuration {
         ...
         OpenAPIConfiguration(
             outputFormat: .yaml,
             outputEndpoint: "/docs/openapi",
             swaggerUiEndpoint: "/ui/swagger",
             title: "The Game - Endangered Nature Edition, built with Apodini"
         )
     }
    

    Our new approach provides the developer with the ability to instantiate the exporters directly with a certain configuration. For example, in the case of a REST exporter, the encoder/decoder or the option to activate case insensitive routing can be specified together with a nested OpenAPI exporter that creates a description of the exported RESTful web services. The specified configuration of the "parent" exporter is automatically passed to the nested exporter to achieve some sort of functionality (eg. here the OpenAPI exporter could use the encoding strategy of the "parent" REST exporter). Note that the RESTInterfaceExporter is now expressed by REST, the OpenAPIInterfaceExporter by OpenAPI and so on.

    struct ExampleWebService: WebService {
       var configuration: Configuration {
           REST(encoder: JSONEncoder(), decoder: JSONDecoder(), caseInsensitiveRouting: true) {
                OpenAPI(
                    outputFormat: .yaml,
                    outputEndpoint: "/docs/openapi",
                    swaggerUiEndpoint: "/ui/swagger",
                    title: "The Game - Endangered Nature Edition, built with Apodini"
                )
           }
       }
    }
    

    This concept works similarly for the WebSocket interface exporter, GRPC interface exporter (with nested Protobuffer interface exporter) etc.

    Ability to parse CLI arguments in the WebService

    The second big change of the PR is the way how CLI arguments can be handed to the WebService and used eg. in its configuration. For the parsing of the CLI parameters, we rely on Apple's swift-argument-parser library (https://github.com/apple/swift-argument-parser). Please refer to the internal documentation of the swift-argument-parser for specific questions about what kind of arguments are available, what options are possible etc.

    A common use case of CLI arguments in Apodini was the configuration of HTTP bindings of the WebService, often used in eg. Docker containers. Prior to the renewal of this feature, Apodini automatically detects certain CLI parameters like 'hostname' or 'port' and then uses these values for the HTTPConfiguration. A possible configuration would look like this:

    struct ExampleWebService: WebService {
        var configuration: Configuration {
            HTTPConfiguration()
        }
    }
    

    A possible execution command would be the following: ./webservice hostname=0.0.0.0 port=8080

    This provided a durable but only very static configuration possibility to use CLI arguments since every parameter was required to be defined directly in the Apodini source code, not by the developer themself (a manual possibility would be to manually parse the CLI parameters via Vapor's ConsoleKit and CommandLine.arguments directly in Apodini´s WebService, an even more unpleasant solution).

    Now, with the full power of the swift-argument-parser in Apodini's WebService, CLI-parameters can be configured easily and dynamically by the developer. First, add the swift-argument-parser to your project's dependencies in the Package.swift file:

    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser", from: "0.3.0")
    ],
    

    The functionality shown above can be migrated to the following:

    import ArgumentParser
    
    struct ExampleWebService: WebService {
        @Option
        var hostname: String
    
        @Option
        var port: Int
    
        var configuration: Configuration {
            HTTPConfiguration(hostname: hostname, port: port)
        }
    }
    

    The parsing, type checking, validation etc. of the command is entirely handled by the swift-argument-parser, no manual interaction is needed.

    This even enables further possibilities that weren't possible before, like the conditional creation, depending on the passed arguments, of a description of the WebService via OpenAPI. A possible example could look like this:

    import ArgumentParser
    
    struct ExampleWebService: WebService {
        @Flag(help: "Generate an OpenAPI documentation of the WebService.")
        var generateOpenAPIDocs = false
      
        var configuration: Configuration {
            if(generateOpenAPIDocs) {
                REST { 
                    OpenAPI()
                }
            } else {
                REST()
            }
        }
    }
    

    As mentioned, the swift-argument-parser is a pretty powerful tool, please refer to its own documentation to learn more about the features (https://github.com/apple/swift-argument-parser).

  • Information for Requests and Responses @PSchmiedmayer (#301)

    Migration Guide Apodini now includes the functionality to easily access context information from incoming requests in `Handler`s and pass information to a `Response`. `Information` is mapped to the corresponding wire format for the different exporters, an REST Exporter e.g. maps `Information` instances to HTTP Header fields.

    The @Parameter option .http(.header) is removed and replaced with the .information property of the Connection of type Set<Information>.

    The following code:

    struct InformationHandler: Handler {
        @Parameter("Authorization",.http(.header))
        var authorization: String
    
        func handle() -> String {
            // Parse the basic authorization information from the Header String ...
            // Check if Paul is signed in ...
            return "Hello Paul 👋"
        }
    }
    

    Can be migrated to:

    struct _InformationHandler: Handler {
        @Apodini.Environment(\.connection) var connection: Connection
     
     
        func handle() -> String {
            guard let basicAuth = connection.information[Authorization.self]?.basic else {
                // Throw error ...
            }
         
            let username = basicAuth.username
            let password = basicAuth.password
         
            // Check if Paul is signed in ...
            return "Hello Paul 👋"
        }
    }
    

    The example shows how Information can be accessed in a type safe way using the Information types implemented in Apodini. Custom information can be accessed using a string based index, e.g. let rawValue = connection.information["MyInformation"]

    The following example shows how information can be passed to a Response instance in a Handler:

    struct InformationHandler: Handler {
        func handle() throws -> Response<Int> {
            .send(42, status: .ok, information: [
               .authorization(.basic(username: "PaulSchmiedmayer", password: "SuperSecretPassword")),
               .cookies(["test": "value"]),
               .etag("ABCD", isWeak: true),
               .expires(Date(timeIntervalSince1970: 1623843720)),
               .redirectTo(try XCTUnwrap(URL(string: "https://ase.in.tum.de/schmiedmayer"))),
               .custom(key: "Test", rawValue: "ATest")
           ])
        }
    }
    

    The example above details all implemented Information types. The .custom(key: String, rawValue: String) Information instance can be used to pass any information to a Response.

SemVer Minor

  • Binary Large Objects @PSchmiedmayer (#300)

    Example

    Binary Large Objects (Blob) allow you to return binary files from a Handler that are not encoded by exporters. This can be used to return files or return already encoded files or binary data such as HTML pages or any other content that should be displayed by a browser. A Blob can be initialized using a Data or ByteBuffer instance as well as a MimeType that indicates the file type expressed with the Blob.

    The following example shows a Handler that returns a Blob:

    struct BlobResponseHandler: Handler {
        func handle() -> Blob {
            let data: Data = // ...
            let mimeType: MimeType = .image(.gif)
            return Blob(data, type: mimeType)
        }
    }
    

    You can get an overview of all MimeTypes that are implemented in Apodini in MimeType.swift

Other Changes

  • Negative Compile tests @Supereg (#285)
  • Run Tests in release configuration @Supereg (#288)
  • Update dependencies @PSchmiedmayer (#291)
  • Update Experimental Async/Await Features to not Break Build on Xcode13 @theMomax (#295)
  • Update dependencies @PSchmiedmayer (#297)
  • Remove Relationship Code from SemanticModelBuilder @theMomax (#299)
  • Fix Propagation of Delegate Optionality @theMomax (#302)

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API