Skip to content

Commit

Permalink
Add example support. (#10)
Browse files Browse the repository at this point in the history
* Add example support.

- Adds `example` support to schema properties.
- Adds support for `x-example` on parameter definitions.
- Adds an `other(String)` type to `StringFormat`.

* Add spaces around content inside one line closure.

* Use fixture helper method to get jsonString.

* Updates based on PR feedback.

* More updates based on PR feedback.
  • Loading branch information
n8chur authored Jun 14, 2017
1 parent 27213b9 commit 8713b2d
Show file tree
Hide file tree
Showing 12 changed files with 468 additions and 257 deletions.
18 changes: 0 additions & 18 deletions Sources/Enums.swift
Original file line number Diff line number Diff line change
@@ -1,23 +1,5 @@
import Foundation

public enum StringFormat: String {

/// Base64 encoded characters
case byte

/// Any sequence of octets
case binary

/// As defined by full-date - RFC3339
case date

/// As defined by date-time - RFC3339
case dateTime = "date-time"

/// Used to hint UIs the input needs to be obscured.
case password
}

public enum IntegerFormat: String {

/// Signed 32 bits
Expand Down
7 changes: 6 additions & 1 deletion Sources/FixedParameterFields.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public struct FixedParameterFields {
/// Determines whether this parameter is mandatory. If the parameter is in "path", this property is
/// required and its value MUST be true. Its default value is false.
public let required: Bool

/// An example value for the parameter.
public let example: Any?
}

struct FixedParameterFieldsBuilder: Builder {
Expand All @@ -32,16 +35,18 @@ struct FixedParameterFieldsBuilder: Builder {
let location: ParameterLocation
let description: String?
let required: Bool
let example: Any?

init(map: Map) throws {
name = try map.value("name")
location = try map.value("in")
description = try? map.value("description")
required = (try? map.value("required")) ?? false
example = try? map.value("x-example")
}

func build(_ swagger: SwaggerBuilder) throws -> FixedParameterFields {
return FixedParameterFields(name: self.name, location: self.location, description: self.description,
required: self.required)
required: self.required, example: self.example)
}
}
7 changes: 6 additions & 1 deletion Sources/Metadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public struct Metadata {

/// Whether or not the schema can be nil. Corresponds to `x-nullable`.
public let nullable: Bool

/// An example value for the schema.
public let example: Any?
}

struct MetadataBuilder: Builder {
Expand All @@ -30,6 +33,7 @@ struct MetadataBuilder: Builder {
let defaultValue: Any?
let enumeratedValues: [Any?]?
let nullable: Bool
let example: Any?

init(map: Map) throws {
if let typeString: String = try? map.value("type"), let mappedType = DataType(rawValue: typeString) {
Expand All @@ -53,11 +57,12 @@ struct MetadataBuilder: Builder {
defaultValue = try? map.value("default")
enumeratedValues = try? map.value("enum")
nullable = (try? map.value("x-nullable")) ?? false
example = try? map.value("example")
}

func build(_ swagger: SwaggerBuilder) throws -> Metadata {
return Metadata(type: self.type, title: self.title, description: self.description,
defaultValue: self.defaultValue, enumeratedValues: self.enumeratedValues,
nullable: self.nullable)
nullable: self.nullable, example: self.example)
}
}
87 changes: 87 additions & 0 deletions Sources/StringFormat.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import Foundation

public enum StringFormat: RawRepresentable {

public typealias RawValue = String

/// Base64 encoded characters.
case byte

/// Any sequence of octets.
case binary

/// "full-date" representation, see RFC 3339, section 5.6.
case date

/// "date-time" representation, see RFC 3339, section 5.6.
case dateTime

/// Internet email address, see RFC 5322, section 3.4.1.
case email

/// Internet host name, see RFC 1034, section 3.1.
case hostname

/// IPv4 address, according to dotted-quad ABNF syntax as defined in RFC 2673, section 3.2.
case ipv4

/// IPv6 address, as defined in RFC 2373, section 2.2.
case ipv6

/// Used to hint UIs the input needs to be obscured.
case password

/// A custom format
case other(String)

/// A universal resource identifier (URI), according to RFC3986.
case uri

public init(rawValue: String) {
self = RawStringFormat(rawValue: rawValue)?.stringFormat ?? .other(rawValue)
}

public var rawValue: String {
switch self {
case .byte: return RawStringFormat.byte.rawValue
case .binary: return RawStringFormat.binary.rawValue
case .date: return RawStringFormat.date.rawValue
case .dateTime: return RawStringFormat.dateTime.rawValue
case .email: return RawStringFormat.email.rawValue
case .hostname: return RawStringFormat.hostname.rawValue
case .ipv4: return RawStringFormat.ipv4.rawValue
case .ipv6: return RawStringFormat.ipv6.rawValue
case .password: return RawStringFormat.password.rawValue
case .uri: return RawStringFormat.uri.rawValue
case .other(let other): return other
}
}

private enum RawStringFormat: String {
case byte
case binary
case date
case dateTime = "date-time"
case email
case hostname
case ipv4
case ipv6
case password
case uri

var stringFormat: StringFormat {
switch self {
case .byte: return .byte
case .binary: return .binary
case .date: return.date
case .dateTime: return .dateTime
case .email: return .email
case .hostname: return .hostname
case .ipv4: return .ipv4
case .ipv6: return .ipv6
case .password: return .password
case .uri: return .uri
}
}
}
}
27 changes: 27 additions & 0 deletions Tests/SwaggerParserTests/AbstractTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import XCTest
@testable import SwaggerParser

class AbstractTests: XCTestCase {
func testAbstract() throws {
let jsonString = try fixture(named: "test_abstract.json")
let swagger = try Swagger(JSONString: jsonString)

guard
let objectDefinition = swagger.definitions.first(where: { $0.name == "Abstract" }),
case .object(let objectSchema) = objectDefinition.structure else
{
return XCTFail("Abstract is not an object schema.")
}

XCTAssertTrue(objectSchema.abstract)

guard
let allOfDefinition = swagger.definitions.first(where: { $0.name == "AbstractAllOf" }),
case .allOf(let allOfSchema) = allOfDefinition.structure else
{
return XCTFail("AbstractAllOf is not an allOf schema.")
}

XCTAssertTrue(allOfSchema.abstract)
}
}
29 changes: 29 additions & 0 deletions Tests/SwaggerParserTests/AllOfTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import XCTest
@testable import SwaggerParser

class AllOfTests: XCTestCase {
func testAllOfSupport() throws {
let jsonString = try fixture(named: "test_all_of.json")
let swagger = try Swagger(JSONString: jsonString)

guard
let baseDefinition = swagger.definitions.first(where: { $0.name == "TestAllOfBase" }),
case .object(let baseSchema) = baseDefinition.structure else
{
return XCTFail("TestAllOfBase is not an object schema.")
}

validate(testAllOfBaseSchema: baseSchema)
try validate(that: swagger.definitions, containsTestAllOfChild: "TestAllOfFoo", withPropertyNames: ["foo"])
try validate(that: swagger.definitions, containsTestAllOfChild: "TestAllOfBar", withPropertyNames: ["bar"])

guard
let fooDefinition = swagger.definitions.first(where: {$0.name == "TestAllOfFoo"}),
case .allOf(let fooSchema) = fooDefinition.structure else
{
return XCTFail("TestAllOfFoo is not an object schema.")
}

XCTAssertEqual(fooSchema.metadata.description, "This is an AllOf description.")
}
}
11 changes: 11 additions & 0 deletions Tests/SwaggerParserTests/BasicTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import XCTest
@testable import SwaggerParser

class BasicTests: XCTestCase {
func testInitialization() throws {
let jsonString = try fixture(named: "uber.json")
let swagger = try Swagger(JSONString: jsonString)

XCTAssertEqual(swagger.host?.absoluteString, "api.uber.com")
}
}
64 changes: 64 additions & 0 deletions Tests/SwaggerParserTests/ExampleTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import XCTest
@testable import SwaggerParser

class ExampleTests: XCTestCase {
func testExamples() throws {
let jsonString = try fixture(named: "test_examples.json")
let swagger = try Swagger(JSONString: jsonString)

guard
let definition = swagger.definitions.first(where: { $0.name == "Example" }),
case .object(let schema) = definition.structure else
{
return XCTFail("Example is not an object schema.")
}

guard
let aStringProperty = schema.properties["a-string"],
case .string(let aStringMetadata, let aStringOptionalFormat) = aStringProperty else
{
return XCTFail("Example has no string property 'a-string'.")
}

guard
let anOptionalStringFormat = aStringOptionalFormat,
case .other(let aStringFormatValue) = anOptionalStringFormat,
aStringFormatValue == "custom" else
{
return XCTFail("Example's 'a-string' does not have `custom` format type.")
}

XCTAssertEqual(aStringMetadata.example as? String, "Example String")

guard
let anIntegerProperty = schema.properties["an-integer"],
case .integer(let anIntegerMetadata, _) = anIntegerProperty else
{
return XCTFail("Example has no string property 'an-integer'.")
}

XCTAssertEqual(anIntegerMetadata.example as? Int64, 987)

guard
let exampleIDPath = swagger.paths["/test-examples/{exampleId}"],
let exampleIDOperation = exampleIDPath.operations[.post] else
{
return XCTFail("POST /test-examples/{exampleId} not found.")
}

XCTAssertEqual(exampleIDOperation.parameters.count, 1)

guard
let exampleIDOperationParameter = exampleIDOperation.parameters.first,
case .a(let exampleIDParameter) = exampleIDOperationParameter else
{
return XCTFail("Example ID parameter should not be a structure.")
}

guard case .other(let exampleIDFixed, _) = exampleIDParameter else {
XCTFail("Example ID parameter is not .other."); return
}

XCTAssertEqual(exampleIDFixed.example as? String, "E_123")
}
}
58 changes: 58 additions & 0 deletions Tests/SwaggerParserTests/Fixtures/test_examples.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"swagger": "2.0",
"info": {
"title": "Test allOf",
"description": "A test API to validate parsing of the `allOf` feature.",
"version": "1.0.0"
},
"host": "api.test.com",
"schemes": [
"https"
],
"basePath": "/v1",
"produces": [
"application/json"
],
"paths": {
"/test-examples/{exampleId}": {
"post": {
"summary": "Test allOf",
"description": "This api is solely defined to test `example` and `x-example` parsing.",
"parameters": [{
"name": "exampleId",
"in": "path",
"description": "The ID of the example",
"required": true,
"type": "string",
"x-example": "E_123"
}],
"responses": {
"201": {
"description": "The example response",
"schema": {
"$ref": "#/definitions/Example"
}
}
}
}
}
},
"definitions": {
"Example": {
"properties": {
"a-string": {
"type": "string",
"format": "custom",
"description": "A key/value present on the TestAllOfBase object",
"example": "Example String"
},
"an-integer": {
"type": "integer",
"format": "int64",
"description": "A test integer",
"example": 987
}
}
}
}
}
Loading

0 comments on commit 8713b2d

Please sign in to comment.