1 // Copyright 2017 Google Inc. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 import Foundation
16 import Gnostic
17
18 extension String {
capitalizingFirstLetternull19 func capitalizingFirstLetter() -> String {
20 let first = String(characters.prefix(1)).capitalized
21 let other = String(characters.dropFirst())
22 return first + other
23 }
24
capitalizeFirstLetternull25 mutating func capitalizeFirstLetter() {
26 self = self.capitalizingFirstLetter()
27 }
28 }
29
30 class ServiceType {
31 var name : String = ""
32 var fields : [ServiceTypeField] = []
33 var isInterfaceType : Bool = false
34 }
35
36 class ServiceTypeField {
37 var name : String = ""
38 var typeName : String = ""
39 var isArrayType : Bool = false
40 var isCastableType : Bool = false
41 var isConvertibleType : Bool = false
42 var elementType : String = ""
43 var jsonName : String = ""
44 var position: String = "" // "body", "header", "formdata", "query", or "path"
45 var initialValue : String = ""
46
47
setTypeForNamenull48 func setTypeForName(_ name : String, _ format : String) {
49 switch name {
50 case "integer":
51 if format == "int32" {
52 self.typeName = "Int32"
53 } else if format == "int64" {
54 self.typeName = "Int64"
55 } else {
56 self.typeName = "Int"
57 }
58 self.initialValue = "0"
59 self.isCastableType = true
60 default:
61 self.typeName = name.capitalizingFirstLetter()
62 self.initialValue = self.typeName + "()"
63 self.isConvertibleType = true
64 }
65 }
66
67
setTypeForSchemanull68 func setTypeForSchema(_ schema : Openapi_V2_Schema, optional : Bool = false) {
69 let ref = schema.ref
70 if ref != "" {
71 self.typeName = typeForRef(ref)
72 self.isConvertibleType = true
73 self.initialValue = self.typeName + "()"
74 }
75 if schema.hasType {
76 let types = schema.type.value
77 let format = schema.format
78 if types.count == 1 && types[0] == "string" {
79 self.typeName = "String"
80 self.isCastableType = true
81 self.initialValue = "\"\""
82 }
83 if types.count == 1 && types[0] == "integer" && format == "int32" {
84 self.typeName = "Int32"
85 self.isCastableType = true
86 self.initialValue = "0"
87 }
88 if types.count == 1 && types[0] == "array" && schema.hasItems {
89 // we have an array.., but of what?
90 let items = schema.items.schema
91 if items.count == 1 && items[0].ref != "" {
92 self.isArrayType = true
93 self.elementType = typeForRef(items[0].ref)
94 self.typeName = "[" + self.elementType + "]"
95 self.initialValue = "[]"
96 }
97 }
98 }
99 // this function is incomplete... so return a string representing anything that we don't handle
100 if self.typeName == "" {
101 self.typeName = "\(schema)"
102 }
103 if optional {
104 self.typeName += "?"
105 self.initialValue = "nil"
106 }
107 }
108 }
109
110 class ServiceMethod {
111 var name : String = ""
112 var path : String = ""
113 var method : String = ""
114 var description : String = ""
115 var handlerName : String = ""
116 var processorName : String = ""
117 var clientName : String = ""
118 var resultTypeName : String?
119 var parametersTypeName : String?
120 var responsesTypeName : String?
121 var parametersType : ServiceType?
122 var responsesType : ServiceType?
123 }
124
propertyNameForResponseCodenull125 func propertyNameForResponseCode(_ code:String) -> String {
126 switch code {
127 case "200":
128 return "ok"
129 case "default":
130 return "error"
131 default:
132 return code
133 }
134 }
135
typeForRefnull136 func typeForRef(_ ref : String) -> String {
137 let parts = ref.components(separatedBy:"/")
138 return parts.last!.capitalizingFirstLetter()
139 }
140
141 class ServiceRenderer {
142 internal var name : String = ""
143 internal var package: String = ""
144 internal var types : [ServiceType] = []
145 internal var methods : [ServiceMethod] = []
146 internal var surface : Surface_V1_Model
147
148 public init(surface : Surface_V1_Model, document : Openapi_V2_Document) {
149 self.surface = surface
150 loadService(document:document)
151 }
152
153 private func loadServiceTypeFromParameters(_ name:String,
154 _ parameters:[Openapi_V2_ParametersItem])
155 -> ServiceType? {
156 let t = ServiceType()
157 t.name = name.capitalizingFirstLetter() + "Parameters"
158 for parametersItem in parameters {
159 let f = ServiceTypeField()
160 f.typeName = "\(parametersItem)"
161
162 switch parametersItem.oneof! {
163 case .parameter(let parameter):
164 switch parameter.oneof! {
165 case .bodyParameter(let bodyParameter):
166 f.name = bodyParameter.name
167 if bodyParameter.hasSchema {
168 f.setTypeForSchema(bodyParameter.schema)
169 f.position = "body"
170 }
171 case .nonBodyParameter(let nonBodyParameter):
172 switch (nonBodyParameter.oneof!) {
173 case .headerParameterSubSchema(let headerParameter):
174 f.name = headerParameter.name
175 f.position = "header"
176 case .formDataParameterSubSchema(let formDataParameter):
177 f.name = formDataParameter.name
178 f.position = "formdata"
179 case .queryParameterSubSchema(let queryParameter):
180 f.name = queryParameter.name
181 f.position = "query"
182 case .pathParameterSubSchema(let pathParameter):
183 f.name = pathParameter.name
184 f.jsonName = pathParameter.name
185 f.position = "path"
186 f.setTypeForName(pathParameter.type, pathParameter.format)
187 }
188 }
189 case .jsonReference: // (let reference):
190 Log("?")
191 }
192 t.fields.append(f)
193 }
194 if t.fields.count > 0 {
195 self.types.append(t)
196 return t
197 } else {
198 return nil
199 }
200 }
201
202 private func loadServiceTypeFromResponses(_ m:ServiceMethod,
203 _ name:String,
204 _ responses:Openapi_V2_Responses)
205 -> ServiceType? {
206 let t = ServiceType()
207 t.name = name.capitalizingFirstLetter() + "Responses"
208 for responseCode in responses.responseCode {
209 let f = ServiceTypeField()
210 f.name = propertyNameForResponseCode(responseCode.name)
211 f.jsonName = ""
212 if let responseCodeValueOneOf = responseCode.value.oneof {
213 switch responseCodeValueOneOf {
214 case .response(let response):
215 let schema = response.schema
216 if let schemaOneOf = schema.oneof {
217 switch schemaOneOf {
218 case .schema(let schema):
219 f.setTypeForSchema(schema, optional:true)
220 t.fields.append(f)
221 if f.name == "ok" {
222 m.resultTypeName = f.typeName.replacingOccurrences(of:"?", with:"")
223 }
224 default:
225 break
226 }
227 }
228 default:
229 break
230 }
231 }
232 }
233 if t.fields.count > 0 {
234 self.types.append(t)
235 return t
236 } else {
237 return nil
238 }
239 }
240
241 private func loadOperation(_ operation : Openapi_V2_Operation,
242 method : String,
243 path : String) {
244 let m = ServiceMethod()
245 m.name = operation.operationID
246 m.path = path
247 m.method = method
248 m.description = operation.description_p
249 m.handlerName = "handle" + m.name
250 m.processorName = "" + m.name
251 m.clientName = m.name
252 m.parametersType = loadServiceTypeFromParameters(m.name, operation.parameters)
253 if m.parametersType != nil {
254 m.parametersTypeName = m.parametersType!.name
255 }
256 m.responsesType = loadServiceTypeFromResponses(m, m.name, operation.responses)
257 if m.responsesType != nil {
258 m.responsesTypeName = m.responsesType!.name
259 }
260 self.methods.append(m)
261 }
262
loadServicenull263 private func loadService(document : Openapi_V2_Document) {
264 // collect service type descriptions
265 for pair in document.definitions.additionalProperties {
266 let t = ServiceType()
267 t.isInterfaceType = true
268 let schema = pair.value
269 for pair2 in schema.properties.additionalProperties {
270 let f = ServiceTypeField()
271 f.name = pair2.name
272 f.setTypeForSchema(pair2.value)
273 f.jsonName = pair2.name
274 t.fields.append(f)
275 }
276 t.name = pair.name.capitalizingFirstLetter()
277 self.types.append(t)
278 }
279 // collect service method descriptions
280 for pair in document.paths.path {
281 let v = pair.value
282 if v.hasGet {
283 loadOperation(v.get, method:"GET", path:pair.name)
284 }
285 if v.hasPost {
286 loadOperation(v.post, method:"POST", path:pair.name)
287 }
288 if v.hasPut {
289 loadOperation(v.put, method:"PUT", path:pair.name)
290 }
291 if v.hasDelete {
292 loadOperation(v.delete, method:"DELETE", path:pair.name)
293 }
294 }
295 }
296
generatenull297 public func generate(filenames : [String], response : inout Gnostic_Plugin_V1_Response) throws {
298 for filename in filenames {
299 var data : Data?
300 switch filename {
301 case "types.swift":
302 data = renderTypes().data(using:.utf8)
303 case "server.swift":
304 data = renderServer().data(using:.utf8)
305 case "client.swift":
306 data = renderClient().data(using:.utf8)
307 case "fetch.swift":
308 data = renderFetch().data(using:.utf8)
309 default:
310 print("error: unable to render \(filename)")
311 }
312 if let data = data {
313 var clientfile = Gnostic_Plugin_V1_File()
314 clientfile.name = filename
315 clientfile.data = data
316 response.files.append(clientfile)
317 }
318 }
319 }
320 }
321
322 let header = """
323 // Copyright 2017 Google Inc. All Rights Reserved.
324 //
325 // Licensed under the Apache License, Version 2.0 (the "License");
326 // you may not use this file except in compliance with the License.
327 // You may obtain a copy of the License at
328 //
329 // http://www.apache.org/licenses/LICENSE-2.0
330 //
331 // Unless required by applicable law or agreed to in writing, software
332 // distributed under the License is distributed on an "AS IS" BASIS,
333 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
334 // See the License for the specific language governing permissions and
335 // limitations under the License.
336 """
337