292911e75192 — Sean Russell 11 years ago
Implements DELETE and POST. Factors out some common code from GET for reuse in DELETE.  Adds license.
Bug fixes
Work-around for POST with both body and params.
gofmt
10 files changed, 268 insertions(+), 109 deletions(-)

A => LICENSE.txt
M README.md
M api.go
M cmd/sashay/main.go
A => delete.go
M generator.go
A => get.go
A => post.go
M swagger_test.go
M util.go
A => LICENSE.txt +27 -0
@@ 0,0 1,27 @@ 
+Copyright (c) 2014, Sean E. Russell
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.))")""""""

          
M README.md +5 -0
@@ 17,3 17,8 @@ Example
 
 will generate a Go library called "petstore" which can be imported and used to
 interact with the Swagger sample application.
+
+License
+-------
+
+This software is released under the BSD Simplified license.

          
M api.go +19 -13
@@ 73,7 73,7 @@ type ResponseMessage struct {
 
 type DataTypeFields struct {
 	Type         string
-	Ref          string     `json:"$ref"`
+	Ref          string `json:"$ref"`
 	Format       string
 	DefaultValue interface{}
 	Enum         []string

          
@@ 113,9 113,9 @@ func (p DataTypeFields) typeOf() string 
 	case "boolean":
 	case "array":
 		if p.Items["type"] != "" {
-			return "[]"+p.Items["type"]
+			return "[]" + p.Items["type"]
 		} else {
-			return "[]"+p.Items["$ref"]
+			return "[]" + p.Items["$ref"]
 		}
 	default:
 		if p.Ref != "" {

          
@@ 127,25 127,31 @@ func (p DataTypeFields) typeOf() string 
 
 func (p DataTypeFields) zero() string {
 	switch p.Type {
-	case "integer": return "0"
-	case "number": return "0.0"
+	case "integer":
+		return "0"
+	case "number":
+		return "0.0"
 	case "string":
 		switch p.Format {
-		default: return `""`
-		case "byte": return "[]byte{}"
-		case "date", "date-time": return "time.Time{}"
+		default:
+			return `""`
+		case "byte":
+			return "[]byte{}"
+		case "date", "date-time":
+			return "time.Time{}"
 		}
-	case "boolean": return "false"
+	case "boolean":
+		return "false"
 	case "array":
 		if p.Items["type"] != "" {
-			return "[]"+p.Items["type"]+"{}"
+			return "[]" + p.Items["type"] + "{}"
 		} else {
-			return "[]"+p.Items["$ref"]+"{}"
+			return "[]" + p.Items["$ref"] + "{}"
 		}
 	default:
 		if p.Ref != "" {
-			return p.Ref+"{}"
+			return p.Ref + "{}"
 		}
-		return p.Type+"{}"
+		return p.Type + "{}"
 	}
 }

          
M cmd/sashay/main.go +1 -1
@@ 1,9 1,9 @@ 
 package main
 
 import (
+	"bitbucket.org/seanerussell/sashay"
 	"fmt"
 	"io/ioutil"
-	"bitbucket.org/seanerussell/sashay"
 )
 
 func main() {

          
A => delete.go +5 -0
@@ 0,0 1,5 @@ 
+package sashay
+
+func genDelete(api Api, op Operation, out chan string) {
+	genGetBase(api, op, out)
+}

          
M generator.go +30 -83
@@ 16,17 16,20 @@ func Generate(pack string, j []byte, out
 	out <- fmt.Sprintf("package %s", pack)
 	out <- ""
 	out <- "import ("
+	out <- "\t\"bytes\""
 	out <- "\t\"encoding/json\""
 	out <- "\t\"errors\""
 	out <- "\t\"fmt\""
+	out <- "\t\"io\""
 	out <- "\t\"io/ioutil\""
-	out <- "\t\"io\""
 	out <- "\t\"net/http\""
 	out <- ")"
 	out <- ""
 	switch t.(type) {
-	case ApiDecl: generateApi(t.(ApiDecl), out)
-	case ResourceListing: generateResourceListing(t.(ResourceListing), out)
+	case ApiDecl:
+		generateApi(t.(ApiDecl), out)
+	case ResourceListing:
+		generateResourceListing(t.(ResourceListing), out)
 	}
 	return nil
 }

          
@@ 35,9 38,9 @@ func generateApi(a ApiDecl, out chan str
 	out <- fmt.Sprintf(`const basePath = "%s"`, a.BasePath)
 	out <- ""
 	generateModels(a, out)
-	np := len(a.Apis)-1
+	np := len(a.Apis) - 1
 	for i, api := range a.Apis {
-		no := len(api.Operations)-1
+		no := len(api.Operations) - 1
 		for j, op := range api.Operations {
 			generateDocs(op, out)
 			generateFunc(api, op, out)

          
@@ 66,35 69,37 @@ func generateModels(a ApiDecl, out chan 
 }
 
 func generateDocs(op Operation, out chan string) {
-	out <- "/*"
-	out <- op.Summary
-	out <- ""
+	out <- "/*******************************************************************************"
+	out <- " * " + op.Summary
+	out <- " *"
 	if op.Notes != "" {
-		out <- op.Notes
-		out <- ""
+		out <- " * " + op.Notes
+		out <- " *"
 	}
-	out <- "Parameters:"
+	out <- " * Parameters:"
 	for _, p := range op.Parameters {
-		out <- fmt.Sprintf("\t%s: %s", p.Name, p.Description)
+		out <- fmt.Sprintf(" *\t%s: %s", p.Name, p.Description)
 	}
-	out <- "*/"
+	out <- " */"
 }
 
 func generateFunc(api Api, op Operation, out chan string) {
 	fname := upcase(op.Nickname)
-	s := fmt.Sprintf("func %s(%s) (%serror) {", fname, genArgs(op), genRv(op))
+	s := fmt.Sprintf("func %s(%s) %s {", fname, genArgs(op), genRv(op))
 	out <- s
 	switch op.Method {
 	case "DELETE":
-		// FIXME
-		//genDelete(api.Path, op.Nickname, )
+		genDelete(api, op, out)
 		out <- "\treturn nil"
 	case "PATCH":
 		// FIXME
+		out <- "\t// IMPLEMENT PATCH, YOU SILLY GOOSE"
 		out <- "\treturn nil, nil"
 	case "POST":
-		out <- "\treturn nil"
+		genPost(api, op, out)
 	case "PUT":
+		// FIXME
+		out <- "\t// IMPLEMENT PUT, ALREADY.  SHEESH."
 		out <- "\treturn nil"
 	case "GET":
 		genGet(api, op, out)

          
@@ 102,65 107,6 @@ func generateFunc(api Api, op Operation,
 	out <- "}"
 }
 
-func genGet(api Api, op Operation, out chan string) {
-	out <- fmt.Sprintf("\trv := %s", op.zero())
-	format := fmt.Sprintf(`"%%s%s`, api.Path)
-	formatArgs := []string{"basePath"}
-	params := make(map[string]string)
-	for _, p := range op.Parameters {
-		if p.Minimum != "" {
-			out <- fmt.Sprintf("\tif %s < %s(%s) {", p.Name, p.typeOf(), p.Minimum)
-			out <- "\t\treturn rv, errors.New(fmt.Sprintf(\"invalid value (%d < "+p.Minimum+")\", "+p.Name+"))"
-			out <- "\t}"
-		}
-		if p.Maximum != "" {
-			out <- fmt.Sprintf("\tif %s > %s(%s) {", p.Name, p.typeOf(), p.Maximum)
-			out <- "\t\treturn rv, errors.New(fmt.Sprintf(\"invalid value (%d > "+p.Maximum+")\", "+p.Name+"))"
-			out <- "\t}"
-		}
-		match := fmt.Sprintf("{%s}", p.Name)
-		if strings.Contains(format, match) {
-			format = strings.Replace(format, match, "%v", 1)
-			formatArgs = append(formatArgs, p.Name)
-		} else {
-			v := p.Name
-			if p.Type == "array" {
-				v = genArrayToString(p)
-			}
-			params[p.Name] = v
-		}
-	}
-	if len(params) > 0 {
-		format += "?"
-		l := len(params)-1
-		c := 0
-		for n, v := range params {
-			format = format + n + "=%s"
-			formatArgs = append(formatArgs, v)
-			if c < l {
-				format += "&"
-			}
-		}
-	}
-	format += `"`
-	url := "\turl := fmt.Sprintf("+format
-	for _, v := range formatArgs {
-		url += ", "+v
-	}
-	url += ")"
-	out <- url
-	out <- "\tresp, err := http.Get(url)"
-	out <- "\tif err != nil {"
-	out <- fmt.Sprintf("\t\treturn %s, err", op.zero())
-	out <- "\t}"
-	for _, c := range op.ResponseMessages {
-		out <- fmt.Sprintf("\tif resp.StatusCode == %d {", c.Code)
-		out <- fmt.Sprintf("\t\treturn rv, errors.New(\"%s\")", c.Message)
-		out <- "\t}"
-	}
-	genDeserialize(op, out)
-}
-
 func genArrayToString(p Parameter) string {
 	rv := bytes.Buffer{}
 	xs := fmt.Sprintf("%ss", p.Name)

          
@@ 200,7 146,7 @@ func genDeserialize(op Operation, out ch
 
 func genArgs(op Operation) string {
 	b := bytes.Buffer{}
-	np := len(op.Parameters)-1
+	np := len(op.Parameters) - 1
 	for i, p := range op.Parameters {
 		b.WriteString(p.Name)
 		b.WriteString(" ")

          
@@ 213,21 159,22 @@ func genArgs(op Operation) string {
 }
 
 func genRv(op Operation) string {
+	t := op.Type
 	switch op.Type {
-	case "void": return ""
+	case "void":
+		return "error"
 	case "array":
 		if op.Items["$ref"] != "" {
-			return "[]"+op.Items["$ref"]+", "
+			t = "[]" + op.Items["$ref"]
 		} else if op.Items["type"] != "" {
-			return "[]"+op.Items["type"]+", "
+			t = "[]" + op.Items["type"]
 		} else {
 			panic("Implement me.")
 		}
 	}
-	return op.Type+", "
+	return fmt.Sprintf("(%s, error)", t)
 }
 
-
 func generateResourceListing(r ResourceListing, out chan string) {
 }
 

          
@@ 240,5 187,5 @@ func upcase(s string) string {
 	}
 	head := s[0:1]
 	tail := s[1:len(s)]
-	return strings.ToUpper(head)+tail
+	return strings.ToUpper(head) + tail
 }

          
A => get.go +77 -0
@@ 0,0 1,77 @@ 
+package sashay
+
+import (
+	"fmt"
+	"strings"
+)
+
+func genGetBase(api Api, op Operation, out chan string) {
+	rv := ""
+	zero := ""
+	if op.Type != "void" {
+		rv = "rv, "
+		zero = op.zero() + ", "
+	}
+	format := fmt.Sprintf(`"%%s%s`, api.Path)
+	formatArgs := []string{"basePath"}
+	params := make(map[string]string)
+	for _, p := range op.Parameters {
+		if p.Minimum != "" {
+			out <- fmt.Sprintf("\tif %s < %s(%s) {", p.Name, p.typeOf(), p.Minimum)
+			out <- fmt.Sprintf("\t\treturn %serrors.New(fmt.Sprintf(\"invalid value (%%d < %s)\", %s))", rv, p.Minimum, p.Name)
+			out <- "\t}"
+		}
+		if p.Maximum != "" {
+			out <- fmt.Sprintf("\tif %s > %s(%s) {", p.Name, p.typeOf(), p.Maximum)
+			out <- fmt.Sprintf("\t\treturn %serrors.New(fmt.Sprintf(\"invalid value (%%d > %s)\", %s))", rv, p.Maximum, p.Name)
+			out <- "\t}"
+		}
+		match := fmt.Sprintf("{%s}", p.Name)
+		if strings.Contains(format, match) {
+			format = strings.Replace(format, match, "%v", 1)
+			formatArgs = append(formatArgs, p.Name)
+		} else {
+			v := p.Name
+			if p.Type == "array" {
+				v = genArrayToString(p)
+			}
+			params[p.Name] = v
+		}
+	}
+	if len(params) > 0 {
+		format += "?"
+		l := len(params) - 1
+		c := 0
+		for n, v := range params {
+			format = format + n + "=%s"
+			formatArgs = append(formatArgs, v)
+			if c < l {
+				format += "&"
+			}
+		}
+	}
+	format += `"`
+	url := "\turl := fmt.Sprintf(" + format
+	for _, v := range formatArgs {
+		url += ", " + v
+	}
+	url += ")"
+	out <- url
+	out <- "\tclient := &http.Client{}"
+	out <- fmt.Sprintf("\treq, err := http.NewRequest(%#v, url, nil)", op.Method)
+	out <- "\tresp, err := client.Do(req)"
+	out <- "\tif err != nil {"
+	out <- fmt.Sprintf("\t\treturn %serr", zero)
+	out <- "\t}"
+	for _, c := range op.ResponseMessages {
+		out <- fmt.Sprintf("\tif resp.StatusCode == %d {", c.Code)
+		out <- fmt.Sprintf("\t\treturn %serrors.New(%#v)", rv, c.Message)
+		out <- "\t}"
+	}
+}
+
+func genGet(api Api, op Operation, out chan string) {
+	out <- fmt.Sprintf("\trv := %s", op.zero())
+	genGetBase(api, op, out)
+	genDeserialize(op, out)
+}

          
A => post.go +85 -0
@@ 0,0 1,85 @@ 
+package sashay
+
+import (
+	"fmt"
+	"strings"
+)
+
+func genPostBase(api Api, op Operation, out chan string) {
+	rv := ""
+	zero := ""
+	if op.Type != "void" {
+		rv = "rv, "
+		zero = op.zero() + ", "
+	}
+	format := fmt.Sprintf(`"%%s%s`, api.Path)
+	formatArgs := []string{"basePath"}
+	params := make(map[string]string)
+	hasBody := false
+	for _, p := range op.Parameters {
+		if p.Minimum != "" {
+			out <- fmt.Sprintf("\tif %s < %s(%s) {", p.Name, p.typeOf(), p.Minimum)
+			out <- fmt.Sprintf("\t\treturn %serrors.New(fmt.Sprintf(\"invalid value (%%d < %s)\", %s))", rv, p.Minimum, p.Name)
+			out <- "\t}"
+		}
+		if p.Maximum != "" {
+			out <- fmt.Sprintf("\tif %s > %s(%s) {", p.Name, p.typeOf(), p.Maximum)
+			out <- fmt.Sprintf("\t\treturn %serrors.New(fmt.Sprintf(\"invalid value (%%d > %s)\", %s))", rv, p.Maximum, p.Name)
+			out <- "\t}"
+		}
+		match := fmt.Sprintf("{%s}", p.Name)
+		if strings.Contains(format, match) {
+			format = strings.Replace(format, match, "%v", 1)
+			formatArgs = append(formatArgs, p.Name)
+		} else if p.ParamType == "body" {
+			hasBody = true
+			out <- fmt.Sprintf("\tb, e := json.Marshal(%s)", p.Name)
+			out <- "\tif e != nil {"
+			out <- "\t\treturn e"
+			out <- "\t}"
+		} else {
+			v := p.Name
+			if p.Type == "array" {
+				v = genArrayToString(p)
+			}
+			params[p.Name] = v
+		}
+	}
+	format += `"`
+	// FIXME for the case of body AND params
+	if len(params) > 0 && !hasBody {
+		out <- "\tm := make(map[string]interface{})"
+		for n, v := range params {
+			out <- fmt.Sprintf("\tm[%#v] = %s", n, v)
+		}
+		out <- "\tb, e := json.Marshal(m)"
+		out <- "\tif e != nil {"
+		out <- "\t\treturn e"
+		out <- "\t}"
+	}
+	url := "\turl := fmt.Sprintf(" + format
+	for _, v := range formatArgs {
+		url += ", " + v
+	}
+	url += ")"
+	out <- url
+	out <- "\tclient := &http.Client{}"
+	out <- fmt.Sprintf("\treq, err := http.NewRequest(%#v, url, bytes.NewReader(b))", op.Method)
+	out <- "\tresp, err := client.Do(req)"
+	out <- "\tif err != nil {"
+	out <- fmt.Sprintf("\t\treturn %serr", zero)
+	out <- "\t}"
+	for _, c := range op.ResponseMessages {
+		out <- fmt.Sprintf("\tif resp.StatusCode == %d {", c.Code)
+		out <- fmt.Sprintf("\t\treturn %serrors.New(%#v)", rv, c.Message)
+		out <- "\t}"
+	}
+	out <- "\tif resp.StatusCode >= 500 && resp.StatusCode < 600 {"
+	out <- fmt.Sprintf("\t\treturn %serrors.New(\"Internal server error\")", rv)
+	out <- "\t}"
+	out <- "\treturn nil"
+}
+
+func genPost(api Api, op Operation, out chan string) {
+	genPostBase(api, op, out)
+}

          
M swagger_test.go +8 -6
@@ 1,15 1,18 @@ 
-package sashay 
+package sashay
+
 import (
-	"testing"
+	"fmt"
+	. "gopkg.in/check.v1"
 	"io/ioutil"
-	"fmt"
 	"os"
-	. "gopkg.in/check.v1"
+	"testing"
 )
 
 // Boiler plate for check
 func Test(t *testing.T) { TestingT(t) }
-type MySuite struct {}
+
+type MySuite struct{}
+
 var _ = Suite(&MySuite{})
 
 var resourceListing, apiDocs []byte

          
@@ 73,6 76,5 @@ func (s *MySuite) TestParse(c *C) {
 func (s *MySuite) TestGen(c *C) {
 }
 
-
 // http://petstore.swagger.wordnik.com/api/api-docs
 // http://petstore.swagger.wordnik.com/api/api-docs/pet

          
M util.go +11 -6
@@ 9,16 9,19 @@ import (
 )
 
 const (
-	UNDEFINED = -1
+	UNDEFINED    = -1
 	RESOURCELIST = iota
 	API
 )
 
 func Parse(d []byte) (interface{}, error) {
 	switch t, e := GetType(d); {
-		case e != nil: return nil, e
-		case t == API: return ParseApi(d)
-		case t == RESOURCELIST: return ParseResource(d)
+	case e != nil:
+		return nil, e
+	case t == API:
+		return ParseApi(d)
+	case t == RESOURCELIST:
+		return ParseResource(d)
 	}
 	return nil, errors.New("unrecognized json format")
 }

          
@@ 29,8 32,10 @@ func GetType(d []byte) (int, error) {
 		return UNDEFINED, e
 	}
 	switch t.(type) {
-	case ApiDecl: return API, nil
-	case ResourceListing: return RESOURCELIST, nil
+	case ApiDecl:
+		return API, nil
+	case ResourceListing:
+		return RESOURCELIST, nil
 	}
 	return UNDEFINED, errors.New("json document not recognized")
 }