1Thrift Swift Library
2=========================
3
4License
5-------
6Licensed to the Apache Software Foundation (ASF) under one
7or more contributor license agreements. See the NOTICE file
8distributed with this work for additional information
9regarding copyright ownership. The ASF licenses this file
10to you under the Apache License, Version 2.0 (the
11"License"); you may not use this file except in compliance
12with the License. You may obtain a copy of the License at
13
14  http://www.apache.org/licenses/LICENSE-2.0
15
16Unless required by applicable law or agreed to in writing,
17software distributed under the License is distributed on an
18"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19KIND, either express or implied. See the License for the
20specific language governing permissions and limitations
21under the License.
22
23
24## Build
25    swift build
26
27## Test
28    swift test
29
30## Install Library
31##### Cocoapods
32Add the following to your podfile
33```ruby
34    pod 'Thrift-swift3', :git => 'git@github.com:apache/thrift.git', :branch => 'master'
35```
36
37##### SPM
38Unfortunately due to some limitations in SPM, the Package manifest and Sources directory must be at the root of the project.
39To get around that for the time being, you can use this mirrored repo.
40Add the following to your Package.swift
41```swift
42dependencies: [
43    .Package(url: "https://github.com/apocolipse/Thrift-Swift.git", majorVersion: 1)
44]
45```
46
47## Thrift Compiler
48
49You can compile IDL sources for Swift 3 with the following command:
50
51    thrift --gen swift thrift_file
52
53## Client Example
54```swift
55let transport = TSocketTransport(hostname: "localhost", port: 9090)!
56
57//  var proto = TCompactProtocol(transport: transport)
58let proto = TBinaryProtocol(on: transport)
59//  var client = HermesClient(inoutProtocol: proto)
60let client = ThriftTestClient(inoutProtocol: proto)
61do {
62    try client.testVoid()
63} catch let error {
64    print("\(error)")
65}
66```
67
68## Library Notes
69- Eliminated Protocol Factories, They were only used in async clients and server implementations, where Generics provide a better alternative.
70- Swifty Errors, All `TError` types have a nested `ErrorCode` Enum as well as some extra flavor where needed.
71- Value typed everything.  `TTransport` operates on value typed `Data` rather than reference typed `NSData` or `UnsafeBufferPointer`s
72- Swift 3 Named protocols.  Swift 3 naming conventions suggest the elimination of redundant words that can be inferred from variable/function signatures.  This renaming is applied throughout the Swift 3 library converting most naming conventions used in the Swift2/Cocoa library to Swift 3-esque naming conventions. eg.
73```swift
74func readString() throws -> String
75func writeString(_ val: String) throws
76```
77have been renamed to eliminate redundant words:
78```swift
79func read() throws -> String
80func write(_ val: String) throws
81```
82
83- Eliminated `THTTPTransport` that uses `NSURLConnection` due to it being deprecated and not available at all in Swift 3 for Linux.  `THTTPSessionTransport` from the Swift2/Cocoa library that uses `NSURLSession` has been renamed to `THTTPTransport` for this library and leverages `URLSession`, providing both synchronous (with semaphores) and asynchronous behavior.
84- Probably some More things I've missed here.
85
86## Generator Notes
87#### Generator Flags
88| Flag          | Description           |
89| ------------- |:-------------:|
90| async_clients | Generate clients which invoke asynchronously via block syntax. Asynchronous classes are appended with `_Async` |
91| no_strict*    | Generates non-strict structs      |
92| debug_descriptions | Allow use of debugDescription so the app can add description via a cateogory/extension      |
93| log_unexpected | Log every time an unexpected field ID or type is encountered. |
94| safe_enums     | Generate enum types with an unknown case to handle unspecified values rather than throw a serialization error  |
95
96
97
98*Most thrift libraries allow empty initialization of Structs, initializing `required` fields with nil/null/None (Python and Node generators).  Swift on the other hand requires initializers to initialize all non-Optional fields, and thus the Swift 3 generator does not provide default values (unlike the Swift 2/Cocoa generator).  In other languages, this allows the sending of NULL values in fields that are marked `required`, and thus will throw an error in Swift clients attempting to validate fields.  The `no_strict` option here will ignore the validation check, as well as behave similar to the Swift2/Cocoa generator and initialize required fields with empty initializers (where possible).
99
100
101## Whats implemented
102#### Library
103##### Transports
104- [x] TSocketTransport - CFSocket and PosixSocket variants available. CFSocket variant only currently available for Darwin platforms
105- [x] THTTPTransport - Currently only available for Darwin platforms, Swift Foundation URLSession implementation needs completion on linux.
106- [x] TSocketServer - Uses CFSockets only for binding, should be working on linux
107- [x] TFramedTransport
108- [x] TMemoryBufferTransport
109- [x] TFileTransport - A few variants using File handles and file descriptors.
110- [x] TStreamTransport - Fully functional in Darwin, Foundation backing not yet completed in Linux (This limits TCFSocketTransport to Darwin)
111- [ ] HTTPServer - Currently there is no lightweight  HTTPServer implementation the Swift Standard Library, so other 3rd party alternatives are required and out of scope for the Thrift library.  Examples using Perfect will be provided.
112- [ ] Other (gz, etc)
113
114##### Protocols
115- [x] TBinaryProtocol
116- [x] TCompactProtocol
117- [ ] TJSONProtocol - This will need to be implemented
118
119##### Generator
120- [x] Code Complete Generator
121- [x] Async clients
122- [x] Documentation Generation - Generator will transplant IDL docs to Swift code for easy lookup in Xcode
123- [ ] Default Values - TODO
124- [ ] no_strict mode - TODO
125- [ ] Namespacing - Still haven't nailed down a good paradigm for namespacing.  It will likely involve creating subdirectories for different namespaces and expecting the developer to import each subdirectory as separate modules.  It could extend to creating SPM Package manifests with sub-modules within the generated module
126
127
128
129## Example HTTP Server with Perfect
130```swift
131import PerfectLib
132import PerfectHTTP
133import PerfectHTTPServer
134import Dispatch
135
136let logQueue = DispatchQueue(label: "log", qos: .background, attributes: .concurrent)
137let pQueue = DispatchQueue(label: "log", qos: .userInitiated, attributes: .concurrent)
138
139
140class TPerfectServer<InProtocol: TProtocol, OutProtocol: TProtocol> {
141
142 private var server = HTTPServer()
143 private var processor: TProcessor
144
145 init(address: String? = nil,
146      path: String? = nil,
147      port: Int,
148      processor: TProcessor,
149      inProtocol: InProtocol.Type,
150      outProtocol: OutProtocol.Type) throws {
151
152   self.processor = processor
153
154   if let address = address {
155     server.serverAddress = address
156   }
157   server.serverPort = UInt16(port)
158
159   var routes = Routes()
160   var uri = "/"
161   if let path = path {
162     uri += path
163   }
164   routes.add(method: .post, uri: uri) { request, response in
165     pQueue.async {
166       response.setHeader(.contentType, value: "application/x-thrift")
167
168       let itrans = TMemoryBufferTransport()
169       if let bytes = request.postBodyBytes {
170         let data = Data(bytes: bytes)
171         itrans.reset(readBuffer: data)
172       }
173
174       let otrans = TMemoryBufferTransport(flushHandler: { trans, buff in
175         let array = buff.withUnsafeBytes {
176           Array<UInt8>(UnsafeBufferPointer(start: $0, count: buff.count))
177         }
178         response.status = .ok
179         response.setBody(bytes: array)
180         response.completed()
181       })
182
183       let inproto = InProtocol(on: itrans)
184       let outproto = OutProtocol(on: otrans)
185
186       do {
187         try processor.process(on: inproto, outProtocol: outproto)
188         try otrans.flush()
189       } catch {
190         response.status = .badRequest
191         response.completed()
192       }
193     }
194   }
195   server.addRoutes(routes)
196 }
197
198 func serve() throws {
199   try server.start()
200 }
201}
202```
203
204#### Example Usage
205```swift
206class ServiceHandler : Service {
207    ...
208}
209let server = try? TPerfectServer(port: 9090,
210                                processor: ServiceProcessor(service: ServiceHandler()),
211                                inProtocol: TBinaryProtocol.self,
212                                outProtocol: TBinaryProtocol.self)
213
214try? server?.serve()
215```
216