f5f323b10a8a — Sean Russell 11 years ago
Documentation; a bug fix for constant deduplication; generate import collection rather than individual imports
13 files changed, 232 insertions(+), 34 deletions(-)

M api.go
M cmd/sashay/main.go
M delete.go
A => doc.go
M generator.go
A => generator_test.go
M get.go
M patch.go
M post.go
M put.go
M resource_listing.go
M swagger_test.go
M util.go
M api.go +10 -0
@@ 1,3 1,11 @@ 
+// Copyright 2014 Sean E. Russell. All rights reserved.
+// Use of this source code is governed by a BSD-Simple
+// license that can be found in the LICENSE.txt file.
+
+/* Swagger API model
+ * https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#52-api-declaration
+ * Author: Sean E. Russell
+ */
 package sashay
 
 type ApiDecl struct {

          
@@ 83,6 91,7 @@ type DataTypeFields struct {
 	UniqueItems  bool
 }
 
+// Swagger -> Go type conversion
 func (p DataTypeFields) typeOf() string {
 	switch p.Type {
 	case "File":

          
@@ 125,6 134,7 @@ func (p DataTypeFields) typeOf() string 
 	return p.Type
 }
 
+// Zero values for Swagger types
 func (p DataTypeFields) zero() string {
 	switch p.Type {
 	case "integer":

          
M cmd/sashay/main.go +7 -7
@@ 15,17 15,17 @@ func main() {
 	pack := flag.String("p", "", "API package")
 	flag.Parse()
 	if *pack == "" {
-		fmt.Printf("ERROR: package must be provided")
+		fmt.Println("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")
+		fmt.Println("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")
+		fmt.Println("ERROR: only one API specification can be provided")
 		flag.Usage()
 		os.Exit(1)
 	}

          
@@ 34,19 34,19 @@ func main() {
 	if *file != "" {
 		b, e = ioutil.ReadFile(flag.Args()[0])
 		if e != nil {
-			fmt.Printf("error reading %s: %s", *file, e.Error())
+			fmt.Printf("error reading %s: %s\n", *file, e.Error())
 			os.Exit(1)
 		}
 	} else {
 		r, e := http.Get(*url)
 		if e != nil {
-			fmt.Printf("error fetching %s: %s", *url, e.Error())
+			fmt.Printf("error fetching %s: %s\n", *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())
+			fmt.Printf("error reading body of %s: %s\n", *url, e.Error())
 			os.Exit(1)
 		}
 	}

          
@@ 60,7 60,7 @@ func main() {
 	}()
 	t, e := sashay.GetType(b);
 	if e != nil {
-		fmt.Printf("error getting Swagger type of content (%s):\n%s", e.Error(), b)
+		fmt.Printf("error getting Swagger type of content (%s):\n%s\n", e.Error(), b)
 	}
 	if t == sashay.API {
 		sashay.GenerateApi(*pack, b, c)

          
M delete.go +7 -0
@@ 1,3 1,10 @@ 
+// Copyright 2014 Sean E. Russell. All rights reserved.
+// Use of this source code is governed by a BSD-Simple
+// license that can be found in the LICENSE.txt file.
+
+/* Generates DELETE function code
+ * Author: Sean E. Russell
+ */
 package sashay
 
 func genDelete(api Api, op Operation, out chan string) {

          
A => doc.go +35 -0
@@ 0,0 1,35 @@ 
+// Copyright 2014 Sean E. Russell. All rights reserved.
+// Use of this source code is governed by a BSD-Simple
+// license that can be found in the LICENSE.txt file.
+
+/*
+ * Sashay is a library and command for turning Swagger JSON into Go code.
+ *
+ * Installation:
+ *    go get bitbucket.org/seanerussell/sashay/cmd/sashay
+ *
+ * Usage:
+ *    sashay <-f> <-u> <-p>
+ *      -f="": API specification JSON file
+ *      -p="": API package
+ *      -u="": API specification URL
+ *    Either -f or -u must be provided, and only one can be. -p must be provided.
+ *
+ * Example:
+ *    go run cmd/sashay/main.go -u http://petstore.swagger.wordnik.com/api/api-docs -p petstore > petstore.go
+ *
+ * Then:
+ *    package main
+ *
+ *    import "petstore"
+ *
+ *    func main() {
+ *      pet, err := petstore.GetPetById(33)
+ *      if err != nil {
+ *        log.Println("error fetching pet:", err.Error())
+ *        return
+ *      }
+ *      log.Println("Pet's name is", pet.Name)
+ *    }
+ */
+package sashay

          
M generator.go +74 -15
@@ 1,3 1,10 @@ 
+// Copyright 2014 Sean E. Russell. All rights reserved.
+// Use of this source code is governed by a BSD-Simple
+// license that can be found in the LICENSE.txt file.
+
+/* Top-level generation functions
+ * Author: Sean E. Russell
+ */
 package sashay
 
 import (

          
@@ 11,14 18,22 @@ import (
 	"sync"
 )
 
-var swaggerToGo map[string]string
-
+/* Generates library functions for interacting with a RESTful interface from a
+ * JSON Swagger specification file.
+ * 
+ * Arguments:
+ *   pack: the package name to use for the generated code
+ *   j: JSON Swagger API specification (string)
+ *   out: generated code are written line-by-line to this channel
+ * Returns:
+ *   error: in case of a spec parse error
+ */
 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)
+	outWrapper, wait := makeImportHandler(pack, out)
 	if e != nil {
 		return e
 	}

          
@@ 28,12 43,28 @@ func GenerateApi(pack string, j []byte, 
 	return nil
 }
 
+/* Generates a library for a RESTful application interface from a Swagger
+ * Resource Listing specification provided as a JSON byte array.
+ * 
+ * This function will recursively fetch the API definitions for the resources
+ * listed in the Resource Listing from baseUri, and will produce a single file
+ * containing all of the structures and functions for accessing those
+ * resources.
+ * 
+ * Arguments:
+ *   baseUri: The URL from whence the resource API specs can be pulled
+ *   pack: the package name to use for the generated code
+ *   j: JSON Swagger resource listing specification (string)
+ *   out: generated code are written line-by-line to this channel
+ * Returns:
+ *   error: in case of a spec parse error
+ */
 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)
+	outWrapper, wait := makeImportHandler(pack, out)
 	if e != nil {
 		return e
 	}

          
@@ 43,6 74,8 @@ func GenerateResourceListing(baseUri, pa
 	return nil
 }
 
+// Recursively fetch each resource API and generate the code for accessing that
+// resource
 func generateResourceListing(baseUri string, r ResourceListing, out chan string) error {
 	l := len(r.Apis)-1
 	for i, a := range r.Apis {

          
@@ 68,9 101,13 @@ func generateResourceListing(baseUri str
 	return nil
 }
 
-func makeImportHandler(pack string, out chan string) (outWrapper chan string, wait *sync.WaitGroup, err error) {
-	outWrapper = make(chan string)
-	wait = &sync.WaitGroup{}
+// A utility function for collating and deduplicating imports and constants.
+// The calling function should use the returned value for writing, and must
+// close the channel when done writing. It must then wait on the returned
+// WaitGroup before exiting.
+func makeImportHandler(pack string, out chan string) (chan string, *sync.WaitGroup) {
+	outWrapper := make(chan string)
+	wait := &sync.WaitGroup{}
 	wait.Add(1)
 	go func() {
 		basePath := ""

          
@@ 82,8 119,8 @@ func makeImportHandler(pack string, out 
 			if strings.HasPrefix(line, "import ") {
 				imports.Set(line, true)
 			} else if strings.HasPrefix(line, "const basePath") {
-				if basePath != line {
-					err = errors.New("inconsistent basePaths")
+				if basePath != "" && basePath != line {
+					panic(errors.New("inconsistent basePaths "+basePath+" "+line))
 				}
 				basePath = line
 			} else {

          
@@ 92,9 129,11 @@ func makeImportHandler(pack string, out 
 		}
 		out <- fmt.Sprintf("package %s", pack)
 		out <- ""
+		out <- "import ("
 		for f := imports.Front(); f != nil; f = f.Next() {
-			out <- f.Key().(string)
+			out <- strings.Replace(f.Key().(string), "import ", "\t", 1)
 		}
+		out <- ")"
 		out <- ""
 		out <- basePath
 		out <- ""

          
@@ 103,9 142,11 @@ func makeImportHandler(pack string, out 
 		}
 		wait.Done()
 	}()
-	return outWrapper, wait, nil
+	return outWrapper, wait
 }
 
+// Generates the code from a Swagger API definition, writing lines to the
+// supplied out channel.
 func generateApi(a ApiDecl, out chan string) {
 	out <- fmt.Sprintf(`const basePath = "%s"`, a.BasePath)
 	out <- ""

          
@@ 126,9 167,10 @@ func generateApi(a ApiDecl, out chan str
 	}
 }
 
+// Generates the Go structs from an API definition
 func generateModels(a ApiDecl, out chan string) {
 	for _, m := range a.Models {
-		out <- fmt.Sprintf("type %s struct {", upcase(m.Id))
+		out <- fmt.Sprintf("type %s struct {", capitalize(m.Id))
 		for pk, pv := range m.Properties {
 			if pv.Description != "" {
 				out <- fmt.Sprintf("\t// %s", pv.Description)

          
@@ 138,13 180,14 @@ func generateModels(a ApiDecl, out chan 
 			} else if strings.HasPrefix(pv.typeOf(), "*os.") {
 				out <- "import \"os\""
 			}
-			out <- fmt.Sprintf("\t%s %s", upcase(pk), pv.typeOf())
+			out <- fmt.Sprintf("\t%s %s", capitalize(pk), pv.typeOf())
 		}
 		out <- "}"
 		out <- ""
 	}
 }
 
+// Generates function documentation for a function calling a REST service
 func generateDocs(op Operation, out chan string) {
 	out <- "/*******************************************************************************"
 	out <- " * " + op.Summary

          
@@ 160,8 203,9 @@ func generateDocs(op Operation, out chan
 	out <- " */"
 }
 
+// Generates the function for calling a REST service
 func generateFunc(api Api, op Operation, out chan string) {
-	fname := upcase(op.Nickname)
+	fname := capitalize(op.Nickname)
 	s := fmt.Sprintf("func %s(%s) %s {", fname, genArgs(op, out), genRv(op))
 	out <- s
 	switch op.Method {

          
@@ 180,6 224,14 @@ func generateFunc(api Api, op Operation,
 	out <- "}"
 }
 
+// Utility function generating code turning an array of values into an HTTP
+// parameter string, with values separated by commas.  E.g., the generated code
+// will convert:
+//      [ 1, "sean", 2.3, false ] 
+// to
+//      1,sean,2.3,false
+// The purpose is to generate the string for multivalues sent as parameter
+// arguments.
 func genArrayToString(p Parameter, out chan string) string {
 	rv := bytes.Buffer{}
 	xs := fmt.Sprintf("%ss", p.Name)

          
@@ 192,6 244,7 @@ func genArrayToString(p Parameter, out c
 	return string(rv.Bytes())
 }
 
+// Generates the code to deserialize a REST return value into a struct.
 func genDeserialize(op Operation, out chan string) {
 	if op.Type == "void" {
 		out <- "\treturn nil"

          
@@ 227,6 280,8 @@ func genDeserialize(op Operation, out ch
 	}
 }
 
+// Utility function for handling XML or JSON encodings for in and out variables
+// to REST calls.  Is also responsible for importing the necessary Go packages.
 func encoding(items []string, format string, out chan string) {
 	hasJson := false
 	hasXml := false

          
@@ 252,6 307,8 @@ func encoding(items []string, format str
 	}
 }
 
+// Generates the code for function arguments, and imports the required Go
+// packages.
 func genArgs(op Operation, out chan string) string {
 	b := bytes.Buffer{}
 	np := len(op.Parameters) - 1

          
@@ 271,6 328,7 @@ func genArgs(op Operation, out chan stri
 	return string(b.Bytes())
 }
 
+// Generates the code for function return values
 func genRv(op Operation) string {
 	t := op.Type
 	switch op.Type {

          
@@ 288,7 346,8 @@ func genRv(op Operation) string {
 	return fmt.Sprintf("(%s, error)", t)
 }
 
-func upcase(s string) string {
+// Utility function for capitalizing strings
+func capitalize(s string) string {
 	if len(s) == 0 {
 		return s
 	}

          
A => generator_test.go +34 -0
@@ 0,0 1,34 @@ 
+package sashay
+
+import (
+	. "gopkg.in/check.v1"
+	"fmt"
+)
+
+func ExampleGenerateApi() {
+	json := []byte(`{ "apiVersion": "1.0.0", "swaggerVersion": "1.2", "basePath": "http://petstore.swagger.wordnik.com/api", "resourcePath": "/pet", "apis": [ { "path": "/pet/{petId}", "operations": [ { "method": "GET", "summary": "Find pet by ID", "notes": "Returns a pet based on ID", "type": "Pet", "nickname": "getPetById", "authorizations": {}, "parameters": [ { "name": "petId", "description": "ID of pet that needs to be fetched", "required": true, "type": "integer", "format": "int64", "paramType": "path" } ], "responseMessages": [ { "code": 400, "message": "Invalid ID supplied" } ] } ] } ] } }`)
+	out := make(chan string)
+	go func() {
+		for l := range out {
+			fmt.Println(l)
+		}
+	}()
+	e := GenerateApi("test", json, out)
+	if e != nil {
+		fmt.Println(e)
+	}
+}
+
+func ExampleGenerateResourceListing() {
+	resourceJson := []byte(`{ "info" : { "contact" : "foo@bar.edu", "title" : "Swagger Sample App", "license" : "BSD Simple", "description" : "This is a sample server" }, "apiVersion" : "1.0.0", "swaggerVersion" : "1.2", "apis" : [ { "path" : "/pet", "description" : "Operations about pets" }, ] }`)
+	out := make(chan string)
+	go func() {
+		for l := range out {
+			fmt.Println(l)
+		}
+	}()
+	e := GenerateResourceListing("http://localhost/api", "test", json, out)
+	if e != nil {
+		fmt.Println(e)
+	}
+}

          
M get.go +10 -0
@@ 1,3 1,10 @@ 
+// Copyright 2014 Sean E. Russell. All rights reserved.
+// Use of this source code is governed by a BSD-Simple
+// license that can be found in the LICENSE.txt file.
+
+/* Code generator functions for GET requests
+ * Author: Sean E. Russell
+ */
 package sashay
 
 import (

          
@@ 5,6 12,7 @@ import (
 	"strings"
 )
 
+// Factored out common code for generating GET requests
 func genGetBase(api Api, op Operation, out chan string) {
 	rv := ""
 	zero := ""

          
@@ 78,6 86,7 @@ func genGetBase(api Api, op Operation, o
 	}
 }
 
+// Generates code for setting the HTTP request Accept-Type header
 func addProduce(op Operation, out chan string) {
 	hasJson := false
 	hasXml := false

          
@@ 101,6 110,7 @@ func addProduce(op Operation, out chan s
 	}
 }
 
+// Wrapper for generating a GET request
 func genGet(api Api, op Operation, out chan string) {
 	if op.Type != "void" {
 		out <- fmt.Sprintf("\trv := %s", op.zero())

          
M patch.go +10 -0
@@ 1,3 1,13 @@ 
+// Copyright 2014 Sean E. Russell. All rights reserved.
+// Use of this source code is governed by a BSD-Simple
+// license that can be found in the LICENSE.txt file.
+
+/* Code generator functions for PATCH requests 
+ * *Note* that this does not implement http://tools.ietf.org/html/rfc5789 or
+ * (especially) any of http://tools.ietf.org/html/draft-pbryan-json-patch-04.
+ * But that's OK, because neither does go-restful.
+ * Author: Sean E. Russell
+ */
 package sashay
 
 func genPatch(api Api, op Operation, out chan string) {

          
M post.go +7 -0
@@ 1,3 1,10 @@ 
+// Copyright 2014 Sean E. Russell. All rights reserved.
+// Use of this source code is governed by a BSD-Simple
+// license that can be found in the LICENSE.txt file.
+
+/* Code generator functions for POST requests 
+ * Author: Sean E. Russell
+ */
 package sashay
 
 import (

          
M put.go +7 -0
@@ 1,3 1,10 @@ 
+// Copyright 2014 Sean E. Russell. All rights reserved.
+// Use of this source code is governed by a BSD-Simple
+// license that can be found in the LICENSE.txt file.
+
+/* Code generator functions for PUT requests 
+ * Author: Sean E. Russell
+ */
 package sashay
 
 func genPut(api Api, op Operation, out chan string) {

          
M resource_listing.go +8 -0
@@ 1,3 1,11 @@ 
+// Copyright 2014 Sean E. Russell. All rights reserved.
+// Use of this source code is governed by a BSD-Simple
+// license that can be found in the LICENSE.txt file.
+
+/* Swagger Resource Listing model
+ * https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#51-resource-listing
+ * Author: Sean E. Russell
+ */
 package sashay
 
 type ResourceListing struct {

          
M swagger_test.go +0 -6
@@ 72,9 72,3 @@ func (s *MySuite) TestParse(c *C) {
 	c.Assert(e, IsNil)
 	c.Assert(t, FitsTypeOf, ResourceListing{})
 }
-
-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 +23 -6
@@ 1,6 1,10 @@ 
-/*
-The sashay package provides primitives for turning Swagger JSON into Go code
-*/
+// Copyright 2014 Sean E. Russell. All rights reserved.
+// Use of this source code is governed by a BSD-Simple
+// license that can be found in the LICENSE.txt file.
+
+/* Sashay utility functions
+ * Author: Sean E. Russell
+ */
 package sashay
 
 import (

          
@@ 8,12 12,17 @@ import (
 	"errors"
 )
 
+// Swagger specification types
 const (
-	UNDEFINED    = -1
-	RESOURCELIST = iota
-	API
+	UNDEFINED    = -1       // Error -- unrecognized type
+	RESOURCELIST = iota     // Resource List
+	API                     // API specification
 )
 
+// Identify the type of Swagger specification (resource listing or API), parse
+// it into a struct, and return the results.  The type will be an ApiDecl, a
+// ResourceListing, or an error if the type can't be identified or there is
+// some other error in parsing.
 func Parse(d []byte) (interface{}, error) {
 	switch t, e := GetType(d); {
 	case e != nil:

          
@@ 26,6 35,8 @@ func Parse(d []byte) (interface{}, error
 	return nil, errors.New("unrecognized json format")
 }
 
+// Utility function for identifying the type of Swagger specification.  Will
+// return one a constant, one of UNDEFINED, API, or RESOURCELIST
 func GetType(d []byte) (int, error) {
 	t, e := getStruct(d)
 	if e != nil {

          
@@ 40,6 51,8 @@ func GetType(d []byte) (int, error) {
 	return UNDEFINED, errors.New("json document not recognized")
 }
 
+// Utility for identifying and unmarshalling content into a struct.  Factored
+// out common code.
 func getStruct(d []byte) (interface{}, error) {
 	var listing ResourceListing
 	e := json.Unmarshal(d, &listing)

          
@@ 60,12 73,16 @@ func getStruct(d []byte) (interface{}, e
 	return nil, errors.New("json document not recognized")
 }
 
+// Direct-to-parse function for demarshalling a JSON spec; for use when the
+// type of spec is already known to be a resource listing.  Most efficient.
 func ParseResource(j []byte) (ResourceListing, error) {
 	var r ResourceListing
 	err := json.Unmarshal(j, &r)
 	return r, err
 }
 
+// Direct-to-parse function for demarshalling a JSON spec; for use when the
+// type of spec is already known to be an API spec.  Most efficient.
 func ParseApi(j []byte) (ApiDecl, error) {
 	var r ApiDecl
 	err := json.Unmarshal(j, &r)