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"
+}