Exemplo n.º 1
0
// addRestService adds Swagger service definitions for RESTful services
func addRestService(swag *swagger2.Swagger, midl *idl.Idl, svc *idl.Service) error {
	svcComments := strings.Join(svc.Comments, "\n")

	// sort methods by common paths
	pathmap := make(PathMap)
	for _, mth := range svc.Methods {
		// Read annotations
		op, err := rest.ReadOp(mth)
		if err != nil {
			return err
		}
		if !op.Hide {
			methmap, ok := pathmap[op.Path]
			if !ok {
				methmap = make(HttpMethodMap)
				pathmap[op.Path] = methmap
			}
			if _, ok := methmap[op.Method]; ok {
				return errors.New("The " + op.Method.String() + " method is defined multiple times for the same path of " + op.Path)
			}
			methmap[op.Method] = RestOperation{Annotation: op, IdlMethod: mth}
		}
	}

	// Loop through the paths
	for path, methmap := range pathmap {
		var p swagger2.PathItem

		for httpmethod, restop := range methmap {
			op := new(swagger2.Operation)
			switch httpmethod {
			case rest.GET:
				p.Get = op
			case rest.PUT:
				p.Put = op
			case rest.POST:
				p.Post = op
			case rest.DELETE:
				p.Delete = op
			case rest.OPTIONS:
				p.Options = op
			case rest.HEAD:
				p.Head = op
			case rest.PATCH:
				p.Patch = op
			}
			op.Tags = []string{svc.Name}
			mthComments := strings.Join(restop.IdlMethod.Comments, "\n")
			theseArgs := make([]string, 0)
			for _, s := range restop.IdlMethod.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
			op.Description = html.EscapeString(restop.IdlMethod.Returns.String()) + " " + restop.IdlMethod.Name + "(" + strings.Join(theseArgs, ", ") + ")"
			if mthComments != "" {
				op.Description += "\n\n" + mthComments
			}
			if svcComments != "" {
				op.Description += "\n\n" + svc.Name + ": " + svcComments
			}
			op.OperationId = svc.Name + "_" + restop.IdlMethod.Name
			op.Summary = svc.Name + "." + restop.IdlMethod.Name
			op.Deprecated = restop.Annotation.Deprecated

			// Add parameters
			op.Parameters = make([]swagger2.Parameter, 0)
			for _, fld := range restop.IdlMethod.Parameters {
				parm, err := rest.ReadParm(fld)
				if err != nil {
					return err
				}
				var p *swagger2.Parameter
				if parm.In == rest.BODY {
					p = fieldToBodyParm(midl, fld)
				} else {
					p = fieldToParm(midl, fld)
				}
				if parm.Name != "" {
					p.Name = parm.Name
				}
				p.In = strings.ToLower(parm.In.String())
				if parm.Format != rest.NONE {
					p.Format = strings.ToLower(parm.Format.String())
				}
				if parm.Required == true {
					p.Required = new(bool)
					*p.Required = parm.Required
				}
				op.Parameters = append(op.Parameters, *p)
			}

			// Add responses
			op.Responses = make(swagger2.Responses, 0)
			for statuscode, resp := range restop.Annotation.Responses {
				strcode := strconv.Itoa(statuscode)
				if statuscode <= 0 {
					strcode = "default"
				}
				desc := resp.Desc
				if desc == "" {
					desc = "Response of type " + html.EscapeString(resp.Type.String())
				}
				theResp := swagger2.Response{
					Description: desc,
					Schema:      returnsToSchema(midl, resp.Type),
					Headers:     make(swagger2.Headers),
				}
				// Headers
				// SWAGGER-BUG: Swagger-ui doesn't show response headers
				for hdrname, hdr := range resp.Headers {
					theHeader := swagger2.Header{
						Description: hdr.Desc,
						ItemsDef:    *typeToItems(midl, hdr.Type),
					}
					if hdr.Format != rest.NONE {
						theHeader.ItemsDef.CollectionFormat = strings.ToLower(hdr.Format.String())
					}
					theResp.Headers[hdrname] = theHeader
				}
				op.Responses[strcode] = theResp
			}
		}
		swag.Paths[path] = p
	}

	return nil
}
Exemplo n.º 2
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))
	}
}