github.com/tada-team/tdproto@v1.51.57/codegen/sphinx/paths_doc/main.go (about) 1 package main 2 3 import ( 4 "flag" 5 "fmt" 6 "os" 7 "reflect" 8 "strings" 9 "text/template" 10 11 "github.com/tada-team/tdproto/codegen/api_paths" 12 ) 13 14 type pathDoc struct { 15 MethodName string 16 Path string 17 Description string 18 RequestDescription string 19 RequestObjectName string 20 RequestQueryName string 21 ResultDescription string 22 ResultObjectName string 23 ResultKind reflect.Kind 24 } 25 26 func (p pathDoc) ToSwaggerUrl() string { 27 28 suffix := fmt.Sprintf( 29 "%s%s", p.MethodName, 30 strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll( 31 strings.ReplaceAll(p.Path, "/", "_"), 32 "{", "_"), "}", "_"), ".", "_")) 33 34 return fmt.Sprintf("`🔍 Try it! <https://tada-team.github.io/td-swagger-ui/#/default/%s>`__", suffix) 35 } 36 37 func (p pathDoc) ToParams() string { 38 39 var builder strings.Builder 40 41 possibleParameters := []string{ 42 "team_id", "contact_id", 43 "chat_id", "message_id", 44 "group_id", "task_id"} 45 46 for _, paramString := range possibleParameters { 47 if strings.Contains(p.Path, paramString) { 48 builder.WriteString(fmt.Sprintf("\n :param %s: ID of the %s.", paramString, strings.Split(paramString, "_")[0])) 49 } 50 } 51 52 return builder.String() 53 } 54 55 func (p pathDoc) ToRequestText() string { 56 if p.RequestDescription != "" { 57 return p.RequestDescription 58 } 59 60 if p.RequestObjectName == "" { 61 return "" 62 } 63 64 return fmt.Sprintf("The :tdproto:tdmodels:`%s` object.", p.RequestObjectName) 65 66 } 67 68 func (p pathDoc) ToResultText() string { 69 if p.ResultDescription != "" { 70 return p.ResultDescription 71 } 72 73 if p.ResultKind == reflect.Slice { 74 return fmt.Sprintf("List of :tdproto:ref:`%s` objects.", p.ResultObjectName) 75 } else { 76 return fmt.Sprintf("The :tdproto:ref:`%s` object.", p.ResultObjectName) 77 } 78 } 79 80 func (p pathDoc) ToResultType() string { 81 switch p.ResultKind { 82 case reflect.Slice: 83 return "array" 84 case reflect.Struct: 85 return "object" 86 case reflect.String: 87 return "string" 88 case reflect.Int: 89 return "int" 90 } 91 92 panic(fmt.Errorf("unknown result kind %v", p.ResultKind)) 93 } 94 95 func (p pathDoc) ToQueryLink() string { 96 if p.RequestQueryName == "" { 97 return "" 98 } 99 100 return fmt.Sprintf("\n\n Query parameters: :ref:`tdproto-%sQuery`", p.RequestQueryName) 101 } 102 103 var pathsTemplate = template.Must(template.New("rstPath").Parse(` 104 .. http:{{- .MethodName -}}:: {{.Path}} 105 106 {{.Description}}{{.ToQueryLink}} 107 108 {{.ToSwaggerUrl}} 109 {{.ToParams}}{{if .ToRequestText}} 110 :reqjson object: {{.ToRequestText}}{{end}} 111 :resjson boolean ok: True if no error occured.{{if .ResultObjectName}} 112 :resjson {{.ToResultType}} result: {{.ToResultText}}{{end}} 113 :status 200: No error. 114 `)) 115 116 func generateSpecRst(path string, spec api_paths.OperationSpec, method string) error { 117 118 if spec.Description == nil { 119 return fmt.Errorf("path %s %s missing description", method, path) 120 } 121 122 var resultObjectName string 123 var resultKind reflect.Kind 124 if spec.Response != nil { 125 resultKind = reflect.TypeOf(spec.Response).Kind() 126 if resultKind == reflect.Slice { 127 resultObjectName = reflect.TypeOf(spec.Response).Elem().Name() 128 } else { 129 resultObjectName = reflect.TypeOf(spec.Response).Name() 130 } 131 } 132 133 var description string 134 switch d := reflect.TypeOf(spec.Description).Kind(); d { 135 case reflect.String: 136 description = spec.Description.(string) 137 case reflect.Slice: 138 if reflect.TypeOf(spec.Description).Elem().Kind() != reflect.String { 139 return fmt.Errorf("path %s %s description is not slice of strings", method, path) 140 } 141 description = strings.Join(spec.Description.([]string), "\n\n ") 142 } 143 144 var requestObjectName string 145 146 if spec.Request != nil { 147 requestTypeOf := reflect.TypeOf(spec.Request) 148 requestObjectName = requestTypeOf.Name() 149 if requestObjectName == "" && spec.Request != nil { 150 return fmt.Errorf("failed to get request type name %v", spec.Request) 151 } 152 } 153 154 var requestQueryName string 155 if spec.QueryStruct != nil { 156 queryTypeOf := reflect.TypeOf(spec.QueryStruct) 157 requestQueryName = queryTypeOf.Name() 158 if requestQueryName == "" { 159 return fmt.Errorf("failed to get query type name %v", spec.QueryStruct) 160 } 161 } 162 163 err := pathsTemplate.Execute(os.Stdout, pathDoc{ 164 Path: path, 165 MethodName: method, 166 Description: description, 167 RequestDescription: spec.RequestDescription, 168 RequestObjectName: requestObjectName, 169 RequestQueryName: requestQueryName, 170 ResultDescription: spec.ResponseDescription, 171 ResultObjectName: resultObjectName, 172 ResultKind: resultKind, 173 }) 174 if err != nil { 175 return err 176 } 177 178 return nil 179 } 180 181 func generatePathsRst(pathCollectionName string) error { 182 183 specCollection, found := api_paths.AllPaths[pathCollectionName] 184 if !found { 185 return fmt.Errorf("path collection not found %v", pathCollectionName) 186 } 187 188 collectionTitle, found := api_paths.PathTitles[pathCollectionName] 189 if !found { 190 return fmt.Errorf("no title found for collection %s", pathCollectionName) 191 } 192 193 fmt.Fprintf(os.Stdout, `%s 194 ---------------------------------------------- 195 `, 196 collectionTitle) 197 198 for _, spec := range specCollection { 199 if spec.Get != nil { 200 err := generateSpecRst(spec.Path, *spec.Get, "get") 201 if err != nil { 202 return err 203 } 204 } 205 if spec.Post != nil { 206 err := generateSpecRst(spec.Path, *spec.Post, "post") 207 if err != nil { 208 return err 209 } 210 } 211 if spec.Put != nil { 212 err := generateSpecRst(spec.Path, *spec.Put, "put") 213 if err != nil { 214 return err 215 } 216 } 217 if spec.Delete != nil { 218 err := generateSpecRst(spec.Path, *spec.Delete, "delete") 219 if err != nil { 220 return err 221 } 222 } 223 } 224 225 return nil 226 } 227 228 func main() { 229 flag.Parse() 230 arg := flag.Arg(0) 231 232 err := generatePathsRst(arg) 233 if err != nil { 234 panic(err) 235 } 236 }