Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to optional Security Requirements and top-level inheritance #310

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/SwagGenKit/CodeFormatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ public class CodeFormatter {
var context: Context = [:]

context["name"] = securityRequirement.name
context["isRequired"] = securityRequirement.isRequired
context["scopes"] = securityRequirement.scopes
context["scope"] = securityRequirement.scopes.first

Expand Down
25 changes: 23 additions & 2 deletions Sources/Swagger/Operation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ public struct Operation {

extension Operation {

public init(path: String, method: Method, pathParameters: [PossibleReference<Parameter>], jsonDictionary: JSONDictionary) throws {
public init(path: String,
method: Method,
pathParameters: [PossibleReference<Parameter>],
jsonDictionary: JSONDictionary,
topLevelSecurityRequirements: [SecurityRequirement]?) throws {
json = jsonDictionary
self.path = path
self.method = method
Expand All @@ -57,7 +61,10 @@ extension Operation {

identifier = jsonDictionary.json(atKeyPath: "operationId")
tags = (jsonDictionary.json(atKeyPath: "tags")) ?? []
securityRequirements = jsonDictionary.json(atKeyPath: "security")
securityRequirements = type(of: self).getSecurityRequirements(
from: jsonDictionary,
topLevelSecurityRequirements: topLevelSecurityRequirements
)

let allResponses: [String: PossibleReference<Response>] = try jsonDictionary.json(atKeyPath: "responses")
var mappedResponses: [OperationResponse] = []
Expand Down Expand Up @@ -88,4 +95,18 @@ extension Operation {

deprecated = (jsonDictionary.json(atKeyPath: "deprecated")) ?? false
}

private static func getSecurityRequirements(
from jsonDictionary: JSONDictionary,
topLevelSecurityRequirements: [SecurityRequirement]?
) -> [SecurityRequirement]? {
// Even an empty list of operation security requirements
// can remove top-level security requirements.
guard let securityRequirements: [SecurityRequirement] =
jsonDictionary.json(atKeyPath: "security") else {
return topLevelSecurityRequirements
}

return securityRequirements.updatingRequiredFlag()
}
}
11 changes: 9 additions & 2 deletions Sources/Swagger/Path.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,22 @@ public struct Path {

extension Path: NamedMappable {

public init(name: String, jsonDictionary: JSONDictionary) throws {
public init(name: String,
jsonDictionary: JSONDictionary,
topLevelSecurityRequirements: [SecurityRequirement]?) throws {
path = name
parameters = (jsonDictionary.json(atKeyPath: "parameters")) ?? []

var mappedOperations: [Operation] = []
for (key, value) in jsonDictionary {
if let method = Operation.Method(rawValue: key) {
if let json = value as? [String: Any] {
let operation = try Operation(path: path, method: method, pathParameters: parameters, jsonDictionary: json)
let operation = try Operation(
path: path,
method: method,
pathParameters: parameters,
jsonDictionary: json,
topLevelSecurityRequirements: topLevelSecurityRequirements)
mappedOperations.append(operation)
}
}
Expand Down
29 changes: 27 additions & 2 deletions Sources/Swagger/Security/SecurityRequirement.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
import Foundation
import JSONUtilities

public struct SecurityRequirement: JSONObjectConvertible {
public struct SecurityRequirement: Equatable {
public let name: String
public let isRequired: Bool
public let scopes: [String]

public static let optionalMarker: SecurityRequirement = {
.init(name: "optional-marker", isRequired: false, scopes: [])
}()
}

extension SecurityRequirement: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
name = jsonDictionary.keys.first!
guard let firstKey = jsonDictionary.keys.first else {
self = .optionalMarker
return
}
name = firstKey
isRequired = true
scopes = try jsonDictionary.json(atKeyPath: .key(name))
}
}

extension Array where Element == SecurityRequirement {
func updatingRequiredFlag() -> [Element] {
// Check if there is an optional security requirement marker
guard (contains(where: { $0 == .optionalMarker })) else {
return self
}
// Remove the optional marker and set all security requirements as optional
return self
.drop { $0 == .optionalMarker }
.map { .init(name: $0.name, isRequired: false, scopes: $0.scopes) }
}
}
33 changes: 28 additions & 5 deletions Sources/Swagger/SwaggerSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ public enum TransferScheme: String {
}

public protocol NamedMappable {
init(name: String, jsonDictionary: JSONDictionary) throws
init(name: String,
jsonDictionary: JSONDictionary,
topLevelSecurityRequirements: [SecurityRequirement]?) throws
}

extension SwaggerSpec {
Expand Down Expand Up @@ -69,12 +71,20 @@ extension SwaggerSpec: JSONObjectConvertible {

public init(jsonDictionary: JSONDictionary) throws {

func decodeNamed<T: NamedMappable>(jsonDictionary: JSONDictionary, key: String) throws -> [T] {
func decodeNamed<T: NamedMappable>(
jsonDictionary: JSONDictionary,
key: String,
topLevelSecurityRequirements: [SecurityRequirement]?
) throws -> [T] {
var values: [T] = []
if let dictionary = jsonDictionary[key] as? [String: Any] {
for (key, value) in dictionary {
if let dictionary = value as? [String: Any] {
let value = try T(name: key, jsonDictionary: dictionary)
let value = try T(
name: key,
jsonDictionary: dictionary,
topLevelSecurityRequirements: topLevelSecurityRequirements
)
values.append(value)
}
}
Expand All @@ -91,13 +101,15 @@ extension SwaggerSpec: JSONObjectConvertible {

info = try jsonDictionary.json(atKeyPath: "info")
servers = jsonDictionary.json(atKeyPath: "servers") ?? []
securityRequirements = jsonDictionary.json(atKeyPath: "security")
securityRequirements = type(of: self).getSecurityRequirements(from: jsonDictionary)
if jsonDictionary["components"] != nil {
components = try jsonDictionary.json(atKeyPath: "components")
} else {
components = Components()
}
paths = try decodeNamed(jsonDictionary: jsonDictionary, key: "paths")
paths = try decodeNamed(jsonDictionary: jsonDictionary,
key: "paths",
topLevelSecurityRequirements: securityRequirements)
operations = paths.reduce([]) { $0 + $1.operations }
.sorted(by: { (lhs, rhs) -> Bool in
if lhs.path == rhs.path {
Expand All @@ -109,4 +121,15 @@ extension SwaggerSpec: JSONObjectConvertible {
let resolver = ComponentResolver(spec: self)
resolver.resolve()
}

private static func getSecurityRequirements(
from jsonDictionary: JSONDictionary
) -> [SecurityRequirement]? {
guard let securityRequirements: [SecurityRequirement] =
jsonDictionary.json(atKeyPath: "security") else {
return nil
}

return securityRequirements.updatingRequiredFlag()
}
}
4 changes: 3 additions & 1 deletion Specs/Petstore/generated/Swift/Sources/APIService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ extension APIService: CustomStringConvertible {

public struct SecurityRequirement {
public let type: String
public let isRequired: Bool
public let scopes: [String]

public init(type: String, scopes: [String]) {
public init(type: String, isRequired: Bool, scopes: [String]) {
self.type = type
self.isRequired = isRequired
self.scopes = scopes
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extension Petstore.Pets {
/** Create a pet */
public enum CreatePets {

public static let service = APIService<Response>(id: "createPets", tag: "pets", method: "POST", path: "/pets", hasBody: false, securityRequirements: [SecurityRequirement(type: "petstore_auth", scopes: ["write:pets", "read:pets"])])
public static let service = APIService<Response>(id: "createPets", tag: "pets", method: "POST", path: "/pets", hasBody: false, securityRequirements: [SecurityRequirement(type: "petstore_auth", isRequired: true, scopes: ["write:pets", "read:pets"])])

public final class Request: APIRequest<Response> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extension Petstore.Pets {
/** List all pets */
public enum ListPets {

public static let service = APIService<Response>(id: "listPets", tag: "pets", method: "GET", path: "/pets", hasBody: false, securityRequirements: [])
public static let service = APIService<Response>(id: "listPets", tag: "pets", method: "GET", path: "/pets", hasBody: false, securityRequirements: [SecurityRequirement(type: "petstore_auth", isRequired: false, scopes: ["read:pets"])])

public final class Request: APIRequest<Response> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extension Petstore.Pets {
/** Info for a specific pet */
public enum ShowPetById {

public static let service = APIService<Response>(id: "showPetById", tag: "pets", method: "GET", path: "/pets/{petId}", hasBody: false, securityRequirements: [])
public static let service = APIService<Response>(id: "showPetById", tag: "pets", method: "GET", path: "/pets/{petId}", hasBody: false, securityRequirements: [SecurityRequirement(type: "petstore_auth", isRequired: true, scopes: ["read:pets"])])

public final class Request: APIRequest<Response> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extension Petstore.Pets {
/** Updates a pet in the store with form data */
public enum UpdatePetWithForm {

public static let service = APIService<Response>(id: "updatePetWithForm", tag: "pets", method: "POST", path: "/pets/{petId}", hasBody: true, securityRequirements: [SecurityRequirement(type: "petstore_auth", scopes: ["write:pets", "read:pets"])])
public static let service = APIService<Response>(id: "updatePetWithForm", tag: "pets", method: "POST", path: "/pets/{petId}", hasBody: true, securityRequirements: [])

public final class Request: APIRequest<Response> {

Expand Down
13 changes: 9 additions & 4 deletions Specs/Petstore/spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ servers:
- x-name: Prod
description: Prod environment
url: http://petstore.swagger.io/v1
security:
- petstore_auth:
- read:pets
paths:
/pets:
get:
Expand Down Expand Up @@ -54,6 +57,10 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Error"
security:
- {}
- petstore_auth:
- read:pets
post:
summary: Create a pet
operationId: createPets
Expand Down Expand Up @@ -129,9 +136,7 @@ paths:
"405":
description: Invalid input
security:
- petstore_auth:
- write:pets
- read:pets
- []
components:
securitySchemes:
petstore_auth:
Expand Down Expand Up @@ -174,4 +179,4 @@ components:
type: integer
format: int32
message:
type: string
type: string
4 changes: 3 additions & 1 deletion Specs/PetstoreTest/generated/Swift/Sources/APIService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ extension APIService: CustomStringConvertible {

public struct SecurityRequirement {
public let type: String
public let isRequired: Bool
public let scopes: [String]

public init(type: String, scopes: [String]) {
public init(type: String, isRequired: Bool, scopes: [String]) {
self.type = type
self.isRequired = isRequired
self.scopes = scopes
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension PetstoreTest.Fake {
*/
public enum TestEndpointParameters {

public static let service = APIService<Response>(id: "testEndpointParameters", tag: "fake", method: "POST", path: "/fake", hasBody: true, isUpload: true, securityRequirements: [SecurityRequirement(type: "http_basic_test", scopes: [])])
public static let service = APIService<Response>(id: "testEndpointParameters", tag: "fake", method: "POST", path: "/fake", hasBody: true, isUpload: true, securityRequirements: [SecurityRequirement(type: "http_basic_test", isRequired: true, scopes: [])])

public final class Request: APIRequest<Response> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extension PetstoreTest.Pet {
/** Add a new pet to the store */
public enum AddPet {

public static let service = APIService<Response>(id: "addPet", tag: "pet", method: "POST", path: "/pet", hasBody: true, securityRequirements: [SecurityRequirement(type: "petstore_auth", scopes: ["write:pets", "read:pets"])])
public static let service = APIService<Response>(id: "addPet", tag: "pet", method: "POST", path: "/pet", hasBody: true, securityRequirements: [SecurityRequirement(type: "petstore_auth", isRequired: true, scopes: ["write:pets", "read:pets"])])

public final class Request: APIRequest<Response> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extension PetstoreTest.Pet {
/** Deletes a pet */
public enum DeletePet {

public static let service = APIService<Response>(id: "deletePet", tag: "pet", method: "DELETE", path: "/pet/{petId}", hasBody: false, securityRequirements: [SecurityRequirement(type: "petstore_auth", scopes: ["write:pets", "read:pets"])])
public static let service = APIService<Response>(id: "deletePet", tag: "pet", method: "DELETE", path: "/pet/{petId}", hasBody: false, securityRequirements: [SecurityRequirement(type: "petstore_auth", isRequired: true, scopes: ["write:pets", "read:pets"])])

public final class Request: APIRequest<Response> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension PetstoreTest.Pet {
*/
public enum FindPetsByStatus {

public static let service = APIService<Response>(id: "findPetsByStatus", tag: "pet", method: "GET", path: "/pet/findByStatus", hasBody: false, securityRequirements: [SecurityRequirement(type: "petstore_auth", scopes: ["write:pets", "read:pets"])])
public static let service = APIService<Response>(id: "findPetsByStatus", tag: "pet", method: "GET", path: "/pet/findByStatus", hasBody: false, securityRequirements: [SecurityRequirement(type: "petstore_auth", isRequired: true, scopes: ["write:pets", "read:pets"])])

/** Status values that need to be considered for filter */
public enum Status: String, Codable, Equatable, CaseIterable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension PetstoreTest.Pet {
*/
public enum FindPetsByTags {

public static let service = APIService<Response>(id: "findPetsByTags", tag: "pet", method: "GET", path: "/pet/findByTags", hasBody: false, securityRequirements: [SecurityRequirement(type: "petstore_auth", scopes: ["write:pets", "read:pets"])])
public static let service = APIService<Response>(id: "findPetsByTags", tag: "pet", method: "GET", path: "/pet/findByTags", hasBody: false, securityRequirements: [SecurityRequirement(type: "petstore_auth", isRequired: true, scopes: ["write:pets", "read:pets"])])

public final class Request: APIRequest<Response> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension PetstoreTest.Pet {
*/
public enum GetPetById {

public static let service = APIService<Response>(id: "getPetById", tag: "pet", method: "GET", path: "/pet/{petId}", hasBody: false, securityRequirements: [SecurityRequirement(type: "api_key", scopes: [])])
public static let service = APIService<Response>(id: "getPetById", tag: "pet", method: "GET", path: "/pet/{petId}", hasBody: false, securityRequirements: [SecurityRequirement(type: "api_key", isRequired: true, scopes: [])])

public final class Request: APIRequest<Response> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extension PetstoreTest.Pet {
/** Update an existing pet */
public enum UpdatePet {

public static let service = APIService<Response>(id: "updatePet", tag: "pet", method: "PUT", path: "/pet", hasBody: true, securityRequirements: [SecurityRequirement(type: "petstore_auth", scopes: ["write:pets", "read:pets"])])
public static let service = APIService<Response>(id: "updatePet", tag: "pet", method: "PUT", path: "/pet", hasBody: true, securityRequirements: [SecurityRequirement(type: "petstore_auth", isRequired: true, scopes: ["write:pets", "read:pets"])])

public final class Request: APIRequest<Response> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extension PetstoreTest.Pet {
/** Updates a pet in the store with form data */
public enum UpdatePetWithForm {

public static let service = APIService<Response>(id: "updatePetWithForm", tag: "pet", method: "POST", path: "/pet/{petId}", hasBody: true, securityRequirements: [SecurityRequirement(type: "petstore_auth", scopes: ["write:pets", "read:pets"])])
public static let service = APIService<Response>(id: "updatePetWithForm", tag: "pet", method: "POST", path: "/pet/{petId}", hasBody: true, securityRequirements: [SecurityRequirement(type: "petstore_auth", isRequired: true, scopes: ["write:pets", "read:pets"])])

public final class Request: APIRequest<Response> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extension PetstoreTest.Pet {
/** uploads an image */
public enum UploadFile {

public static let service = APIService<Response>(id: "uploadFile", tag: "pet", method: "POST", path: "/pet/{petId}/uploadImage", hasBody: true, isUpload: true, securityRequirements: [SecurityRequirement(type: "petstore_auth", scopes: ["write:pets", "read:pets"])])
public static let service = APIService<Response>(id: "uploadFile", tag: "pet", method: "POST", path: "/pet/{petId}/uploadImage", hasBody: true, isUpload: true, securityRequirements: [SecurityRequirement(type: "petstore_auth", isRequired: true, scopes: ["write:pets", "read:pets"])])

public final class Request: APIRequest<Response> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension PetstoreTest.Store {
*/
public enum GetInventory {

public static let service = APIService<Response>(id: "getInventory", tag: "store", method: "GET", path: "/store/inventory", hasBody: false, securityRequirements: [SecurityRequirement(type: "api_key", scopes: [])])
public static let service = APIService<Response>(id: "getInventory", tag: "store", method: "GET", path: "/store/inventory", hasBody: false, securityRequirements: [SecurityRequirement(type: "api_key", isRequired: true, scopes: [])])

public final class Request: APIRequest<Response> {

Expand Down
4 changes: 3 additions & 1 deletion Specs/Rocket/generated/Swift/Sources/APIService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ extension APIService: CustomStringConvertible {

public struct SecurityRequirement {
public let type: String
public let isRequired: Bool
public let scopes: [String]

public init(type: String, scopes: [String]) {
public init(type: String, isRequired: Bool, scopes: [String]) {
self.type = type
self.isRequired = isRequired
self.scopes = scopes
}
}
Expand Down
Loading