Exemplo n.º 1
1
func allStructs(pidl *idl.Idl) []*idl.Struct {
	s := make([]*idl.Struct, 0)
	s = append(s, pidl.Structs...)
	for _, i := range pidl.UniqueImports() {
		s = append(s, i.Structs...)
	}
	return s
}
Exemplo n.º 2
0
func allServices(pidl *idl.Idl) []*idl.Service {
	s := make([]*idl.Service, 0)
	s = append(s, pidl.Services...)
	for _, i := range pidl.UniqueImports() {
		s = append(s, i.Services...)
	}
	return s
}
Exemplo n.º 3
0
func allEnums(pidl *idl.Idl) []*idl.Enum {
	s := make([]*idl.Enum, 0)
	s = append(s, pidl.Enums...)
	for _, i := range pidl.UniqueImports() {
		s = append(s, i.Enums...)
	}
	return s
}
Exemplo n.º 4
0
func loadBabelFiles(args []string) (*idl.Idl, error) {
	// initialize map to track processed files
	processedFiles := make(map[string]bool)

	// create base IDL to aggregate into
	var midl idl.Idl
	midl.Init()
	midl.AddDefaultNamespace("babelrpc.io", "Foo/Bar")

	// load specified IDL files into it
	for _, infilePat := range args {
		infiles, err := filepath.Glob(infilePat)
		if err != nil {
			return nil, errors.New(fmt.Sprintf("Cannot glob files: %s\n", err))
		}
		if len(infiles) == 0 {
			log.Printf("Warning: No files match \"%s\"\n", infilePat)
		}
		for _, infile := range infiles {
			_, ok := processedFiles[infile]
			if ok {
				log.Printf("Already processed %s\n", infile)
			} else {
				processedFiles[infile] = true
				// fmt.Printf("%s:\n", infile)
				bidl, err := parser.ParseIdl(infile, "test")
				if err != nil {
					return nil, errors.New(fmt.Sprintf("Parsing error in %s: %s\n", infile, err))
				}

				midl.Imports = append(midl.Imports, bidl)
			}
		}
	}

	// validate combined babel
	err := midl.Validate("test")
	if err != nil {
		log.Printf("Warning: Combined IDL does not validate: %s\n", err)
	}

	return &midl, nil
}
Exemplo n.º 5
0
func typeToItems(pidl *idl.Idl, t *idl.Type) *swagger2.ItemsDef {
	it := new(swagger2.ItemsDef)
	it.Ref = ""
	if t.IsPrimitive() {
		it.Format = t.String()
		if t.IsInt() || t.IsByte() {
			it.Type = "integer"
			it.Format = "int32"
			if t.Name == "int64" {
				if swagInt && restful {
					// Swagger style int64
					it.Format = "int64"
				} else {
					// Babel style int64
					it.Type = "string"  // ??? Babel quotes large integers to avoid precision loss in JavaScript
					it.Format = "int64" // SWAGGER-CLARIFICATION: is format int64 legal with type string?
				}
			}
		} else if t.IsFloat() {
			it.Type = "number"
			it.Format = "float"
			if t.Name == "float64" {
				it.Format = "double"
			}
		} else if t.IsBool() {
			it.Type = "boolean"
			it.Format = ""
		} else if t.IsDatetime() {
			it.Type = "string"
			it.Format = "date-time"
		} else if t.IsDecimal() {
			it.Type = "string"
			it.Format = ""
		} else if t.IsString() || t.IsChar() {
			it.Type = "string"
			it.Format = ""
		}
	} else if t.IsBinary() {
		it.Type = "string"
		it.Format = "byte"
	} else if t.IsMap() {
		it.Type = "object"
		// hmmm....what to do if keytype is not string?
		// SWAGGER-CLARIFICATION: Does swagger require all key types to be strings?
		it.AdditionalProperties = typeToItems(pidl, t.ValueType)
	} else if t.IsList() {
		it.Type = "array"
		it.Format = ""
		it.Items = typeToItems(pidl, t.ValueType)
	} else if t.IsEnum(pidl) {
		// SWAGGER-BUG: Enums cannot be delared in a schema
		it.Type = "string"
		it.Format = ""
		it.Enum = make([]interface{}, 0)
		e := pidl.FindEnum(t.Name)
		if e != nil {
			for _, x := range e.Values {
				it.Enum = append(it.Enum, x.Name)
			}
		}
	} else {
		// user-defined, struct or enum
		it.Ref = "#/definitions/" + t.Name
	}
	return it
}
Exemplo n.º 6
0
// main entry point
func main() {
	/*
		// recover panicking parser
		defer func() {
			if r := recover(); r != nil {
				e, y := r.(*idl.Error)
				if y {
					fmt.Fprintln(os.Stderr, e.Error())
					os.Exit(e.Code)
				} else {
					fmt.Fprintf(os.Stderr, "Exiting due to fatal error:\n%s\n", r)
					os.Exit(1)
				}
			}
		}()
	*/

	var format string
	flag.StringVar(&format, "format", "json", "Specifies output format - can be json or yaml")

	var output string
	flag.StringVar(&output, "out", "", "Specifies the file to write to")

	var host string
	flag.StringVar(&host, "host", "localhost", "Specifies the host to include in the file, for example localhost:8080")

	var basePath string
	flag.StringVar(&basePath, "basepath", "/", "Specifies the base path to include in the file, for example /foo/bar")

	var title string
	flag.StringVar(&title, "title", "My Application", "Sets the application title")

	flag.BoolVar(&flatten, "flat", false, "Flatten composed objects into a single object definition")

	var version string
	flag.StringVar(&version, "version", "1.0", "Sets the API version")

	flag.BoolVar(&restful, "rest", false, "Process @rest annotations (resulting Swagger won't be able to invoke Babel services)")

	flag.BoolVar(&swagInt, "int64", false, "When -rest is enabled, format int64 Swagger-style instead of Babel-style")

	flag.BoolVar(&genErr, "error", false, "When -rest is enabled, still include the Babel error definition")

	flag.Parse()

	if format != "json" && format != "yaml" {
		fmt.Printf("-format must be json or yaml.\n")
		os.Exit(0)
	}

	if swagInt && !restful {
		fmt.Fprintf(os.Stderr, "Warning: Ignoring -int64 because -rest is not set.\n")
		swagInt = false
	}

	if len(flag.Args()) == 0 {
		fmt.Printf("Please specify files to process or -help for options.\n")
		os.Exit(0)
	}

	// initialize map to track processed files
	processedFiles := make(map[string]bool)

	// create base IDL to aggregate into
	var midl idl.Idl
	midl.Init()
	midl.AddDefaultNamespace("babelrpc.io", "Foo/Bar")

	// load specified IDL files into it
	for _, infilePat := range flag.Args() {
		infiles, err := filepath.Glob(infilePat)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Cannot glob files: %s\n", err)
			os.Exit(5)
		}
		if len(infiles) == 0 {
			fmt.Fprintf(os.Stderr, "Warning: No files match \"%s\"\n", infilePat)
		}
		for _, infile := range infiles {
			_, ok := processedFiles[infile]
			if ok {
				fmt.Fprintf(os.Stderr, "Already processed %s\n", infile)
			} else {
				processedFiles[infile] = true
				// fmt.Printf("%s:\n", infile)
				bidl, err := parser.ParseIdl(infile, "test")
				if err != nil {
					fmt.Fprintf(os.Stderr, "Parsing error in %s: %s\n", infile, err)
					os.Exit(6)
				}

				midl.Imports = append(midl.Imports, bidl)
			}
		}
	}

	// validate combined babel
	err := midl.Validate("test")
	if err != nil {
		fmt.Fprintf(os.Stderr, "Warning: Combined IDL does not validate: %s\n", err)
	}

	// convert to swagger

	var swag swagger2.Swagger
	swag.Swagger = "2.0"
	swag.Host = host
	swag.BasePath = basePath
	swag.Consumes = []string{"application/json"}
	swag.Produces = []string{"application/json"}
	swag.Tags = []swagger2.Tag{swagger2.Tag{Name: "babel"}}
	swag.Info.Title = title
	swag.Info.Version = version
	swag.Definitions = make(swagger2.Definitions, 0)

	// parse and attach error model (not included with REST unless -genErr is used)
	if !restful || genErr {
		errorB := Lookup("/error.json")
		errorS, err := swagger2.LoadJson(errorB)
		if err != nil {
			panic("Cannot parse embedded error file")
		}
		// attach error definitions
		for sx, sy := range errorS.Definitions {
			swag.Definitions[sx] = sy
		}
	}

	// Glob together file-level commebts
	sarr := make([]string, 0)
	for _, idl := range midl.Imports {
		if len(idl.Comments) > 0 {
			sarr = append(sarr, idl.Filename+":\n\n")
			sarr = append(sarr, idl.Comments...)
		}
	}
	swag.Info.Description = strings.Join(sarr, "\n\n")
	if swag.Info.Description != "" {
		swag.Info.Description += "\n\n"
	}
	gentxt := "Generated on " + time.Now().Format(time.RFC1123) + " via:\n\n\tbabel2swagger"
	if format != "json" {
		gentxt += " -format " + format
	}
	if flatten {
		gentxt += " -flat"
	}
	if restful {
		gentxt += " -rest"
		if swagInt {
			gentxt += " -int64"
		}
		if genErr {
			gentxt += " -error"
		}
	}
	if host != "localhost" {
		gentxt += " -host " + host
	}
	if basePath != "/" {
		gentxt += " -basepath " + basePath
	}
	if version != "1.0" {
		gentxt += " -version " + version
	}
	if title != "My Application" {
		gentxt += " -title \"" + title + "\""
	}
	for _, g := range flag.Args() {
		gentxt += " \"" + g + "\""
	}
	swag.Info.Description += gentxt

	// add structs to swagger definitions
	for _, st := range allStructs(&midl) {
		swag.Definitions[st.Name] = *structToSchema(&midl, st)
	}

	// add enums to swagger definitions
	// SWAGGER-BUG: swagger-ui doesn't like it this way - need expand them out individually as strings
	/*
		for _, en := range allEnums(&midl) {
			swag.Definitions[en.Name] = *enumToSchema(&midl, en)
		}
	*/

	// track whether we find any empty parameters
	hasEmptyParms := false

	// add service/methods to paths
	swag.Paths = make(swagger2.Paths, 0)
	for _, svc := range allServices(&midl) {
		if restful {
			err := addRestService(&swag, &midl, svc)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Error: Cannot generate REST service: %s\n", err)
				os.Exit(11)
			}
		} else {
			svcComments := strings.Join(svc.Comments, "\n")
			/*
				Seems like these belong somewhere else
				if svcComments != "" {
					swag.Info.Description += "\n" + svcComments
				}
			*/
			for _, mth := range svc.Methods {
				var p swagger2.PathItem
				p.Post = new(swagger2.Operation)
				p.Post.Tags = []string{svc.Name}
				mthComments := strings.Join(mth.Comments, "\n")
				theseArgs := make([]string, 0)
				for _, s := range mth.Parameters {
					// SWAGGER-BUG: Swagger should be HTML escaping this
					theseArgs = append(theseArgs, html.EscapeString(s.Type.String())+" "+s.Name)
				}
				// SWAGGER-BUG: Swagger should be HTML escaping this
				p.Post.Description = html.EscapeString(mth.Returns.String()) + " " + mth.Name + "(" + strings.Join(theseArgs, ", ") + ")"
				if mthComments != "" {
					p.Post.Description += "\n\n" + mthComments
				}
				if svcComments != "" {
					p.Post.Description += "\n\n" + svc.Name + ": " + svcComments
				}
				p.Post.OperationId = svc.Name + "_" + mth.Name
				p.Post.Summary = svc.Name + "." + mth.Name
				p.Post.Parameters = make([]swagger2.Parameter, 0)
				var parm swagger2.Parameter
				parm.Name = "request"
				parm.In = "body"
				parm.Required = new(bool)
				if mth.HasParameters() {
					oname := strings.Title(svc.Name) + strings.Title(mth.Name) + "RequestArgs"
					swag.Definitions[oname] = *parmsToSchema(&midl, mth)
					parm.Schema = &swagger2.Schema{ItemsDef: swagger2.ItemsDef{Ref: "#/definitions/" + oname}}
					parm.Description = "Request definition"
					*parm.Required = true
				} else {
					parm.Schema = &swagger2.Schema{ItemsDef: swagger2.ItemsDef{Ref: "#/definitions/EmptyRequestArgs"}}
					hasEmptyParms = true
					*parm.Required = false
				}
				p.Post.Parameters = append(p.Post.Parameters, parm)
				p.Post.Responses = make(swagger2.Responses, 0)
				// SWAGGER-BUG: note that swagger-ui does not show primitive types for responses, even though they are allowed.
				p.Post.Responses["200"] = swagger2.Response{
					Description: "Response of type " + html.EscapeString(mth.Returns.String()),
					Schema:      returnsToSchema(&midl, mth.Returns),
				}
				p.Post.Responses["default"] = swagger2.Response{
					Description: "error",
					Schema:      &swagger2.Schema{ItemsDef: swagger2.ItemsDef{Ref: "#/definitions/ServiceError"}},
				}
				swag.Paths["/"+svc.Name+"/"+mth.Name] = p
			}
		}
	}

	// Add empty parameter definition if needed
	if hasEmptyParms {
		swag.Definitions["EmptyRequestArgs"] = swagger2.Schema{ItemsDef: swagger2.ItemsDef{Type: "object"}, Description: "Empty object"}
	}

	// Validate swagger
	errs := swag.Validate()
	if len(errs) > 0 {
		fmt.Fprintf(os.Stderr, "Warning: Swagger does not validate\n%s\n", swagger2.ErrorList(errs).Indent("\t"))
	}

	// open output
	var outfile io.WriteCloser
	outfile = os.Stdout
	if output != "" {
		outfile, err = os.Create(output)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error: Cannot open output file %s\n", output)
			os.Exit(9)
		}
		defer outfile.Close()
	}

	// Write output
	if format == "json" {
		s, err := swag.Json()
		if err != nil {
			fmt.Fprintf(os.Stderr, "Cannot convert to JSON: %s\n", err)
			os.Exit(7)
		}
		fmt.Fprintln(outfile, string(s))
	} else {
		s, err := swag.Yaml()
		if err != nil {
			fmt.Fprintf(os.Stderr, "Cannot convert to YAML: %s\n", err)
			os.Exit(8)
		}
		fmt.Fprintln(outfile, string(s))
	}
}