Files
services/cmd/clients/main.go
2021-09-22 11:55:04 +01:00

863 lines
24 KiB
Go

package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"text/template"
"github.com/Masterminds/semver/v3"
"github.com/fatih/camelcase"
"github.com/getkin/kin-openapi/openapi3"
"github.com/stoewer/go-strcase"
)
type service struct {
Spec *openapi3.Swagger
Name string
}
type example struct {
Title string `json:"title"`
Description string `json:"description"`
Request map[string]interface{}
Response map[string]interface{}
}
func main() {
files, err := ioutil.ReadDir(os.Args[1])
if err != nil {
log.Fatal(err)
}
workDir, _ := os.Getwd()
tsPath := filepath.Join(workDir, "clients", "ts")
err = os.MkdirAll(tsPath, 0777)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
goPath := filepath.Join(workDir, "clients", "go")
err = os.MkdirAll(goPath, 0777)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
examplesPath := filepath.Join(workDir, "examples")
err = os.MkdirAll(goPath, 0777)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
funcs := map[string]interface{}{
"recursiveTypeDefinition": func(language, serviceName, typeName string, schemas map[string]*openapi3.SchemaRef) string {
return schemaToType(language, serviceName, typeName, schemas)
},
"requestTypeToEndpointName": func(requestType string) string {
parts := camelcase.Split(requestType)
return strings.Join(parts[1:len(parts)-1], "")
},
// strips service name from the request type
"requestType": func(requestType string) string {
parts := camelcase.Split(requestType)
return strings.Join(parts[1:], "")
},
"requestTypeToResponseType": func(requestType string) string {
parts := camelcase.Split(requestType)
return strings.Join(parts[1:len(parts)-1], "") + "Response"
},
"endpointComment": func(endpoint string, schemas map[string]*openapi3.SchemaRef) string {
v := schemas[strings.Title(endpoint)+"Request"]
if v == nil {
panic("can't find " + strings.Title(endpoint) + "Request")
}
if v.Value == nil {
return ""
}
comm := v.Value.Description
ret := ""
for _, line := range strings.Split(comm, "\n") {
ret += "// " + strings.TrimSpace(line) + "\n"
}
return ret
},
"requestTypeToEndpointPath": func(requestType string) string {
parts := camelcase.Split(requestType)
return strings.Title(strings.Join(parts[1:len(parts)-1], ""))
},
"title": strings.Title,
"untitle": func(t string) string {
return strcase.LowerCamelCase(t)
},
"goExampleRequest": func(serviceName, endpoint string, schemas map[string]*openapi3.SchemaRef, exampleJSON map[string]interface{}) string {
return schemaToGoExample(serviceName, strings.Title(endpoint)+"Request", schemas, exampleJSON)
},
"tsExampleRequest": func(serviceName, endpoint string, schemas map[string]*openapi3.SchemaRef, exampleJSON map[string]interface{}) string {
bs, _ := json.MarshalIndent(exampleJSON, "", " ")
return string(bs)
},
}
services := []service{}
tsExportsMap := map[string]string{}
for _, f := range files {
if strings.Contains(f.Name(), "clients") || strings.Contains(f.Name(), "examples") {
continue
}
if f.IsDir() && !strings.HasPrefix(f.Name(), ".") {
serviceName := f.Name()
// see https://stackoverflow.com/questions/44345257/import-from-subfolder-of-npm-package
tsExportsMap["./"+serviceName] = "./dist/" + serviceName + "/index.js"
serviceDir := filepath.Join(workDir, f.Name())
cmd := exec.Command("make", "api")
cmd.Dir = serviceDir
outp, err := cmd.CombinedOutput()
if err != nil {
fmt.Println(string(outp))
}
serviceFiles, err := ioutil.ReadDir(serviceDir)
if err != nil {
fmt.Println("Failed to read service dir", err)
os.Exit(1)
}
skip := false
// detect openapi json file
apiJSON := ""
for _, serviceFile := range serviceFiles {
if strings.Contains(serviceFile.Name(), "api") && strings.Contains(serviceFile.Name(), "-") && strings.HasSuffix(serviceFile.Name(), ".json") {
apiJSON = filepath.Join(serviceDir, serviceFile.Name())
}
if serviceFile.Name() == "skip" {
skip = true
}
}
if skip {
continue
}
fmt.Println("Processing folder", serviceDir, "api json", apiJSON)
js, err := ioutil.ReadFile(apiJSON)
if err != nil {
fmt.Println("Failed to read json spec", err)
os.Exit(1)
}
spec := &openapi3.Swagger{}
err = json.Unmarshal(js, &spec)
if err != nil {
fmt.Println("Failed to unmarshal", err)
os.Exit(1)
}
service := service{
Name: serviceName,
Spec: spec,
}
services = append(services, service)
templ, err := template.New("ts" + serviceName).Funcs(funcs).Parse(tsServiceTemplate)
if err != nil {
fmt.Println("Failed to unmarshal", err)
os.Exit(1)
}
var b bytes.Buffer
buf := bufio.NewWriter(&b)
err = templ.Execute(buf, map[string]interface{}{
"service": service,
})
if err != nil {
fmt.Println("Failed to unmarshal", err)
os.Exit(1)
}
err = os.MkdirAll(filepath.Join(tsPath, serviceName), 0777)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
f, err := os.OpenFile(filepath.Join(tsPath, serviceName, "index.ts"), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0744)
if err != nil {
fmt.Println("Failed to open schema file", err)
os.Exit(1)
}
buf.Flush()
_, err = f.Write(b.Bytes())
if err != nil {
fmt.Println("Failed to append to schema file", err)
os.Exit(1)
}
cmd = exec.Command("prettier", "-w", "index.ts")
cmd.Dir = filepath.Join(tsPath, serviceName)
outp, err = cmd.CombinedOutput()
if err != nil {
fmt.Println(fmt.Sprintf("Problem formatting '%v' client: %v %s", serviceName, string(outp), err.Error()))
os.Exit(1)
}
templ, err = template.New("go" + serviceName).Funcs(funcs).Parse(goServiceTemplate)
if err != nil {
fmt.Println("Failed to unmarshal", err)
os.Exit(1)
}
b = bytes.Buffer{}
buf = bufio.NewWriter(&b)
err = templ.Execute(buf, map[string]interface{}{
"service": service,
})
if err != nil {
fmt.Println("Failed to unmarshal", err)
os.Exit(1)
}
err = os.MkdirAll(filepath.Join(goPath, serviceName), 0777)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
goClientFile := filepath.Join(goPath, serviceName, serviceName+".go")
f, err = os.OpenFile(goClientFile, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0744)
if err != nil {
fmt.Println("Failed to open schema file", err)
os.Exit(1)
}
buf.Flush()
_, err = f.Write(b.Bytes())
if err != nil {
fmt.Println("Failed to append to schema file", err)
os.Exit(1)
}
cmd = exec.Command("gofmt", "-w", serviceName+".go")
cmd.Dir = filepath.Join(goPath, serviceName)
outp, err = cmd.CombinedOutput()
if err != nil {
fmt.Println(fmt.Sprintf("Problem formatting '%v' client: %v", serviceName, string(outp)))
os.Exit(1)
}
cmd = exec.Command("go", "build", "-o", "/tmp/bin/outputfile")
cmd.Dir = filepath.Join(goPath, serviceName)
outp, err = cmd.CombinedOutput()
if err != nil {
fmt.Println(fmt.Sprintf("Problem building '%v' example: %v", serviceName, string(outp)))
os.Exit(1)
}
exam, err := ioutil.ReadFile(filepath.Join(workDir, serviceName, "examples.json"))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if err == nil {
m := map[string][]example{}
err = json.Unmarshal(exam, &m)
if err != nil {
fmt.Println(string(exam), err)
os.Exit(1)
}
if len(service.Spec.Paths) != len(m) {
fmt.Printf("Service has %v endpoints, but only %v examples\n", len(service.Spec.Paths), len(m))
}
for endpoint, examples := range m {
for _, example := range examples {
title := regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(strcase.LowerCamelCase(strings.Replace(example.Title, " ", "_", -1)), "")
templ, err = template.New("go" + serviceName + endpoint).Funcs(funcs).Parse(goExampleTemplate)
if err != nil {
fmt.Println("Failed to unmarshal", err)
os.Exit(1)
}
b = bytes.Buffer{}
buf = bufio.NewWriter(&b)
err = templ.Execute(buf, map[string]interface{}{
"service": service,
"example": example,
"endpoint": endpoint,
"funcName": strcase.UpperCamelCase(title),
})
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// create go examples directory
err = os.MkdirAll(filepath.Join(examplesPath, serviceName, endpoint, "go"), 0777)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
goExampleFile := filepath.Join(examplesPath, serviceName, endpoint, "go", title+".go")
f, err = os.OpenFile(goExampleFile, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0744)
if err != nil {
fmt.Println("Failed to open schema file", err)
os.Exit(1)
}
buf.Flush()
_, err = f.Write(b.Bytes())
if err != nil {
fmt.Println("Failed to append to schema file", err)
os.Exit(1)
}
cmd := exec.Command("gofmt", "-w", title+".go")
cmd.Dir = filepath.Join(examplesPath, serviceName, endpoint, "go")
outp, err = cmd.CombinedOutput()
if err != nil {
fmt.Println(fmt.Sprintf("Problem with '%v' example '%v': %v", serviceName, endpoint, string(outp)))
os.Exit(1)
}
// node example
templ, err = template.New("ts" + serviceName + endpoint).Funcs(funcs).Parse(tsExampleTemplate)
if err != nil {
fmt.Println("Failed to unmarshal", err)
os.Exit(1)
}
b = bytes.Buffer{}
buf = bufio.NewWriter(&b)
err = templ.Execute(buf, map[string]interface{}{
"service": service,
"example": example,
"endpoint": endpoint,
"funcName": strcase.UpperCamelCase(title),
})
err = os.MkdirAll(filepath.Join(examplesPath, serviceName, endpoint, "node"), 0777)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
tsExampleFile := filepath.Join(examplesPath, serviceName, endpoint, "node", title+".js")
f, err = os.OpenFile(tsExampleFile, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0744)
if err != nil {
fmt.Println("Failed to open schema file", err)
os.Exit(1)
}
buf.Flush()
_, err = f.Write(b.Bytes())
if err != nil {
fmt.Println("Failed to append to schema file", err)
os.Exit(1)
}
cmd = exec.Command("prettier", "-w", title+".js")
cmd.Dir = filepath.Join(examplesPath, serviceName, endpoint, "node")
outp, err = cmd.CombinedOutput()
if err != nil {
fmt.Println(fmt.Sprintf("Problem with '%v' example '%v': %v", serviceName, endpoint, string(outp)))
os.Exit(1)
}
// curl example
templ, err = template.New("curl" + serviceName + endpoint).Funcs(funcs).Parse(curlExampleTemplate)
if err != nil {
fmt.Println("Failed to unmarshal", err)
os.Exit(1)
}
b = bytes.Buffer{}
buf = bufio.NewWriter(&b)
err = templ.Execute(buf, map[string]interface{}{
"service": service,
"example": example,
"endpoint": endpoint,
"funcName": strcase.UpperCamelCase(title),
})
err = os.MkdirAll(filepath.Join(examplesPath, serviceName, endpoint, "curl"), 0777)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
curlExampleFile := filepath.Join(examplesPath, serviceName, endpoint, "curl", title+".sh")
f, err = os.OpenFile(curlExampleFile, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0744)
if err != nil {
fmt.Println("Failed to open schema file", err)
os.Exit(1)
}
buf.Flush()
_, err = f.Write(b.Bytes())
if err != nil {
fmt.Println("Failed to append to schema file", err)
os.Exit(1)
}
}
// only build after each example is generated as old files from
// previous generation might not compile
cmd = exec.Command("go", "build", "-o", "/tmp/bin/outputfile")
cmd.Dir = filepath.Join(examplesPath, serviceName, endpoint, "go")
outp, err = cmd.CombinedOutput()
if err != nil {
fmt.Println(fmt.Sprintf("Problem with '%v' example '%v': %v", serviceName, endpoint, string(outp)))
os.Exit(1)
}
}
} else {
fmt.Println(err)
}
}
}
templ, err := template.New("tsclient").Funcs(funcs).Parse(tsIndexTemplate)
if err != nil {
fmt.Println("Failed to unmarshal", err)
os.Exit(1)
}
var b bytes.Buffer
buf := bufio.NewWriter(&b)
err = templ.Execute(buf, map[string]interface{}{
"services": services,
})
if err != nil {
fmt.Println("Failed to unmarshal", err)
os.Exit(1)
}
f, err := os.OpenFile(filepath.Join(tsPath, "index.ts"), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0744)
if err != nil {
fmt.Println("Failed to open schema file", err)
os.Exit(1)
}
buf.Flush()
_, err = f.Write(b.Bytes())
if err != nil {
fmt.Println("Failed to append to schema file", err)
os.Exit(1)
}
cmd := exec.Command("prettier", "-w", "index.ts")
cmd.Dir = filepath.Join(tsPath)
outp, err := cmd.CombinedOutput()
if err != nil {
fmt.Println(fmt.Sprintf("Problem with prettifying clients index.ts '%v", string(outp)))
os.Exit(1)
}
tsFiles := filepath.Join(workDir, "cmd", "clients", "ts")
cmd = exec.Command("cp", filepath.Join(tsFiles, "package.json"), filepath.Join(tsFiles, ".gitignore"), filepath.Join(tsFiles, "package-lock.json"), filepath.Join(tsFiles, "tsconfig.json"), filepath.Join(workDir, "clients", "ts"))
cmd.Dir = filepath.Join(tsPath)
outp, err = cmd.CombinedOutput()
if err != nil {
fmt.Println(fmt.Sprintf("Problem with prettifying clients index.ts '%v", string(outp)))
os.Exit(1)
}
templ, err = template.New("goclient").Funcs(funcs).Parse(goIndexTemplate)
if err != nil {
fmt.Println("Failed to unmarshal", err)
os.Exit(1)
}
b = bytes.Buffer{}
buf = bufio.NewWriter(&b)
err = templ.Execute(buf, map[string]interface{}{
"services": services,
})
if err != nil {
fmt.Println("Failed to unmarshal", err)
os.Exit(1)
}
f, err = os.OpenFile(filepath.Join(goPath, "m3o.go"), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0744)
if err != nil {
fmt.Println("Failed to open schema file", err)
os.Exit(1)
}
buf.Flush()
_, err = f.Write(b.Bytes())
if err != nil {
fmt.Println("Failed to append to schema file", err)
os.Exit(1)
}
cmd = exec.Command("gofmt", "-w", "m3o.go")
cmd.Dir = filepath.Join(goPath)
outp, err = cmd.CombinedOutput()
if err != nil {
fmt.Println(fmt.Sprintf("Problem with formatting m3o.go '%v", string(outp)))
os.Exit(1)
}
cmd = exec.Command("go", "build", "-o", "/tmp/bin/outputfile")
cmd.Dir = filepath.Join(goPath)
outp, err = cmd.CombinedOutput()
if err != nil {
fmt.Println(fmt.Sprintf("Problem building m3o.go '%v'", string(outp)))
os.Exit(1)
}
// login to NPM
f, err = os.OpenFile(filepath.Join(tsPath, ".npmrc"), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
fmt.Println("Failed to open npmrc", err)
os.Exit(1)
}
defer f.Close()
if len(os.Getenv("NPM_TOKEN")) == 0 {
fmt.Println("No NPM_TOKEN env found")
os.Exit(1)
}
if _, err = f.WriteString("//registry.npmjs.org/:_authToken=" + os.Getenv("NPM_TOKEN")); err != nil {
fmt.Println("Failed to open npmrc", err)
os.Exit(1)
}
// get latest version from github
getVersions := exec.Command("npm", "show", "m3o", "--time", "--json")
getVersions.Dir = tsPath
outp, err = getVersions.CombinedOutput()
if err != nil {
fmt.Println("Failed to get versions of NPM package", string(outp))
os.Exit(1)
}
type npmVers struct {
Versions []string `json:"versions"`
}
npmOutput := &npmVers{}
var latest *semver.Version
if len(outp) > 0 {
err = json.Unmarshal(outp, npmOutput)
if err != nil {
fmt.Println("Failed to unmarshal versions", string(outp))
os.Exit(1)
}
}
for _, version := range npmOutput.Versions {
v, err := semver.NewVersion(version)
if err != nil {
fmt.Println("Failed to parse semver", err)
os.Exit(1)
}
if latest == nil {
latest = v
}
if v.GreaterThan(latest) {
latest = v
}
}
if latest == nil {
latest, _ = semver.NewVersion("0.0.0")
}
newV := latest.IncPatch()
// bump package to latest version
fmt.Println("Bumping to ", newV.String())
repl := exec.Command("sed", "-i", "-e", "s/1.0.1/"+newV.String()+"/g", "package.json")
repl.Dir = tsPath
outp, err = repl.CombinedOutput()
if err != nil {
fmt.Println("Failed to make docs", string(outp))
os.Exit(1)
}
// apppend exports to to package.json
pak, err := ioutil.ReadFile(filepath.Join(tsPath, "package.json"))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
m := map[string]interface{}{}
err = json.Unmarshal(pak, &m)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
m["exports"] = tsExportsMap
pakJS, err := json.MarshalIndent(m, "", " ")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
f, err = os.OpenFile(filepath.Join(tsPath, "package.json"), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0744)
if err != nil {
fmt.Println("Failed to open package.json", err)
os.Exit(1)
}
_, err = f.Write(pakJS)
if err != nil {
fmt.Println("Failed to write to package.json", err)
os.Exit(1)
}
}
func schemaToType(language, serviceName, typeName string, schemas map[string]*openapi3.SchemaRef) string {
var recurse func(props map[string]*openapi3.SchemaRef, level int) string
var spec *openapi3.SchemaRef = schemas[typeName]
detectType := func(currentType string, properties map[string]*openapi3.SchemaRef) (string, bool) {
index := map[string]bool{}
for key, prop := range properties {
index[key+prop.Value.Title+prop.Value.Description] = true
}
for k, schema := range schemas {
// we don't want to return the type matching itself
if strings.ToLower(k) == currentType {
continue
}
if strings.HasSuffix(k, "Request") || strings.HasSuffix(k, "Response") {
continue
}
if len(schema.Value.Properties) != len(properties) {
continue
}
found := false
for key, prop := range schema.Value.Properties {
_, ok := index[key+prop.Value.Title+prop.Value.Description]
found = ok
if !ok {
break
}
}
if found {
return schema.Value.Title, true
}
}
return "", false
}
var fieldSeparator, arrayPrefix, arrayPostfix, fieldDelimiter, stringType, numberType, boolType string
var int32Type, int64Type, floatType, doubleType, mapType, anyType, typePrefix string
var fieldUpperCase bool
switch language {
case "typescript":
fieldUpperCase = false
fieldSeparator = "?: "
arrayPrefix = ""
arrayPostfix = "[]"
//objectOpen = "{\n"
//objectClose = "}"
fieldDelimiter = ";"
stringType = "string"
numberType = "number"
boolType = "boolean"
int32Type = "number"
int64Type = "number"
floatType = "number"
doubleType = "number"
anyType = "any"
mapType = "{ [key: string]: %v }"
typePrefix = ""
case "go":
fieldUpperCase = true
fieldSeparator = " "
arrayPrefix = "[]"
arrayPostfix = ""
//objectOpen = "{"
// objectClose = "}"
fieldDelimiter = ""
stringType = "string"
numberType = "int64"
boolType = "bool"
int32Type = "int32"
int64Type = "int64"
floatType = "float32"
doubleType = "float64"
mapType = "map[string]%v"
anyType = "interface{}"
typePrefix = "*"
}
valueToType := func(v *openapi3.SchemaRef) string {
switch v.Value.Type {
case "string":
return stringType
case "boolean":
return boolType
case "number":
switch v.Value.Format {
case "int32":
return int32Type
case "int64":
return int64Type
case "float":
return floatType
case "double":
return doubleType
}
default:
return "unrecognized: " + v.Value.Type
}
return ""
}
recurse = func(props map[string]*openapi3.SchemaRef, level int) string {
ret := ""
i := 0
var keys []string
for k := range props {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := props[k]
ret += strings.Repeat(" ", level)
if v.Value.Description != "" {
for _, commentLine := range strings.Split(v.Value.Description, "\n") {
ret += "// " + strings.TrimSpace(commentLine) + "\n" + strings.Repeat(" ", level)
}
}
if fieldUpperCase {
k = strcase.UpperCamelCase(k)
}
// @todo clean up this piece of code by
// separating out type string marshaling and not
// repeating code
switch v.Value.Type {
case "object":
typ, found := detectType(k, v.Value.Properties)
if found {
ret += k + fieldSeparator + typePrefix + strings.Title(typ) + fieldDelimiter
} else {
// type is a dynamic map
// if additional properties is not present, it's an any type,
// like the proto struct type
if v.Value.AdditionalProperties != nil {
ret += k + fieldSeparator + fmt.Sprintf(mapType, valueToType(v.Value.AdditionalProperties)) + fieldDelimiter
} else {
ret += k + fieldSeparator + fmt.Sprintf(mapType, anyType) + fieldDelimiter
}
}
case "array":
typ, found := detectType(k, v.Value.Items.Value.Properties)
if found {
ret += k + fieldSeparator + arrayPrefix + strings.Title(typ) + arrayPostfix + fieldDelimiter
} else {
switch v.Value.Items.Value.Type {
case "string":
ret += k + fieldSeparator + arrayPrefix + stringType + arrayPostfix + fieldDelimiter
case "number":
typ := numberType
switch v.Value.Format {
case "int32":
typ = int32Type
case "int64":
typ = int64Type
case "float":
typ = floatType
case "double":
typ = doubleType
}
ret += k + fieldSeparator + arrayPrefix + typ + arrayPostfix + fieldDelimiter
case "boolean":
ret += k + fieldSeparator + arrayPrefix + boolType + arrayPostfix + fieldDelimiter
case "object":
// type is a dynamic map
// if additional properties is not present, it's an any type,
// like the proto struct type
if v.Value.AdditionalProperties != nil {
ret += k + fieldSeparator + arrayPrefix + fmt.Sprintf(mapType, valueToType(v.Value.AdditionalProperties)) + arrayPostfix + fieldDelimiter
} else {
ret += k + fieldSeparator + arrayPrefix + fmt.Sprintf(mapType, anyType) + arrayPostfix + fieldDelimiter
}
}
}
case "string":
ret += k + fieldSeparator + stringType + fieldDelimiter
case "number":
typ := numberType
switch v.Value.Format {
case "int32":
typ = int32Type
case "int64":
typ = int64Type
case "float":
typ = floatType
case "double":
typ = doubleType
}
ret += k + fieldSeparator + typ + fieldDelimiter
case "boolean":
ret += k + fieldSeparator + boolType + fieldDelimiter
}
// go specific hack for lowercase son
if language == "go" {
ret += " " + "`json:\"" + strcase.LowerCamelCase(k) + "\"`"
}
if i < len(props) {
ret += "\n"
}
i++
}
return ret
}
return recurse(spec.Value.Properties, 1)
}
func schemaToMethods(title string, spec *openapi3.RequestBodyRef) string {
return ""
}
// CopyFile copies a file from src to dst. If src and dst files exist, and are
// the same, then return success. Otherise, attempt to create a hard link
// between the two files. If that fail, copy the file contents from src to dst.
// from https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang
func CopyFile(src, dst string) (err error) {
sfi, err := os.Stat(src)
if err != nil {
return
}
if !sfi.Mode().IsRegular() {
// cannot copy non-regular files (e.g., directories,
// symlinks, devices, etc.)
return fmt.Errorf("CopyFile: non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String())
}
dfi, err := os.Stat(dst)
if err != nil {
if !os.IsNotExist(err) {
return
}
} else {
if !(dfi.Mode().IsRegular()) {
return fmt.Errorf("CopyFile: non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String())
}
if os.SameFile(sfi, dfi) {
return
}
}
if err = os.Link(src, dst); err == nil {
return
}
err = copyFileContents(src, dst)
return
}
// copyFileContents copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all it's contents will be replaced by the contents
// of the source file.
func copyFileContents(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return
}
defer func() {
cerr := out.Close()
if err == nil {
err = cerr
}
}()
if _, err = io.Copy(out, in); err != nil {
return
}
err = out.Sync()
return
}