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)