Skip to content

Common Swift Target Features

The following features are supported by all Swift code generation targets.

Generated Types

Scalars

RAML built-in scalar types are mapped according to the following table.

RAML Type Swift Type
any Any
boolean Boolean
number / integer Numerics
string String
date-only Date
time-only Date
datetime-only Date
datetime Date
file Data
nil Optional.none (aka nil)

Numerics

RAML number and integer types are mapped to one of Swift's numeric types based on the format facet.

Format Swift Type
int Int
int8 Int8
int16 Int16
int32 Int32
int64 Int64
long Int64
float Float
double Double

Objects

For each RAML type that is an object or where the root of the inheritance tree is an object and contains any defined properties, a Swift class is generated.

Generated classes are immutable and fluent modifier functions are generated for each property defined on the type.

Each generated class implements Codable and CustomDebugStringConvertible. It's left up to the users to implement other Swift standard protocols like Equatable and/or Hashable.

Example Class Generation

RAML Type Definition

%RAML 1.0
title: Test API

types:

  Item:
    type: object
    properties:
      name: String
      value: integer

Generated Swift Class

public class Item : Codable, CustomDebugStringConvertible {

  public let name: String
  public let value: Int
  public var debugDescription: String {
    return DescriptionBuilder(Test.self)
        .add(name, named: "name")
        .add(value, named: "value")
        .build()
  }

  public init(name: String, value: Int) {
    self.name = name
    self.value = value
  }

  public required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.name = try container.decode(String.self, forKey: .name)
    self.value = try container.decode(Int.self, forKey: .value)
  }

  public func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(self.name, forKey: .name)
    try container.encode(self.value, forKey: .value)
  }

  public func withName(name: String) -> Test {
    return Test(name: name, value: value)
  }

  public func withValue(value: Int) -> Test {
    return Test(name: name, value: value)
  }

  fileprivate enum CodingKeys : String, CodingKey {

    case name = "name"
    case value = "value"

  }

}

Inherited Objects

RAML types that inherit from a single parent type are generated as a class hierarchy.

As long as each type in the hierarchy only inherits from a single parent type the hierarch can be as complex as the author requires.

When the root of the RAML hierarchy has the discriminator facet set, the generated class hierarchy will support polymorphic decoding using a nested reference object named AnyRef.

Example Class Hierarchy Generation

RAML Type Definition

%RAML 1.0
title: Test API
types:

  Device:
    type: object
    discriminator: type
    properties:
      type: string
      name: string

  Phone:
    type: Device
    discriminatorValue: phone
    properties:
      hasGPS: boolean

  Tablet:
    type: Device
    discriminatorValue: tablet
    properties:
      isKeyboardAttached: boolean

Generated Swift Classes

public class Device : Codable {

  public var type: String {
    fatalError("abstract type method")
  }
  public let name: String
  public var debugDescription: String {
    return DescriptionBuilder(Device.self)
        .build()
  }

  public init(name: String) {
    self.name = name
  }

  public required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.name = try container.decode(String.self, forKey: .name)
  }

  public func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(self.name, forKey: .name)
  }

  public enum AnyRef : Codable, CustomDebugStringConvertible {

    case phone(Phone)
    case tablet(Tablet)

    public var value: Device {
      switch self {
      case .phone(let value): return value
      case .tablet(let value): return value
      }
    }
    public var debugDescription: String {
      switch self {
      case .phone(let value): return value.debugDescription
      case .tablet(let value): return value.debugDescription
      }
    }

    public init(value: Device) {
      switch value {
      case let value as Phone: self = .phone(value)
      case let value as Tablet: self = .tablet(value)
      default: fatalError("Invalid value type")
      }
    }

    public init(from decoder: Decoder) throws {
      let container = try decoder.container(keyedBy: CodingKeys.self)
      let type = try container.decode(String.self, forKey: CodingKeys.type)
      switch type {
      case "phone": self = .phone(try Phone(from: decoder))
      case "tablet": self = .tablet(try Tablet(from: decoder))
      default:
          throw DecodingError.dataCorruptedError(
            forKey: CodingKeys.type,
            in: container,
            debugDescription: "unsupported value for \"type\""
          )
      }
    }

    public func encode(to encoder: Encoder) throws {
      var container = encoder.container(keyedBy: CodingKeys.self)
      switch self {
      case .phone(let value):
          try container.encode("phone", forKey: .type)
          try value.encode(to: encoder)
      case .tablet(let value):
          try container.encode("tablet", forKey: .type)
          try value.encode(to: encoder)
      }
    }

  }

  fileprivate enum CodingKeys : String, CodingKey {

    case type = "type"

  }

}


public class Phone : Parent {

  public override var type: String {
    return "phone"
  }
  public let hasGPS: Boolean
  public override var debugDescription: String {
    return DescriptionBuilder(Phone.self)
        .add(type, named: "type")
        .add(name, named: "name")
        .add(hasGPS, named: "hasGPS")
        .build()
  }

  public init(name: String, hasGPS: Boolean) {
    self.hasGPS = hasGPS
    super.init(name: name)
  }

  public required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.hasGPS = try container.decode(Boolean.self, forKey: .hasGPS)
    try super.init(from: decoder)
  }

  public override func encode(to encoder: Encoder) throws {
    try super.encode(to: encoder)
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(self.hasGPS, forKey: .hasGPS)
  }

  public func withName(name: String) -> Phone {
    return Phone(name: name, isKeyboardAttached: isKeyboardAttached)
  }

  public func withHasGPS(hasGPS: Boolean) -> Phone {
    return Phone(name: name, hasGPS: hasGPS)
  }

  fileprivate enum CodingKeys : String, CodingKey {

    case hasGPS = "hasGPS"

  }

}

public class Tablet : Parent {

  public override var type: String {
    return "tablet"
  }
  public let isKeyboardAttached: Boolean
  public override var debugDescription: String {
    return DescriptionBuilder(Tablet.self)
        .add(type, named: "type")
        .add(name, named: "name")
        .add(isKeyboardAttached, named: "isKeyboardAttached")
        .build()
  }

  public init(name: String, isKeyboardAttached: Boolean) {
    self.isKeyboardAttached = isKeyboardAttached
    super.init(name: name)
  }

  public required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.isKeyboardAttached = try container.decode(Boolean.self, forKey: .isKeyboardAttached)
    try super.init(from: decoder)
  }

  public override func encode(to encoder: Encoder) throws {
    try super.encode(to: encoder)
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(self.isKeyboardAttached, forKey: .isKeyboardAttached)
  }

  public func withName(name: String) -> Tablet {
    return Tablet(name: name, isKeyboardAttached: isKeyboardAttached)
  }

  public func withIsKeyboardAttached(isKeyboardAttached: Boolean) -> Tablet {
    return Tablet(name: name, isKeyboardAttached: isKeyboardAttached)
  }

  fileprivate enum CodingKeys : String, CodingKey {

    case isKeyboardAttached = "isKeyboardAttached"

  }

}

Use Generated AnyRef to Encode/Decode

let decodedDevice = JsonDecoder().decode(Device.AnyRef).value
let encodedDevice = JsonEncoder().encode(Device.AnyRef(decodedDevice))

Simple Objects

RAML types that are "simple" objects (where no properties facet is defined) are mapped to Swift dictionaries (i.e. [String: Any]). They are generated inline and no type aliases are generated.

Example Simple Object Generation

RAML Type Definition

%RAML 1.0
title: Test API
types:

  Container:
    map: object

Generated Swift Class (simplified)

class Container : Codable {

  let map: [String: Any]

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

}

Pattern Objects

RAML types that only contain pattern properties (i.e. property names defined by regular expressions) are mapped to Swift dictionaries with the key as a string and the value as the type specified in the pattern (e.g. [String: Int]). They are generated inline and no type aliases are generated.

Example Pattern Object Generation

RAML Type Definition

%RAML 1.0
title: Test API
types:

  MapOfInts:
    type: object
      //: integer

  Container:
    map: MapOfInts

Generated Swift Class (simplified)

class Container : Codable {

  let map: [String: Int]

  init(items: [String: Int]) {
    self.map = map
  }

}

Arrays

RAML array types are mapped to Swift's Array type. They are generated inline and no type aliases are generated.

Example Array Generation

RAML Type Definition

%RAML 1.0
title: Test API
types:

  Item:
    type: string

  Items:
    type: array
    items: Item

  Container:
    items: Items

Generated Swift Class (simplified)

class Container : Codable {

  let items: [String]

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

}

Unions

RAML union types are mapped to the "nearest common ancestor" of all individual types in the union, if one exists, in all other cases the union is mapped to Swit's Any type.

Example Union Generation (Common Aancestor)

RAML Type Definition

%RAML 1.0
title: Test API
types:

  Device:
    type: object
    discriminator: type
    properties:
      type: string
      name: string

  Phone:
    type: Device
    discriminatorValue: phone
    properties:
      hasGPS: boolean

  Tablet:
    type: Device
    discriminatorValue: tablet
    properties:
      isKeyboardAttached: boolean

  UnionOfAllDevices:
    type: (Phone | Tablet)

  Container:
    type: object
    properties:
      device: UnionOfAllDevices

Generated Swift Class (simplified)

public class Container {

  let device: Device

  init(device: Device) {
    self.device = device
  }

}

Generated Problem Types

For each problem defined & referenced using Sunday's Sunday's problem annotations, a Swift Error class is generated. Generating exceptions allows servers to throw a specific problem and clients to catch specific problems.

Custom properties can be defined for problem types and they are added as simple Swift properties on the generated error class.

Example Problem Generation

RAML Type Definition

%RAML 1.0
title: Test API
uses:
    sunday: https://outfoxx.github.io/sunday-generator/sunday.raml
types:

(sunday.problemTypes):
  invalid_id:
    status: 400
    title: Invalid Id
    detail: The id contains one or more invalid characters
    custom:
      offendingId: string

Generated Problem Error Class

public class InvalidIdProblem : Problem {

  public static let type: URL = URL(string: "http://example.com/invalid_id")!
  public let offendingId: String
  var description: String {
    return DescriptionBuilder(Self.self)
        .add(type, named: "type")
        .add(title, named: "title")
        .add(status, named: "status")
        .add(detail, named: "detail")
        .add(instance, named: "instance")
        .add(offendingId, named: "offendingId")
        .build()
  }

  init(offendingId: String, instance: URL? = nil) {
    self.offendingId = offendingId
    super.init(type: Self.type, title: "Invalid Id", status: 400,
        detail: "The id contains one or more invalid characters.", instance: instance,
        parameters: nil)
  }

  public required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.offendingId = try container.decode(String.self, forKey: CodingKeys.offendingId)
    try super.init(from: decoder)
  }

  public override func encode(to encoder: Encoder) throws {
    try super.encode(to: encoder)
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(self.offendingId, forKey: CodingKeys.offendingId)
  }

  fileprivate enum CodingKeys : String, CodingKey {

    case offendingId = "offending_id"

  }

}

Generator Options

In addition to the options supported by all code generations targets, this target also supports the following options:

Type Generation Options

Enable/Disable Swift type generation options.

Supported Options
Add Generated Annotation
Enables or Disables adding generation annotations to generated types
CLI Option Gradle Plugin Properties Type Default
-disable add-generation-annotation boolean enabled