Skip to content

dankinsoid/SwiftOpenAPI

main
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
 
 

SwiftOpenAPI

CI Status Version License Platform

Description

SwiftOpenAPI is a Swift library which can generate output compatible with OpenAPI version 3.1.0. You can describe your API using OpenAPIObject type.
The main accent in the library is on simplifying the syntax: the active use of literals (array, dictionary, string etc) and static methods greatly simplifies writing and reading OpenAPI docs in Swift.

Short example

try OpenAPIObject(
    openapi: "3.0.1",
    info: InfoObject(
        title: "Example API",
        version: "0.1.0"
    ),
    servers: [
        "https://example-server.com",
        "https://example-server-test.com"
    ],
    paths: [
        "services": .get(
            summary: "Get services",
            OperationObject(description: "Get services")
        ),
        "login": .post(
            OperationObject(
                description: "login",
                requestBody: .ref(components: \.requestBodies, "LoginRequest"),
                responses: [
                    .ok: .ref(components: \.responses, "LoginResponse"),
                    .unauthorized: .ref(components: \.responses, "ErrorResponse")
                ]
            )
        ),
        "/services/{serviceID}": [
            .get: OperationObject(description: "Get service"),
            .delete: OperationObject(description: "Delete service")
        ],
        "/services": .ref(components: \.pathItems, "T")
    ],
    components: ComponentsObject(
        schemas: [
            "LoginBody": [
                "username": .string,
                "password": .string
            ],
            "LoginResponse": .value(.encode(LoginResponse.example))
        ],
        examples: [
            "LoginBody": [
                "username": "SomeUser",
                "password": "12345678"
            ],
            "LoginResponse": .value(
            	ExampleObject(value: .encode(LoginResponse.example))
            )
        ],
        requestBodies: [
            "LoginRequest": .value(
                RequestBodyObject(
                    content: [
                        .application(.json): MediaTypeObject(
                            schema: .ref(components: \.schemas, "LoginBody")
                        )
                    ],
                    required: nil
                )
            )
        ]
    )
)

Pets store example

PetsSwagger.swift demonstrates syntaxis well

Creating schemas and parameters for Codable types

There is a possibility to create SchemeObject, [ParameterObject], AnyValue and [String: HeaderObject] instances from Codable types. It's possible to use SchemeObject.decode/encode, [ParameterObject].decode/encode, [String: HeaderObject].decode/encode and AnyValue.encode methods for it.

let loginBodySchemeFromType: SchemeObject = try .decode(LoginBody.self)
let loginBodySchemeFromInstance: SchemeObject = try .encode(LoginBody.example)
let loginBodyExample = try ExampleObject(value: .encode(LoginBody.example))

You can customize the encoding/decoding result by implementing OpenAPIDescriptable and OpenAPIType protocols.

  1. OpenAPIDescriptable protocol allows you to provide a custom description for the type and its properties.
struct LoginBody: Codable, OpenAPIDescriptable {
    
    static var openAPIDescription: OpenAPIDescriptionType? {
        OpenAPIDescription<CodingKeys>("Login body")
            .add(for: .username, "Username")
            .add(for: .password, "Password")
    }
}
  1. OpenAPIType protocol allows you to provide a custom schema for the type.
struct Color: Codable, OpenAPIType {
    
    static var openAPISchema: SchemaObject {
        .string(format: "hex", description: "Color in hex format")
    }
}

Specification extensions

While the OpenAPI Specification tries to accommodate most use cases, additional data can be added to extend the specification at certain points.\

var api = OpenAPIObject(...)
api.specificationExtensions = ["x-some-extension": "some value"]
// or
api.specificationExtensions = try? SpecificationExtensions(from: someEncodable)

It was a bit tricky challenge to implement additional dynamic properties for any codable struct. The solution is to use SpecificationExtendable protocol in combination with WithSpecExtensions property wrapper. There is two ways to decode/encode SpecificationExtendable types with additional properties:

  1. Use SpecificationExtendable.json, SpecificationExtendable.Type.from(json:) methods.
let schema = try SchemaObject.from(json: jsonData)
let jsonData = try schema.json()
  1. If you cannot use custom decoding methods, you can use WithSpecExtensions wrapper.
let api = try WithSpecExtensions(wrappedValue: OpenAPIObject(...))
let jsonData = try JSONEncoder().encode(api)

TODO

  • URI type instead of String
  • refactor method on OpenAPIObject (?)
  • Extend RuntimeExpression type
  • DataEncodingFormat

Installation

  1. Swift Package Manager

Create a Package.swift file.

// swift-tools-version:5.7
import PackageDescription

let package = Package(
  name: "SomeProject",
  dependencies: [
    .package(url: "https://github.com/dankinsoid/SwiftOpenAPI.git", from: "2.15.5")
  ],
  targets: [
    .target(name: "SomeProject", dependencies: ["SwiftOpenAPI"])
  ]
)
$ swift build
  1. CocoaPods

Add the following line to your Podfile:

pod 'SwiftOpenAPI'

and run pod update from the podfile directory first.

Related projects

Author

dankinsoid, voidilov@gmail.com

License

SwiftOpenAPI is available under the MIT license. See the LICENSE file for more info.