be8307965fe8 — Sean Russell 11 years ago
The sashay command now generates entire API resource from a URL.
Fixes void GET handling
Relocates basePath handling for multiple APIs in same file
Fixes GET string handling
Fixes POST return indentation
6 files changed, 443 insertions(+), 22 deletions(-)

M cmd/sashay/main.go
M generator.go
M get.go
M post.go
M test/api-docs_pet.json => test/pet.json
A => test/user.json
M cmd/sashay/main.go +52 -2
@@ 2,12 2,54 @@ package main
 
 import (
 	"bitbucket.org/seanerussell/sashay"
+	"flag"
 	"fmt"
 	"io/ioutil"
+	"net/http"
+	"os"
 )
 
 func main() {
-	b, _ := ioutil.ReadFile("test/api-docs_pet.json")
+	file := flag.String("f", "", "API specification JSON file")
+	url := flag.String("u", "", "API specification URL")
+	pack := flag.String("p", "", "API package")
+	flag.Parse()
+	if *pack == "" {
+		fmt.Printf("ERROR: package must be provided")
+		flag.Usage()
+		os.Exit(1)
+	}
+	if *file == "" && *url == "" {
+		fmt.Printf("ERROR: an API specification must be provided -- either -f or -u")
+		flag.Usage()
+		os.Exit(1)
+	}
+	if *file != "" && *url != "" {
+		fmt.Printf("ERROR: only one API specification can be provided")
+		flag.Usage()
+		os.Exit(1)
+	}
+	var b []byte
+	var e error
+	if *file != "" {
+		b, e = ioutil.ReadFile(flag.Args()[0])
+		if e != nil {
+			fmt.Printf("error reading %s: %s", *file, e.Error())
+			os.Exit(1)
+		}
+	} else {
+		r, e := http.Get(*url)
+		if e != nil {
+			fmt.Printf("error fetching %s: %s", *url, e.Error())
+			os.Exit(1)
+		}
+		defer r.Body.Close()
+		b, e = ioutil.ReadAll(r.Body)
+		if e != nil {
+			fmt.Printf("error reading body of %s: %s", *url, e.Error())
+			os.Exit(1)
+		}
+	}
 	c := make(chan string)
 	wait := make(chan bool)
 	go func() {

          
@@ 16,7 58,15 @@ func main() {
 		}
 		close(wait)
 	}()
-	sashay.Generate("test", b, c)
+	t, e := sashay.GetType(b);
+	if e != nil {
+		fmt.Printf("error getting Swagger type of content (%s):\n%s", e.Error(), b)
+	}
+	if t == sashay.API {
+		sashay.GenerateApi(*pack, b, c)
+	} else {
+		sashay.GenerateResourceListing(*url, *pack, b, c)
+	}
 	close(c)
 	<-wait
 }

          
M generator.go +74 -17
@@ 2,23 2,78 @@ package sashay
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"strings"
 	"github.com/glenn-brown/skiplist"
+	"io/ioutil"
+	"net/http"
 	"sync"
 )
 
 var swaggerToGo map[string]string
 
-func Generate(pack string, j []byte, out chan string) error {
-	t, e := Parse(j)
+func GenerateApi(pack string, j []byte, out chan string) error {
+	t, e := ParseApi(j)
+	if e != nil {
+		return e
+	}
+	outWrapper, wait, e := makeImportHandler(pack, out)
+	if e != nil {
+		return e
+	}
+	generateApi(t, outWrapper)
+	close(outWrapper)
+	wait.Wait()
+	return nil
+}
+
+func GenerateResourceListing(baseUri, pack string, j []byte, out chan string) error {
+	r, e := ParseResource(j)
+	if e != nil {
+		return e
+	}
+	outWrapper, wait, e := makeImportHandler(pack, out)
 	if e != nil {
 		return e
 	}
-	outWrapper := make(chan string)
-	wait := sync.WaitGroup{}
+	generateResourceListing(baseUri, r, outWrapper)
+	close(outWrapper)
+	wait.Wait()
+	return nil
+}
+
+func generateResourceListing(baseUri string, r ResourceListing, out chan string) error {
+	l := len(r.Apis)-1
+	for i, a := range r.Apis {
+		out <- "/*******************************************************************************"
+		out <- fmt.Sprintf(" RESOURCE %s", a.Path)
+		out <- fmt.Sprintf(" %s", a.Description)
+		out <- "*******************************************************************************/"
+		b, e := http.Get(baseUri + a.Path)
+		if e != nil {
+			return e
+		}
+		defer b.Body.Close()
+		j, e := ioutil.ReadAll(b.Body)
+		t, e := ParseApi(j)
+		if e != nil {
+			return e
+		}
+		generateApi(t, out)
+		if i < l {
+			out <- ""
+		}
+	}
+	return nil
+}
+
+func makeImportHandler(pack string, out chan string) (outWrapper chan string, wait *sync.WaitGroup, err error) {
+	outWrapper = make(chan string)
+	wait = &sync.WaitGroup{}
 	wait.Add(1)
 	go func() {
+		basePath := ""
 		lines := make([]string, 0, 100)
 		imports := skiplist.New()
 		imports.Set("import \"fmt\"", true)

          
@@ 26,6 81,11 @@ func Generate(pack string, j []byte, out
 		for line := range outWrapper {
 			if strings.HasPrefix(line, "import ") {
 				imports.Set(line, true)
+			} else if strings.HasPrefix(line, "const basePath") {
+				if basePath != line {
+					err = errors.New("inconsistent basePaths")
+				}
+				basePath = line
 			} else {
 				lines = append(lines, line)
 			}

          
@@ 36,20 96,14 @@ func Generate(pack string, j []byte, out
 			out <- f.Key().(string)
 		}
 		out <- ""
+		out <- basePath
+		out <- ""
 		for _, line := range lines {
 			out <- line
 		}
 		wait.Done()
 	}()
-	switch t.(type) {
-	case ApiDecl:
-		generateApi(t.(ApiDecl), outWrapper)
-	case ResourceListing:
-		generateResourceListing(t.(ResourceListing), outWrapper)
-	}
-	close(outWrapper)
-	wait.Wait()
-	return nil
+	return outWrapper, wait, nil
 }
 
 func generateApi(a ApiDecl, out chan string) {

          
@@ 139,6 193,10 @@ func genArrayToString(p Parameter, out c
 }
 
 func genDeserialize(op Operation, out chan string) {
+	if op.Type == "void" {
+		out <- "\treturn nil"
+		return
+	}
 	out <- "\tdefer resp.Body.Close()"
 	out <- "import \"io/ioutil\""
 	out <- "\ts, e := ioutil.ReadAll(resp.Body)"

          
@@ 147,7 205,7 @@ func genDeserialize(op Operation, out ch
 	out <- "\t}"
 	switch op.typeOf() {
 	case "string":
-		out <- "\treturn s, nil"
+		out <- "\treturn string(s), nil"
 	case "int":
 		out <- "import \"strconv\""
 		out <- "\treturn strconv.Atoi(s)"

          
@@ 158,6 216,8 @@ func genDeserialize(op Operation, out ch
 		out <- fmt.Sprintf("\t\treturn %s, e", op.zero())
 		out <- "\t}"
 		out <- "\treturn k, nil"
+	case "void":
+		out <- "\treturn nil"
 	default:
 		encoding(op.Produces, "\te = %s.Unmarshal(s, &rv)", out)
 		out <- "\tif e != nil {"

          
@@ 228,9 288,6 @@ func genRv(op Operation) string {
 	return fmt.Sprintf("(%s, error)", t)
 }
 
-func generateResourceListing(r ResourceListing, out chan string) {
-}
-
 func upcase(s string) string {
 	if len(s) == 0 {
 		return s

          
M get.go +8 -2
@@ 62,7 62,11 @@ func genGetBase(api Api, op Operation, o
 	out <- "\tclient := &http.Client{}"
 	out <- fmt.Sprintf("\treq, err := http.NewRequest(%#v, url, nil)", op.Method)
 	addProduce(op, out)
-	out <- "\tresp, err := client.Do(req)"
+	if op.Type == "void" && len(op.ResponseMessages) == 0 {
+		out <- "\t_, err = client.Do(req)"
+	} else {
+		out <- "\tresp, err := client.Do(req)"
+	}
 	out <- "\tif err != nil {"
 	out <- fmt.Sprintf("\t\treturn %serr", zero)
 	out <- "\t}"

          
@@ 98,7 102,9 @@ func addProduce(op Operation, out chan s
 }
 
 func genGet(api Api, op Operation, out chan string) {
-	out <- fmt.Sprintf("\trv := %s", op.zero())
+	if op.Type != "void" {
+		out <- fmt.Sprintf("\trv := %s", op.zero())
+	}
 	genGetBase(api, op, out)
 	genDeserialize(op, out)
 }

          
M post.go +1 -1
@@ 82,7 82,7 @@ func genPostBase(api Api, op Operation, 
 	if op.Type != "void" {
 		genDeserialize(op, out)
 	} else {
-		out <- "return nil"
+		out <- "\treturn nil"
 	}
 }
 

          
M test/api-docs_pet.json => test/pet.json +0 -0

        
A => test/user.json +308 -0
@@ 0,0 1,308 @@ 
+{
+   "models" : {
+      "User" : {
+         "id" : "User",
+         "properties" : {
+            "firstName" : {
+               "type" : "string"
+            },
+            "phone" : {
+               "type" : "string"
+            },
+            "username" : {
+               "type" : "string"
+            },
+            "email" : {
+               "type" : "string"
+            },
+            "password" : {
+               "type" : "string"
+            },
+            "userStatus" : {
+               "format" : "int32",
+               "enum" : [
+                  "1-registered",
+                  "2-active",
+                  "3-closed"
+               ],
+               "type" : "integer",
+               "description" : "User Status"
+            },
+            "id" : {
+               "format" : "int64",
+               "type" : "integer"
+            },
+            "lastName" : {
+               "type" : "string"
+            }
+         }
+      }
+   },
+   "produces" : [
+      "application/json"
+   ],
+   "resourcePath" : "/user",
+   "apiVersion" : "1.0.0",
+   "swaggerVersion" : "1.2",
+   "apis" : [
+      {
+         "operations" : [
+            {
+               "parameters" : [
+                  {
+                     "required" : true,
+                     "paramType" : "body",
+                     "name" : "body",
+                     "allowMultiple" : false,
+                     "type" : "array",
+                     "description" : "List of user object",
+                     "items" : {
+                        "$ref" : "User"
+                     }
+                  }
+               ],
+               "authorizations" : {
+                  "oauth2" : [
+                     {
+                        "description" : "anything",
+                        "scope" : "test:anything"
+                     }
+                  ]
+               },
+               "nickname" : "createUsersWithArrayInput",
+               "summary" : "Creates list of users with given input array",
+               "notes" : "",
+               "type" : "void",
+               "method" : "POST"
+            }
+         ],
+         "path" : "/user/createWithArray"
+      },
+      {
+         "operations" : [
+            {
+               "parameters" : [
+                  {
+                     "required" : true,
+                     "paramType" : "body",
+                     "name" : "body",
+                     "allowMultiple" : false,
+                     "type" : "array",
+                     "description" : "List of user object",
+                     "items" : {
+                        "$ref" : "User"
+                     }
+                  }
+               ],
+               "authorizations" : {
+                  "oauth2" : [
+                     {
+                        "description" : "anything",
+                        "scope" : "test:anything"
+                     }
+                  ]
+               },
+               "nickname" : "createUsersWithListInput",
+               "summary" : "Creates list of users with given list input",
+               "notes" : "",
+               "type" : "void",
+               "method" : "POST"
+            }
+         ],
+         "path" : "/user/createWithList"
+      },
+      {
+         "operations" : [
+            {
+               "nickname" : "updateUser",
+               "responseMessages" : [
+                  {
+                     "message" : "Invalid username supplied",
+                     "code" : 400
+                  },
+                  {
+                     "message" : "User not found",
+                     "code" : 404
+                  }
+               ],
+               "authorizations" : {
+                  "oauth2" : [
+                     {
+                        "description" : "anything",
+                        "scope" : "test:anything"
+                     }
+                  ]
+               },
+               "parameters" : [
+                  {
+                     "required" : true,
+                     "paramType" : "path",
+                     "name" : "username",
+                     "allowMultiple" : false,
+                     "type" : "string",
+                     "description" : "name that need to be deleted"
+                  },
+                  {
+                     "required" : true,
+                     "paramType" : "body",
+                     "name" : "body",
+                     "allowMultiple" : false,
+                     "type" : "User",
+                     "description" : "Updated user object"
+                  }
+               ],
+               "summary" : "Updated user",
+               "notes" : "This can only be done by the logged in user.",
+               "method" : "PUT",
+               "type" : "void"
+            },
+            {
+               "nickname" : "deleteUser",
+               "responseMessages" : [
+                  {
+                     "message" : "Invalid username supplied",
+                     "code" : 400
+                  },
+                  {
+                     "message" : "User not found",
+                     "code" : 404
+                  }
+               ],
+               "authorizations" : {
+                  "oauth2" : [
+                     {
+                        "description" : "anything",
+                        "scope" : "test:anything"
+                     }
+                  ]
+               },
+               "parameters" : [
+                  {
+                     "required" : true,
+                     "paramType" : "path",
+                     "name" : "username",
+                     "allowMultiple" : false,
+                     "type" : "string",
+                     "description" : "The name that needs to be deleted"
+                  }
+               ],
+               "summary" : "Delete user",
+               "notes" : "This can only be done by the logged in user.",
+               "method" : "DELETE",
+               "type" : "void"
+            },
+            {
+               "nickname" : "getUserByName",
+               "responseMessages" : [
+                  {
+                     "message" : "Invalid username supplied",
+                     "code" : 400
+                  },
+                  {
+                     "message" : "User not found",
+                     "code" : 404
+                  }
+               ],
+               "authorizations" : {},
+               "parameters" : [
+                  {
+                     "required" : true,
+                     "paramType" : "path",
+                     "name" : "username",
+                     "allowMultiple" : false,
+                     "type" : "string",
+                     "description" : "The name that needs to be fetched. Use user1 for testing."
+                  }
+               ],
+               "summary" : "Get user by user name",
+               "notes" : "",
+               "method" : "GET",
+               "type" : "User"
+            }
+         ],
+         "path" : "/user/{username}"
+      },
+      {
+         "operations" : [
+            {
+               "nickname" : "loginUser",
+               "responseMessages" : [
+                  {
+                     "message" : "Invalid username and password combination",
+                     "code" : 400
+                  }
+               ],
+               "authorizations" : {},
+               "parameters" : [
+                  {
+                     "required" : true,
+                     "paramType" : "query",
+                     "name" : "username",
+                     "allowMultiple" : false,
+                     "type" : "string",
+                     "description" : "The user name for login"
+                  },
+                  {
+                     "required" : true,
+                     "paramType" : "query",
+                     "name" : "password",
+                     "allowMultiple" : false,
+                     "type" : "string",
+                     "description" : "The password for login in clear text"
+                  }
+               ],
+               "summary" : "Logs user into the system",
+               "notes" : "",
+               "method" : "GET",
+               "type" : "string"
+            }
+         ],
+         "path" : "/user/login"
+      },
+      {
+         "operations" : [
+            {
+               "parameters" : [],
+               "authorizations" : {},
+               "nickname" : "logoutUser",
+               "summary" : "Logs out current logged in user session",
+               "notes" : "",
+               "type" : "void",
+               "method" : "GET"
+            }
+         ],
+         "path" : "/user/logout"
+      },
+      {
+         "operations" : [
+            {
+               "parameters" : [
+                  {
+                     "required" : true,
+                     "paramType" : "body",
+                     "name" : "body",
+                     "allowMultiple" : false,
+                     "type" : "User",
+                     "description" : "Created user object"
+                  }
+               ],
+               "authorizations" : {
+                  "oauth2" : [
+                     {
+                        "description" : "anything",
+                        "scope" : "test:anything"
+                     }
+                  ]
+               },
+               "nickname" : "createUser",
+               "summary" : "Create user",
+               "notes" : "This can only be done by the logged in user.",
+               "type" : "void",
+               "method" : "POST"
+            }
+         ],
+         "path" : "/user"
+      }
+   ],
+   "basePath" : "http://petstore.swagger.wordnik.com/api"
+}