自動ドキュメントパーフェクトサーバー

画像

前回Perfectには、実装されたAPIをすぐに自動文書化する機能がないと述べました。 次の実装では、開発者がこの迷惑な省略を修正する可能性があります。 しかし、自分でこれを処理することを妨げるものは何もありません。 幸いなことに、かなりのコードを追加する必要があります。



サーバーでサポートされているコマンドを確認できるスタブが既にあります。 このアプローチの機能を拡張してみましょう。



手順1 :以前に作成したPerfectサーバーを実行し、/ carsコマンドを入力してJSONを取得します。 このJSONをjsonschema.net/#にコピーし、そこからスキームを作成し、cars.jsonファイルとしてプロジェクトに追加します。 index.htmlで行ったのと同じ方法で、Xcode-> Project-> Build phaseに進み、作成したファイルを「ファイルのコピー」リストに追加することを忘れないでください。

cars.json
<code> { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "address": { "type": "object", "properties": { "streetAddress": { "type": "string" }, "city": { "type": "string" } }, "required": [ "streetAddress", "city" ] }, "phoneNumber": { "type": "array", "items": { "type": "object", "properties": { "location": { "type": "string" }, "code": { "type": "integer" } }, "required": [ "location", "code" ] } } }, "required": [ "address", "phoneNumber" ] } </code>
      
      







これは大した必要はありませんが、JSON応答スキームを取得する機会を与える方が常に良いです。 クライアントアプリケーションの開発者には感謝します。



ステップ2 :IRestHelpインターフェイスを追加する

IRestHelp.swift
 <code> import Foundation protocol IRestHelp { var details:String {get} var params :String {get} var schema :String {get} } </code>
      
      









ステップ3 :RestApiクラスを追加する

RestApi.swift
 <code> import PerfectLib class RestApi { var prefix:String? var commands:[String]? = nil var handler:RequestHandler? init(prefix:String?=nil, commands:[String]? = nil, handler:RequestHandler?=nil) { self.prefix = prefix self.commands = commands self.handler = handler } } </code>
      
      







なぜ必要なのか-それはさらに明らかになるでしょう。



ステップ4 :RestApiRegクラスを追加する

RestApiReg.swift
 <code> import Foundation import PerfectLib class RestApiReg { typealias APIList = [RestApi] // MARK: - Public Properties private var commandList = APIList() // MARK: - Private Properties private var globalRegistered = false // MARK: - Singletone Implementation private init() { } private class var sharedInstance: RestApiReg { struct Static { static var instance: RestApiReg? static var token: dispatch_once_t = 0 } dispatch_once(&Static.token) { Static.instance = RestApiReg() } return Static.instance! } // MARK: - Methods of class class func registration(list:APIList) { self.sharedInstance.commandList = list self.sharedInstance.linkAll() } class func add(command:RestApi) { self.sharedInstance.commandList += [command] self.sharedInstance.add(command) } class var list: APIList { return self.sharedInstance.commandList } // MARK: - Private methods private func linkAll() { Routing.Handler.registerGlobally() self.globalRegistered = true for api in self.commandList { self.add(api) } } private func add(api:RestApi) { if !self.globalRegistered { Routing.Handler.registerGlobally() } if let handler = api.handler { let prefix = api.prefix == nil ? "*" : api.prefix! if let commands = api.commands { Routing.Routes[prefix, commands] = { (_:WebResponse) in handler } } else { Routing.Routes[prefix] = { (_:WebResponse) in handler } } } } } </code>
      
      







このクラスのより良い名前を思い付くことができませんでした。 このクラスは、新しいサーバーAPIの登録を仲介します。



手順5 :HelpHandlerクラスを次のコードに置き換えます。

HelpHandler.swift
 <code> import Foundation import PerfectLib class HelpHandler:RequestHandler, IRestHelp { var details = "Show server comands list" var params = "" var schema = "" func handleRequest(request: WebRequest, response: WebResponse) { let list = self.createList() let html = ContentPage(title:"HELP", body:list).page(request.documentRoot) response.appendBodyString("\(html)") response.requestCompletedCallback() } private func createList() -> String { let list = RestApiReg.list var code = "" let allPrefixes = list.map { (api) -> String in api.prefix != nil ? api.prefix! : "/*" } let groups = Set<String>(allPrefixes).sort() for group in groups { let commandsApi = self.commandsByGroup(group, list:list) code += self.titleOfGroup(group) code += self.tableWithCommnads(commandsApi) } return code } private func commandsByGroup(group:String, list:RestApiReg.APIList) -> [String:RestApi] { var dict = [String:RestApi]() let commandsOfGroup = list.filter({ (api) -> Bool in api.prefix == group }) for api in commandsOfGroup { if let commands = api.commands { for cmd in commands { dict[cmd] = api } } else { dict[""] = api } } return dict } private func titleOfGroup(group:String) -> String { return " <B>\(group):</B> " } private func tableWithCommnads(commands:[String:RestApi]) -> String { let sortedList = commands.keys.sort() var table = "" table += "<table border = \"1px\" width=\"100%\">" for name in sortedList { let cmd = commands[name]! table += "<tr>" table += "<td width=\"15%\"><a href=\"\(name)\">\(name)</a></td>" if let help = cmd.handler as? IRestHelp { table += "<td>\(help.details)</td>" table += "<td>\(help.params)</td>" table += help.schema.characters.count > 0 ? "<td><a href=\"\(help.schema)\">/\(help.schema)</a></td>" : "<td></td>" } else { table += "<td></td>" table += "<td></td>" table += "<td></td>" } table += "</tr>" } table += "</table>" return table } } </code>
      
      









手順6 :自動ドキュメント化が必要な各コマンドのハンドラーにIRestHelpプロトコルの実装を追加します。 この手順はオプションです。 プロトコルをサポートしないチームは、適切なフィールドに空の値があります。 たとえば、/ listコマンドハンドラー(CarsJsonクラス)は、このように見えます。

CarsJson.swift
 <code> import Foundation import PerfectLib class CarsJson:RequestHandler, IRestHelp { var details = "Show complexly JSON object" var params = "{}" var schema = "cars.json" func handleRequest(request: WebRequest, response: WebResponse) { let car1:[JSONKey: AnyObject] = ["Wheel":4, "Color":"Black"] let car2:[JSONKey: AnyObject] = ["Wheel":3, "Color":["mixColor":0xf2f2f2]] let cars = [car1, car2] let restResponse = RESTResponse(data:cars) response.appendBodyBytes(restResponse.array) response.requestCompletedCallback() } } </code>
      
      









手順7 :PerfectServerModuleInit()メソッドを新しいコードに置き換えます。

PerfectServerModuleInit()
 <code> public func PerfectServerModuleInit() { RestApiReg.add(RestApi(handler: StaticFileHandler())) RestApiReg.add(RestApi(prefix: "GET", commands: ["/dynamic"], handler: StaticPageHandler(staticPage: "index.mustache"))) RestApiReg.add(RestApi(prefix: "GET", commands: ["/index", "/list"], handler: StaticPageHandler(staticPage: "index.html"))) RestApiReg.add(RestApi(prefix: "GET", commands: ["/hello"], handler: HelloHandler())) RestApiReg.add(RestApi(prefix: "GET", commands: ["/help"], handler: HelpHandler())) RestApiReg.add(RestApi(prefix: "GET", commands: ["/cars", "/car"], handler: CarsJson())) RestApiReg.add(RestApi(prefix: "POST", commands: ["/list"], handler: CarsJson())) } </code>
      
      









発射!



元のページは同じままです。

ブラウザのコマンドラインで入力/ヘルプを試みます:

画像

すべてのチームがアルファベット順にテーブルに並んでおり、ハイパーリンクを取得していることがわかります。 ヘルプページに入った後、ブラウザのコマンドラインに各コマンドを入力して実行する必要はありません。 そして、右端の列には、このコマンドの検証のための回路へのリンクがあります。

将来、クライアントアプリケーションに送信する前に、検証スキームを使用して、作成した応答の正確性を検証できます。 また、クライアントアプリケーションはサーバーから検証スキームを直接ダウンロードする可能性があります。 検証により、二重利益が得られます。



もちろん、テーブルは不器用です。 CSSを使用すると、その外観が大幅に改善されます。 しかし、仕事のためには、原則として、これで十分です。

最初は、リクエスト/ヘルプで、同様のテーブルにデータを並べるスキーマを持つXMLファイルを表示したいという要望がありました。 ただし、HTMLの外観を改善することは、あらゆる種類のXMLマッピングを楽しむよりもはるかに楽しいです。



PS知られているように、Perfect開発者はサーバーを* nixシステムで実行できるようにするためにNextStep(Objective-C)の重いレガシーを取り除くために一生懸命働いています。したがって、NSネームスペースで働くいくつかのよく知られた方法は非コーシャーとみなされています。



All Articles