Skip to content

Commit

Permalink
Merge pull request #97 from cipi1965/codable-implementation
Browse files Browse the repository at this point in the history
Codable implementation
  • Loading branch information
cipi1965 authored Mar 22, 2020
2 parents a1ecd3e + 0bd6063 commit 7b1a0ee
Show file tree
Hide file tree
Showing 82 changed files with 2,720 additions and 8,215 deletions.
94 changes: 47 additions & 47 deletions API/TelegramAPIDefinition.yml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import Foundation
import Rapier

private struct Context {
let directory: String

var outTypes: String = ""
var outMethods: String = ""

init(directory: String) {
self.directory = directory
}
}

class TelegramBotSDKCodableGenerator: CodeGenerator {
required init(directory: String) {
self.context = Context(directory: directory)
}

func start() throws {

}

func beforeGeneratingTypes() throws {
let header = """
// This file is automatically generated by Rapier
import Foundation
"""
context.outTypes.append(header)
}

func generateType(name: String, info: TypeInfo) throws {
context.outTypes.append("""
public class \(name): Codable {\n
""")
var allInitParams: [String] = []
info.fields.forEach { fieldInfo in
let getterName = makeGetterName(typeName: name, fieldName: fieldInfo.name, fieldType: fieldInfo.type)
if fieldInfo.type == "True" {
allInitParams.append(#""\#(fieldInfo.name)" = true"#)
} else {
if let field = buildFieldTemplate(fieldName: getterName, fieldInfo: fieldInfo) {
context.outTypes.append(field)
}
}

}
var initParamsString = allInitParams.joined(separator: ", ")
if initParamsString.isEmpty {
initParamsString = "[:]"
}

context.outTypes.append(generateTypeInit(typeName: name, fields: info.fields))

context.outTypes.append("""
}\n\n\n
""")
}

func generateTypeInit(typeName: String, fields: [FieldInfo]) -> String {
let initParameters = fields.map { (fieldInfo) -> String in
let name = makeGetterName(typeName: typeName, fieldName: fieldInfo.name, fieldType: fieldInfo.type)
var type = fieldInfo.type

if fieldInfo.isArray {
type = "[\(type)]"
}

if fieldInfo.isArrayOfArray {
type = "[\(type)]"
}

if fieldInfo.isOptional {
type = "\(type)? = nil"
}

return "\(name.camelized()): \(type)"
}.joined(separator: ", ")

var initString = ""
initString.append("""
public init(\(initParameters)) {\n
""")

for field in fields {
let name = field.name.camelized()
initString.append(" self.\(name) = \(name)\n")
}

initString.append(" }\n\n")

return initString
}

func afterGeneratingTypes() throws {
}

func beforeGeneratingMethods() throws {
let methodsHeader = """
// This file is automatically generated by Rapier
import Foundation
import Dispatch
public extension TelegramBot {
"""

context.outMethods.append(methodsHeader)
}

func generateMethod(name: String, info: MethodInfo) throws {

let parameters = info.parameters

let fields: [String] = parameters.map { fieldInfo in
var result = "\(fieldInfo.name.camelized()): \(buildSwiftType(fieldInfo: fieldInfo))"
if fieldInfo.isOptional {
result.append(" = nil")
}

return result
}

let arrayFields: [String] = parameters.map { fieldInfo in
return #""\#(fieldInfo.name)": \#(fieldInfo.name.camelized())"#
}

var fieldsString = fields.joined(separator: ",\n ")
var arrayFieldsString = arrayFields.joined(separator: ",\n")

let completionName = (name.first?.uppercased() ?? "") + name.dropFirst() + "Completion"
let resultSwiftType = buildSwiftType(fieldInfo: info.result)

if !fieldsString.isEmpty {
fieldsString.append(",")
}

if arrayFieldsString.isEmpty {
arrayFieldsString = ":"
}

let method = """
typealias \(completionName) = (_ result: \(resultSwiftType), _ error: DataTaskError?) -> ()
@discardableResult
func \(name)Sync(
\(fieldsString)
_ parameters: [String: Encodable?] = [:]) -> \(resultSwiftType) {
return requestSync("\(name)", defaultParameters["\(name)"], parameters, [
\(arrayFieldsString)])
}
func \(name)Async(
\(fieldsString)
_ parameters: [String: Encodable?] = [:],
queue: DispatchQueue = .main,
completion: \(completionName)? = nil) {
return requestAsync("\(name)", defaultParameters["\(name)"], parameters, [
\(arrayFieldsString)],
queue: queue, completion: completion)
}
"""

context.outMethods.append(method)
}

func afterGeneratingMethods() throws {
context.outMethods.append("\n}\n")
}

func finish() throws {
try saveTypes()
try saveMethods()
}

private func saveTypes() throws {
let dir = URL(fileURLWithPath: context.directory, isDirectory: true)
let file = dir.appendingPathComponent("Types.swift", isDirectory: false)
try context.outTypes.write(to: file, atomically: true, encoding: .utf8)
}

private func saveMethods() throws {
let dir = URL(fileURLWithPath: context.directory, isDirectory: true)
let file = dir.appendingPathComponent("Methods.swift", isDirectory: false)
try context.outMethods.write(to: file, atomically: true, encoding: .utf8)
}

private func buildSwiftType(fieldInfo: FieldInfo) -> String {
var type: String
if (fieldInfo.isArray) {
type = "[\(fieldInfo.type)]"
} else {
type = fieldInfo.type
}
if (fieldInfo.isOptional) {
type.append("?")
}
return type
}

private func buildFieldTemplate(fieldName: String, fieldInfo: FieldInfo) -> String? {
let type = fieldInfo.type
let isOptional = fieldInfo.isOptional
let name = fieldName.camelized()

if fieldInfo.isArrayOfArray {
return """
public var \(name): [[\(type)]]\(isOptional ? "?" : "") = [[]]\n
"""
} else if fieldInfo.isArray {
return """
public var \(name): [\(type)]\(isOptional ? "?" : "") = []\n
"""
} else {
return """
public var \(name): \(type)\(isOptional ? "?" : "")\n
"""
}
}

private var context: Context
}

extension TelegramBotSDKCodableGenerator {
private func makeGetterName(typeName: String, fieldName: String, fieldType: String) -> String {
return fieldName
}
}

extension String {
fileprivate func camelized() -> String {
let components = self.components(separatedBy: "_")

let firstLowercasedWord = components.first?.lowercased()

let remainingWords = components.dropFirst().map {
$0.prefix(1).uppercased() + $0.dropFirst().lowercased()
}
return ([firstLowercasedWord].compactMap{ $0 } + remainingWords).joined()
}
}
2 changes: 1 addition & 1 deletion Rapier/Sources/RapierCLI/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ private func main() throws -> Int32 {

try rapier.parseYml()

let generator = TelegramBotSDKGenerator(directory: CommandLine.arguments[2])
let generator = TelegramBotSDKCodableGenerator(directory: CommandLine.arguments[2])
try rapier.generate(generator: generator)

return 0
Expand Down
2 changes: 1 addition & 1 deletion Sources/TelegramBotSDK/BotName.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//
// This source file is part of the Telegram Bot SDK for Swift (unofficial).
//
// Copyright (c) 2015 - 2016 Andrey Fidrya and the project authors
// Copyright (c) 2015 - 2020 Andrey Fidrya and the project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See LICENSE.txt for license information
Expand Down
20 changes: 12 additions & 8 deletions Sources/TelegramBotSDK/DataTaskError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//
// This source file is part of the Telegram Bot SDK for Swift (unofficial).
//
// Copyright (c) 2015 - 2016 Andrey Fidrya and the project authors
// Copyright (c) 2015 - 2020 Andrey Fidrya and the project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See LICENSE.txt for license information
Expand All @@ -29,14 +29,16 @@ public enum DataTaskError {
case libcurlAbortedByCallback

/// Status Code is not 200 (OK)
case invalidStatusCode(statusCode: Int, telegramResponse: Response, data: Data?)
case invalidStatusCode(statusCode: Int, telegramDescription: String, telegramErrorCode: Int, data: Data?)

/// Telegram server returned no data
case noDataReceived

/// Server error (server returned "ok: false")
case serverError(telegramResponse: Response,
data: Data)
case serverError(data: Data)

/// Codable failed to decode to type
case decodeError(data: Data)
}

extension DataTaskError: CustomDebugStringConvertible {
Expand All @@ -51,12 +53,14 @@ extension DataTaskError: CustomDebugStringConvertible {
return "Libcurl error \(code.rawValue): \(description)"
case .libcurlAbortedByCallback:
return "Libcurl aborted by callback"
case let .invalidStatusCode(statusCode, telegramResponse, _):
return "Expected status code 200, got \(statusCode): \(telegramResponse.description.unwrapOptional)"
case let .invalidStatusCode(statusCode, telegramDescription, _, _):
return "Expected status code 200, got \(statusCode): \(telegramDescription)"
case .noDataReceived:
return "No data received"
case let .serverError(telegramResponse, _):
return "Telegram server returned an error: \(telegramResponse.description.unwrapOptional)"
case .serverError:
return "Telegram server returned an error"
case .decodeError:
return "Codable failed to decode result"
}
}
}
2 changes: 1 addition & 1 deletion Sources/TelegramBotSDK/DictionaryUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//
// This source file is part of the Telegram Bot SDK for Swift (unofficial).
//
// Copyright (c) 2015 - 2016 Andrey Fidrya and the project authors
// Copyright (c) 2015 - 2020 Andrey Fidrya and the project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See LICENSE.txt for license information
Expand Down
66 changes: 0 additions & 66 deletions Sources/TelegramBotSDK/Extensions/JSON+Utils.swift

This file was deleted.

Loading

0 comments on commit 7b1a0ee

Please sign in to comment.