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