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  }