mirror of
https://github.com/rclone/rclone.git
synced 2024-11-28 11:25:18 +01:00
319 lines
7.9 KiB
Go
319 lines
7.9 KiB
Go
|
// +build codegen
|
||
|
|
||
|
package api
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
"text/template"
|
||
|
|
||
|
"github.com/aws/aws-sdk-go/private/util"
|
||
|
)
|
||
|
|
||
|
type Examples map[string][]Example
|
||
|
|
||
|
// ExamplesDefinition is the structural representation of the examples-1.json file
|
||
|
type ExamplesDefinition struct {
|
||
|
*API `json:"-"`
|
||
|
Examples Examples `json:"examples"`
|
||
|
}
|
||
|
|
||
|
// Example is a single entry within the examples-1.json file.
|
||
|
type Example struct {
|
||
|
API *API `json:"-"`
|
||
|
Operation *Operation `json:"-"`
|
||
|
OperationName string `json:"-"`
|
||
|
Index string `json:"-"`
|
||
|
Builder examplesBuilder `json:"-"`
|
||
|
VisitedErrors map[string]struct{} `json:"-"`
|
||
|
Title string `json:"title"`
|
||
|
Description string `json:"description"`
|
||
|
ID string `json:"id"`
|
||
|
Comments Comments `json:"comments"`
|
||
|
Input map[string]interface{} `json:"input"`
|
||
|
Output map[string]interface{} `json:"output"`
|
||
|
}
|
||
|
|
||
|
type Comments struct {
|
||
|
Input map[string]interface{} `json:"input"`
|
||
|
Output map[string]interface{} `json:"output"`
|
||
|
}
|
||
|
|
||
|
var exampleFuncMap = template.FuncMap{
|
||
|
"commentify": commentify,
|
||
|
"wrap": wrap,
|
||
|
"generateExampleInput": generateExampleInput,
|
||
|
"generateTypes": generateTypes,
|
||
|
}
|
||
|
|
||
|
var exampleCustomizations = map[string]template.FuncMap{}
|
||
|
|
||
|
var exampleTmpls = template.Must(template.New("example").Funcs(exampleFuncMap).Parse(`
|
||
|
{{ generateTypes . }}
|
||
|
{{ commentify (wrap .Title 80 false) }}
|
||
|
//
|
||
|
{{ commentify (wrap .Description 80 false) }}
|
||
|
func Example{{ .API.StructName }}_{{ .MethodName }}() {
|
||
|
svc := {{ .API.PackageName }}.New(session.New())
|
||
|
input := &{{ .Operation.InputRef.Shape.GoTypeWithPkgNameElem }} {
|
||
|
{{ generateExampleInput . -}}
|
||
|
}
|
||
|
|
||
|
result, err := svc.{{ .OperationName }}(input)
|
||
|
if err != nil {
|
||
|
if aerr, ok := err.(awserr.Error); ok {
|
||
|
switch aerr.Code() {
|
||
|
{{ range $_, $ref := .Operation.ErrorRefs -}}
|
||
|
{{ if not ($.HasVisitedError $ref) -}}
|
||
|
case {{ .API.PackageName }}.{{ $ref.Shape.ErrorCodeName }}:
|
||
|
fmt.Println({{ .API.PackageName }}.{{ $ref.Shape.ErrorCodeName }}, aerr.Error())
|
||
|
{{ end -}}
|
||
|
{{ end -}}
|
||
|
default:
|
||
|
fmt.Println(aerr.Error())
|
||
|
}
|
||
|
} else {
|
||
|
// Print the error, cast err to awserr.Error to get the Code and
|
||
|
// Message from an error.
|
||
|
fmt.Println(err.Error())
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
fmt.Println(result)
|
||
|
}
|
||
|
`))
|
||
|
|
||
|
// Names will return the name of the example. This will also be the name of the operation
|
||
|
// that is to be tested.
|
||
|
func (exs Examples) Names() []string {
|
||
|
names := make([]string, 0, len(exs))
|
||
|
for k := range exs {
|
||
|
names = append(names, k)
|
||
|
}
|
||
|
|
||
|
sort.Strings(names)
|
||
|
return names
|
||
|
}
|
||
|
|
||
|
func (exs Examples) GoCode() string {
|
||
|
buf := bytes.NewBuffer(nil)
|
||
|
for _, opName := range exs.Names() {
|
||
|
examples := exs[opName]
|
||
|
for _, ex := range examples {
|
||
|
buf.WriteString(util.GoFmt(ex.GoCode()))
|
||
|
buf.WriteString("\n")
|
||
|
}
|
||
|
}
|
||
|
return buf.String()
|
||
|
}
|
||
|
|
||
|
// ExampleCode will generate the example code for the given Example shape.
|
||
|
// TODO: Can delete
|
||
|
func (ex Example) GoCode() string {
|
||
|
var buf bytes.Buffer
|
||
|
m := exampleFuncMap
|
||
|
if fMap, ok := exampleCustomizations[ex.API.PackageName()]; ok {
|
||
|
m = fMap
|
||
|
}
|
||
|
tmpl := exampleTmpls.Funcs(m)
|
||
|
if err := tmpl.ExecuteTemplate(&buf, "example", &ex); err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
return strings.TrimSpace(buf.String())
|
||
|
}
|
||
|
|
||
|
func generateExampleInput(ex Example) string {
|
||
|
if ex.Operation.HasInput() {
|
||
|
return ex.Builder.BuildShape(&ex.Operation.InputRef, ex.Input, false)
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// generateTypes will generate no types for default examples, but customizations may
|
||
|
// require their own defined types.
|
||
|
func generateTypes(ex Example) string {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// correctType will cast the value to the correct type when printing the string.
|
||
|
// This is due to the json decoder choosing numbers to be floats, but the shape may
|
||
|
// actually be an int. To counter this, we pass the shape's type and properly do the
|
||
|
// casting here.
|
||
|
func correctType(memName string, t string, value interface{}) string {
|
||
|
if value == nil {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
v := ""
|
||
|
switch value.(type) {
|
||
|
case string:
|
||
|
v = value.(string)
|
||
|
case int:
|
||
|
v = fmt.Sprintf("%d", value.(int))
|
||
|
case float64:
|
||
|
if t == "integer" || t == "long" || t == "int64" {
|
||
|
v = fmt.Sprintf("%d", int(value.(float64)))
|
||
|
} else {
|
||
|
v = fmt.Sprintf("%f", value.(float64))
|
||
|
}
|
||
|
case bool:
|
||
|
v = fmt.Sprintf("%t", value.(bool))
|
||
|
}
|
||
|
|
||
|
return convertToCorrectType(memName, t, v)
|
||
|
}
|
||
|
|
||
|
func convertToCorrectType(memName, t, v string) string {
|
||
|
return fmt.Sprintf("%s: %s,\n", memName, getValue(t, v))
|
||
|
}
|
||
|
|
||
|
func getValue(t, v string) string {
|
||
|
if t[0] == '*' {
|
||
|
t = t[1:]
|
||
|
}
|
||
|
switch t {
|
||
|
case "string":
|
||
|
return fmt.Sprintf("aws.String(%q)", v)
|
||
|
case "integer", "long", "int64":
|
||
|
return fmt.Sprintf("aws.Int64(%s)", v)
|
||
|
case "float", "float64", "double":
|
||
|
return fmt.Sprintf("aws.Float64(%s)", v)
|
||
|
case "boolean":
|
||
|
return fmt.Sprintf("aws.Bool(%s)", v)
|
||
|
default:
|
||
|
panic("Unsupported type: " + t)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// AttachExamples will create a new ExamplesDefinition from the examples file
|
||
|
// and reference the API object.
|
||
|
func (a *API) AttachExamples(filename string) {
|
||
|
p := ExamplesDefinition{API: a}
|
||
|
|
||
|
f, err := os.Open(filename)
|
||
|
defer f.Close()
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
err = json.NewDecoder(f).Decode(&p)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
p.setup()
|
||
|
}
|
||
|
|
||
|
var examplesBuilderCustomizations = map[string]examplesBuilder{
|
||
|
"wafregional": wafregionalExamplesBuilder{},
|
||
|
}
|
||
|
|
||
|
func (p *ExamplesDefinition) setup() {
|
||
|
var builder examplesBuilder
|
||
|
ok := false
|
||
|
if builder, ok = examplesBuilderCustomizations[p.API.PackageName()]; !ok {
|
||
|
builder = defaultExamplesBuilder{}
|
||
|
}
|
||
|
|
||
|
keys := p.Examples.Names()
|
||
|
for _, n := range keys {
|
||
|
examples := p.Examples[n]
|
||
|
for i, e := range examples {
|
||
|
n = p.ExportableName(n)
|
||
|
e.OperationName = n
|
||
|
e.API = p.API
|
||
|
e.Index = fmt.Sprintf("shared%02d", i)
|
||
|
|
||
|
e.Builder = builder
|
||
|
|
||
|
e.VisitedErrors = map[string]struct{}{}
|
||
|
op := p.API.Operations[e.OperationName]
|
||
|
e.OperationName = p.ExportableName(e.OperationName)
|
||
|
e.Operation = op
|
||
|
p.Examples[n][i] = e
|
||
|
}
|
||
|
}
|
||
|
|
||
|
p.API.Examples = p.Examples
|
||
|
}
|
||
|
|
||
|
var exampleHeader = template.Must(template.New("exampleHeader").Parse(`
|
||
|
import (
|
||
|
{{ .Builder.Imports .API }}
|
||
|
)
|
||
|
|
||
|
var _ time.Duration
|
||
|
var _ strings.Reader
|
||
|
var _ aws.Config
|
||
|
|
||
|
func parseTime(layout, value string) *time.Time {
|
||
|
t, err := time.Parse(layout, value)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
return &t
|
||
|
}
|
||
|
|
||
|
`))
|
||
|
|
||
|
type exHeader struct {
|
||
|
Builder examplesBuilder
|
||
|
API *API
|
||
|
}
|
||
|
|
||
|
// ExamplesGoCode will return a code representation of the entry within the
|
||
|
// examples.json file.
|
||
|
func (a *API) ExamplesGoCode() string {
|
||
|
var buf bytes.Buffer
|
||
|
var builder examplesBuilder
|
||
|
ok := false
|
||
|
if builder, ok = examplesBuilderCustomizations[a.PackageName()]; !ok {
|
||
|
builder = defaultExamplesBuilder{}
|
||
|
}
|
||
|
|
||
|
if err := exampleHeader.ExecuteTemplate(&buf, "exampleHeader", &exHeader{builder, a}); err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
code := a.Examples.GoCode()
|
||
|
if len(code) == 0 {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
buf.WriteString(code)
|
||
|
return buf.String()
|
||
|
}
|
||
|
|
||
|
// TODO: In the operation docuentation where we list errors, this needs to be done
|
||
|
// there as well.
|
||
|
func (ex *Example) HasVisitedError(errRef *ShapeRef) bool {
|
||
|
errName := errRef.Shape.ErrorCodeName()
|
||
|
_, ok := ex.VisitedErrors[errName]
|
||
|
ex.VisitedErrors[errName] = struct{}{}
|
||
|
return ok
|
||
|
}
|
||
|
|
||
|
func parseTimeString(ref *ShapeRef, memName, v string) string {
|
||
|
if ref.Location == "header" {
|
||
|
return fmt.Sprintf("%s: parseTime(%q, %q),\n", memName, "Mon, 2 Jan 2006 15:04:05 GMT", v)
|
||
|
} else {
|
||
|
switch ref.API.Metadata.Protocol {
|
||
|
case "json", "rest-json":
|
||
|
return fmt.Sprintf("%s: parseTime(%q, %q),\n", memName, "2006-01-02T15:04:05Z", v)
|
||
|
case "rest-xml", "ec2", "query":
|
||
|
return fmt.Sprintf("%s: parseTime(%q, %q),\n", memName, "2006-01-02T15:04:05Z", v)
|
||
|
default:
|
||
|
panic("Unsupported time type: " + ref.API.Metadata.Protocol)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (ex *Example) MethodName() string {
|
||
|
return fmt.Sprintf("%s_%s", ex.OperationName, ex.Index)
|
||
|
}
|