Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
6e90239fef
|
|||
05580c384a
|
|||
3188b951fb
|
|||
6b211d1443
|
|||
b2b9b40792
|
|||
2f915cb6c1
|
|||
b2b93f570a
|
|||
8247fc4524
|
|||
5dad44ad09
|
|||
f042183433
|
|||
b0be93a7a0
|
|||
1c143921e6
|
BIN
bfcodegen/_test_example_2.tgz
Normal file
BIN
bfcodegen/_test_example_2.tgz
Normal file
Binary file not shown.
@@ -70,9 +70,9 @@ func GenerateCharsetIDSpecs(sourceDir string, destFile string) error {
|
|||||||
newChecksum := cryptext.BytesSha256([]byte(newChecksumStr))
|
newChecksum := cryptext.BytesSha256([]byte(newChecksumStr))
|
||||||
|
|
||||||
if newChecksum != oldChecksum {
|
if newChecksum != oldChecksum {
|
||||||
fmt.Printf("[IDGenerate] Checksum has changed ( %s -> %s ), will generate new file\n\n", oldChecksum, newChecksum)
|
fmt.Printf("[CSIDGenerate] Checksum has changed ( %s -> %s ), will generate new file\n\n", oldChecksum, newChecksum)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("[IDGenerate] Checksum unchanged ( %s ), nothing to do\n", oldChecksum)
|
fmt.Printf("[CSIDGenerate] Checksum unchanged ( %s ), nothing to do\n", oldChecksum)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,8 +12,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed _test_example.tgz
|
//go:embed _test_example_1.tgz
|
||||||
var CSIDExampleModels []byte
|
var CSIDExampleModels1 []byte
|
||||||
|
|
||||||
func TestGenerateCSIDSpecs(t *testing.T) {
|
func TestGenerateCSIDSpecs(t *testing.T) {
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ func TestGenerateCSIDSpecs(t *testing.T) {
|
|||||||
|
|
||||||
tmpDir := filepath.Join(t.TempDir(), langext.MustHexUUID())
|
tmpDir := filepath.Join(t.TempDir(), langext.MustHexUUID())
|
||||||
|
|
||||||
err := os.WriteFile(tmpFile, CSIDExampleModels, 0o777)
|
err := os.WriteFile(tmpFile, CSIDExampleModels1, 0o777)
|
||||||
tst.AssertNoErr(t, err)
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
t.Cleanup(func() { _ = os.Remove(tmpFile) })
|
t.Cleanup(func() { _ = os.Remove(tmpFile) })
|
||||||
|
@@ -3,6 +3,7 @@ package bfcodegen
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/format"
|
"go/format"
|
||||||
@@ -14,6 +15,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
@@ -23,6 +25,8 @@ type EnumDefVal struct {
|
|||||||
VarName string
|
VarName string
|
||||||
Value string
|
Value string
|
||||||
Description *string
|
Description *string
|
||||||
|
Data *map[string]any
|
||||||
|
RawComment *string
|
||||||
}
|
}
|
||||||
|
|
||||||
type EnumDef struct {
|
type EnumDef struct {
|
||||||
@@ -37,7 +41,7 @@ var rexEnumPackage = rext.W(regexp.MustCompile(`^package\s+(?P<name>[A-Za-z0-9_]
|
|||||||
|
|
||||||
var rexEnumDef = rext.W(regexp.MustCompile(`^\s*type\s+(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*//\s*(@enum:type).*$`))
|
var rexEnumDef = rext.W(regexp.MustCompile(`^\s*type\s+(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*//\s*(@enum:type).*$`))
|
||||||
|
|
||||||
var rexEnumValueDef = rext.W(regexp.MustCompile(`^\s*(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*=\s*(?P<value>("[A-Za-z0-9_:\s\-.]+"|[0-9]+))\s*(//(?P<descr>.*))?.*$`))
|
var rexEnumValueDef = rext.W(regexp.MustCompile(`^\s*(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*=\s*(?P<value>("[A-Za-z0-9_:\s\-.]+"|[0-9]+))\s*(//(?P<comm>.*))?.*$`))
|
||||||
|
|
||||||
var rexEnumChecksumConst = rext.W(regexp.MustCompile(`const ChecksumEnumGenerator = "(?P<cs>[A-Za-z0-9_]*)"`))
|
var rexEnumChecksumConst = rext.W(regexp.MustCompile(`const ChecksumEnumGenerator = "(?P<cs>[A-Za-z0-9_]*)"`))
|
||||||
|
|
||||||
@@ -46,11 +50,6 @@ var templateEnumGenerateText string
|
|||||||
|
|
||||||
func GenerateEnumSpecs(sourceDir string, destFile string) error {
|
func GenerateEnumSpecs(sourceDir string, destFile string) error {
|
||||||
|
|
||||||
files, err := os.ReadDir(sourceDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
oldChecksum := "N/A"
|
oldChecksum := "N/A"
|
||||||
if _, err := os.Stat(destFile); !os.IsNotExist(err) {
|
if _, err := os.Stat(destFile); !os.IsNotExist(err) {
|
||||||
content, err := os.ReadFile(destFile)
|
content, err := os.ReadFile(destFile)
|
||||||
@@ -62,6 +61,30 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gocode, _, changed, err := _generateEnumSpecs(sourceDir, destFile, oldChecksum, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !changed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(destFile, []byte(gocode), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func _generateEnumSpecs(sourceDir string, destFile string, oldChecksum string, gofmt bool) (string, string, bool, error) {
|
||||||
|
|
||||||
|
files, err := os.ReadDir(sourceDir)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", false, err
|
||||||
|
}
|
||||||
|
|
||||||
files = langext.ArrFilter(files, func(v os.DirEntry) bool { return v.Name() != path.Base(destFile) })
|
files = langext.ArrFilter(files, func(v os.DirEntry) bool { return v.Name() != path.Base(destFile) })
|
||||||
files = langext.ArrFilter(files, func(v os.DirEntry) bool { return strings.HasSuffix(v.Name(), ".go") })
|
files = langext.ArrFilter(files, func(v os.DirEntry) bool { return strings.HasSuffix(v.Name(), ".go") })
|
||||||
files = langext.ArrFilter(files, func(v os.DirEntry) bool { return !strings.HasSuffix(v.Name(), "_gen.go") })
|
files = langext.ArrFilter(files, func(v os.DirEntry) bool { return !strings.HasSuffix(v.Name(), "_gen.go") })
|
||||||
@@ -71,7 +94,7 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error {
|
|||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
content, err := os.ReadFile(path.Join(sourceDir, f.Name()))
|
content, err := os.ReadFile(path.Join(sourceDir, f.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", "", false, err
|
||||||
}
|
}
|
||||||
newChecksumStr += "\n" + f.Name() + "\t" + cryptext.BytesSha256(content)
|
newChecksumStr += "\n" + f.Name() + "\t" + cryptext.BytesSha256(content)
|
||||||
}
|
}
|
||||||
@@ -82,7 +105,7 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error {
|
|||||||
fmt.Printf("[EnumGenerate] Checksum has changed ( %s -> %s ), will generate new file\n\n", oldChecksum, newChecksum)
|
fmt.Printf("[EnumGenerate] Checksum has changed ( %s -> %s ), will generate new file\n\n", oldChecksum, newChecksum)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("[EnumGenerate] Checksum unchanged ( %s ), nothing to do\n", oldChecksum)
|
fmt.Printf("[EnumGenerate] Checksum unchanged ( %s ), nothing to do\n", oldChecksum)
|
||||||
return nil
|
return "", oldChecksum, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
allEnums := make([]EnumDef, 0)
|
allEnums := make([]EnumDef, 0)
|
||||||
@@ -93,7 +116,7 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error {
|
|||||||
fmt.Printf("========= %s =========\n\n", f.Name())
|
fmt.Printf("========= %s =========\n\n", f.Name())
|
||||||
fileEnums, pn, err := processEnumFile(sourceDir, path.Join(sourceDir, f.Name()))
|
fileEnums, pn, err := processEnumFile(sourceDir, path.Join(sourceDir, f.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", "", false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
@@ -106,20 +129,21 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if pkgname == "" {
|
if pkgname == "" {
|
||||||
return errors.New("no package name found in any file")
|
return "", "", false, errors.New("no package name found in any file")
|
||||||
}
|
}
|
||||||
|
|
||||||
fdata, err := format.Source([]byte(fmtEnumOutput(newChecksum, allEnums, pkgname)))
|
rdata := fmtEnumOutput(newChecksum, allEnums, pkgname)
|
||||||
|
|
||||||
|
if !gofmt {
|
||||||
|
return rdata, newChecksum, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fdata, err := format.Source([]byte(rdata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", "", false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(destFile, fdata, 0o755)
|
return string(fdata), newChecksum, true, nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func processEnumFile(basedir string, fn string) ([]EnumDef, string, error) {
|
func processEnumFile(basedir string, fn string) ([]EnumDef, string, error) {
|
||||||
@@ -171,10 +195,34 @@ func processEnumFile(basedir string, fn string) ([]EnumDef, string, error) {
|
|||||||
|
|
||||||
if match, ok := rexEnumValueDef.MatchFirst(line); ok {
|
if match, ok := rexEnumValueDef.MatchFirst(line); ok {
|
||||||
typename := match.GroupByName("type").Value()
|
typename := match.GroupByName("type").Value()
|
||||||
|
|
||||||
|
comment := match.GroupByNameOrEmpty("comm").ValueOrNil()
|
||||||
|
var descr *string = nil
|
||||||
|
var data *map[string]any = nil
|
||||||
|
if comment != nil {
|
||||||
|
comment = langext.Ptr(strings.TrimSpace(*comment))
|
||||||
|
if strings.HasPrefix(*comment, "{") {
|
||||||
|
if v, ok := tryParseDataComment(*comment); ok {
|
||||||
|
data = &v
|
||||||
|
if anyDataDescr, ok := v["description"]; ok {
|
||||||
|
if dataDescr, ok := anyDataDescr.(string); ok {
|
||||||
|
descr = &dataDescr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
descr = comment
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
descr = comment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def := EnumDefVal{
|
def := EnumDefVal{
|
||||||
VarName: match.GroupByName("name").Value(),
|
VarName: match.GroupByName("name").Value(),
|
||||||
Value: match.GroupByName("value").Value(),
|
Value: match.GroupByName("value").Value(),
|
||||||
Description: match.GroupByNameOrEmpty("descr").ValueOrNil(),
|
RawComment: comment,
|
||||||
|
Description: descr,
|
||||||
|
Data: data,
|
||||||
}
|
}
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
@@ -199,6 +247,41 @@ func processEnumFile(basedir string, fn string) ([]EnumDef, string, error) {
|
|||||||
return enums, pkgname, nil
|
return enums, pkgname, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tryParseDataComment(s string) (map[string]any, bool) {
|
||||||
|
|
||||||
|
r := make(map[string]any)
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(s), &r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range r {
|
||||||
|
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
|
||||||
|
if rv.Kind() == reflect.Ptr && rv.IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rv.Kind() == reflect.Bool {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rv.Kind() == reflect.String {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rv.Kind() == reflect.Int64 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rv.Kind() == reflect.Float64 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, true
|
||||||
|
}
|
||||||
|
|
||||||
func fmtEnumOutput(cs string, enums []EnumDef, pkgname string) string {
|
func fmtEnumOutput(cs string, enums []EnumDef, pkgname string) string {
|
||||||
|
|
||||||
templ := template.New("enum-generate")
|
templ := template.New("enum-generate")
|
||||||
@@ -211,6 +294,47 @@ func fmtEnumOutput(cs string, enums []EnumDef, pkgname string) string {
|
|||||||
"hasDescr": func(v EnumDef) bool {
|
"hasDescr": func(v EnumDef) bool {
|
||||||
return langext.ArrAll(v.Values, func(val EnumDefVal) bool { return val.Description != nil })
|
return langext.ArrAll(v.Values, func(val EnumDefVal) bool { return val.Description != nil })
|
||||||
},
|
},
|
||||||
|
"hasData": func(v EnumDef) bool {
|
||||||
|
return len(v.Values) > 0 && langext.ArrAll(v.Values, func(val EnumDefVal) bool { return val.Data != nil })
|
||||||
|
},
|
||||||
|
"gostr": func(v any) string {
|
||||||
|
return fmt.Sprintf("%#+v", v)
|
||||||
|
},
|
||||||
|
"goobj": func(name string, v any) string {
|
||||||
|
return fmt.Sprintf("%#+v", v)
|
||||||
|
},
|
||||||
|
"godatakey": func(v string) string {
|
||||||
|
return strings.ToUpper(v[0:1]) + v[1:]
|
||||||
|
},
|
||||||
|
"godatavalue": func(v any) string {
|
||||||
|
return fmt.Sprintf("%#+v", v)
|
||||||
|
},
|
||||||
|
"godatatype": func(v any) string {
|
||||||
|
return fmt.Sprintf("%T", v)
|
||||||
|
},
|
||||||
|
"mapindex": func(v map[string]any, k string) any {
|
||||||
|
return v[k]
|
||||||
|
},
|
||||||
|
"generalDataKeys": func(v EnumDef) map[string]string {
|
||||||
|
r0 := make(map[string]int)
|
||||||
|
|
||||||
|
for _, eval := range v.Values {
|
||||||
|
for k := range *eval.Data {
|
||||||
|
if ctr, ok := r0[k]; ok {
|
||||||
|
r0[k] = ctr + 1
|
||||||
|
} else {
|
||||||
|
r0[k] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r1 := langext.MapToArr(r0)
|
||||||
|
r2 := langext.ArrFilter(r1, func(p langext.MapEntry[string, int]) bool { return p.Value == len(v.Values) })
|
||||||
|
r3 := langext.ArrMap(r2, func(p langext.MapEntry[string, int]) string { return p.Key })
|
||||||
|
r4 := langext.ArrToKVMap(r3, func(p string) string { return p }, func(p string) string { return fmt.Sprintf("%T", (*v.Values[0].Data)[p]) })
|
||||||
|
|
||||||
|
return r4
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
templ = template.Must(templ.Parse(templateEnumGenerateText))
|
templ = template.Must(templ.Parse(templateEnumGenerateText))
|
||||||
|
@@ -11,21 +11,38 @@ const ChecksumEnumGenerator = "{{.Checksum}}" // GoExtVersion: {{.GoextVersion}}
|
|||||||
|
|
||||||
{{ $hasStr := ( . | hasStr ) }}
|
{{ $hasStr := ( . | hasStr ) }}
|
||||||
{{ $hasDescr := ( . | hasDescr ) }}
|
{{ $hasDescr := ( . | hasDescr ) }}
|
||||||
|
{{ $hasData := ( . | hasData ) }}
|
||||||
|
|
||||||
// ================================ {{.EnumTypeName}} ================================
|
// ================================ {{.EnumTypeName}} ================================
|
||||||
//
|
//
|
||||||
// File: {{.FileRelative}}
|
// File: {{.FileRelative}}
|
||||||
// StringEnum: {{$hasStr | boolToStr}}
|
// StringEnum: {{$hasStr | boolToStr}}
|
||||||
// DescrEnum: {{$hasDescr | boolToStr}}
|
// DescrEnum: {{$hasDescr | boolToStr}}
|
||||||
|
// DataEnum: {{$hasData | boolToStr}}
|
||||||
//
|
//
|
||||||
|
|
||||||
|
{{ $typename := .EnumTypeName }}
|
||||||
|
{{ $enumdef := . }}
|
||||||
|
|
||||||
var __{{.EnumTypeName}}Values = []{{.EnumTypeName}}{ {{range .Values}}
|
var __{{.EnumTypeName}}Values = []{{.EnumTypeName}}{ {{range .Values}}
|
||||||
{{.VarName}}, {{end}}
|
{{.VarName}}, {{end}}
|
||||||
}
|
}
|
||||||
|
|
||||||
{{if $hasDescr}}
|
{{if $hasDescr}}
|
||||||
var __{{.EnumTypeName}}Descriptions = map[{{.EnumTypeName}}]string{ {{range .Values}}
|
var __{{.EnumTypeName}}Descriptions = map[{{.EnumTypeName}}]string{ {{range .Values}}
|
||||||
{{.VarName}}: "{{.Description | deref | trimSpace}}", {{end}}
|
{{.VarName}}: {{.Description | deref | trimSpace | gostr}}, {{end}}
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if $hasData}}
|
||||||
|
type {{ .EnumTypeName }}Data struct { {{ range $datakey, $datatype := ($enumdef | generalDataKeys) }}
|
||||||
|
{{ $datakey | godatakey }} {{ $datatype }} `json:"{{ $datakey }}"` {{ end }}
|
||||||
|
}
|
||||||
|
|
||||||
|
var __{{.EnumTypeName}}Data = map[{{.EnumTypeName}}]{{.EnumTypeName}}Data{ {{range .Values}} {{ $enumvalue := . }}
|
||||||
|
{{.VarName}}: {{ $typename }}Data{ {{ range $datakey, $datatype := $enumdef | generalDataKeys }}
|
||||||
|
{{ $datakey | godatakey }}: {{ (mapindex $enumvalue.Data $datakey) | godatavalue }}, {{ end }}
|
||||||
|
}, {{end}}
|
||||||
}
|
}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
@@ -64,6 +81,15 @@ func (e {{.EnumTypeName}}) Description() string {
|
|||||||
}
|
}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{if $hasData}}
|
||||||
|
func (e {{.EnumTypeName}}) Data() {{.EnumTypeName}}Data {
|
||||||
|
if d, ok := __{{.EnumTypeName}}Data[e]; ok {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
return {{.EnumTypeName}}Data{}
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
func (e {{.EnumTypeName}}) VarName() string {
|
func (e {{.EnumTypeName}}) VarName() string {
|
||||||
if d, ok := __{{.EnumTypeName}}Varnames[e]; ok {
|
if d, ok := __{{.EnumTypeName}}Varnames[e]; ok {
|
||||||
return d
|
return d
|
||||||
|
@@ -12,8 +12,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed _test_example.tgz
|
//go:embed _test_example_1.tgz
|
||||||
var EnumExampleModels []byte
|
var EnumExampleModels1 []byte
|
||||||
|
|
||||||
|
//go:embed _test_example_2.tgz
|
||||||
|
var EnumExampleModels2 []byte
|
||||||
|
|
||||||
func TestGenerateEnumSpecs(t *testing.T) {
|
func TestGenerateEnumSpecs(t *testing.T) {
|
||||||
|
|
||||||
@@ -21,7 +24,7 @@ func TestGenerateEnumSpecs(t *testing.T) {
|
|||||||
|
|
||||||
tmpDir := filepath.Join(t.TempDir(), langext.MustHexUUID())
|
tmpDir := filepath.Join(t.TempDir(), langext.MustHexUUID())
|
||||||
|
|
||||||
err := os.WriteFile(tmpFile, EnumExampleModels, 0o777)
|
err := os.WriteFile(tmpFile, EnumExampleModels1, 0o777)
|
||||||
tst.AssertNoErr(t, err)
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
t.Cleanup(func() { _ = os.Remove(tmpFile) })
|
t.Cleanup(func() { _ = os.Remove(tmpFile) })
|
||||||
@@ -34,17 +37,53 @@ func TestGenerateEnumSpecs(t *testing.T) {
|
|||||||
_, err = cmdext.Runner("tar").Arg("-xvzf").Arg(tmpFile).Arg("-C").Arg(tmpDir).FailOnExitCode().FailOnTimeout().Timeout(time.Minute).Run()
|
_, err = cmdext.Runner("tar").Arg("-xvzf").Arg(tmpFile).Arg("-C").Arg(tmpDir).FailOnExitCode().FailOnTimeout().Timeout(time.Minute).Run()
|
||||||
tst.AssertNoErr(t, err)
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
err = GenerateEnumSpecs(tmpDir, tmpDir+"/enums_gen.go")
|
s1, cs1, _, err := _generateEnumSpecs(tmpDir, "", "N/A", true)
|
||||||
tst.AssertNoErr(t, err)
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
err = GenerateEnumSpecs(tmpDir, tmpDir+"/enums_gen.go")
|
s2, cs2, _, err := _generateEnumSpecs(tmpDir, "", "N/A", true)
|
||||||
tst.AssertNoErr(t, err)
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertEqual(t, cs1, cs2)
|
||||||
|
tst.AssertEqual(t, s1, s2)
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("=====================================================================================================")
|
fmt.Println("=====================================================================================================")
|
||||||
fmt.Println(string(tst.Must(os.ReadFile(tmpDir + "/enums_gen.go"))(t)))
|
fmt.Println(s1)
|
||||||
|
fmt.Println("=====================================================================================================")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateEnumSpecsData(t *testing.T) {
|
||||||
|
|
||||||
|
tmpFile := filepath.Join(t.TempDir(), langext.MustHexUUID()+".tgz")
|
||||||
|
|
||||||
|
tmpDir := filepath.Join(t.TempDir(), langext.MustHexUUID())
|
||||||
|
|
||||||
|
err := os.WriteFile(tmpFile, EnumExampleModels2, 0o777)
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
t.Cleanup(func() { _ = os.Remove(tmpFile) })
|
||||||
|
|
||||||
|
err = os.Mkdir(tmpDir, 0o777)
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
t.Cleanup(func() { _ = os.RemoveAll(tmpFile) })
|
||||||
|
|
||||||
|
_, err = cmdext.Runner("tar").Arg("-xvzf").Arg(tmpFile).Arg("-C").Arg(tmpDir).FailOnExitCode().FailOnTimeout().Timeout(time.Minute).Run()
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
s1, _, _, err := _generateEnumSpecs(tmpDir, "", "", true)
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("=====================================================================================================")
|
||||||
|
fmt.Println(s1)
|
||||||
fmt.Println("=====================================================================================================")
|
fmt.Println("=====================================================================================================")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
@@ -12,8 +12,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed _test_example.tgz
|
//go:embed _test_example_1.tgz
|
||||||
var IDExampleModels []byte
|
var IDExampleModels1 []byte
|
||||||
|
|
||||||
func TestGenerateIDSpecs(t *testing.T) {
|
func TestGenerateIDSpecs(t *testing.T) {
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ func TestGenerateIDSpecs(t *testing.T) {
|
|||||||
|
|
||||||
tmpDir := filepath.Join(t.TempDir(), langext.MustHexUUID())
|
tmpDir := filepath.Join(t.TempDir(), langext.MustHexUUID())
|
||||||
|
|
||||||
err := os.WriteFile(tmpFile, IDExampleModels, 0o777)
|
err := os.WriteFile(tmpFile, IDExampleModels1, 0o777)
|
||||||
tst.AssertNoErr(t, err)
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
t.Cleanup(func() { _ = os.Remove(tmpFile) })
|
t.Cleanup(func() { _ = os.Remove(tmpFile) })
|
||||||
|
13
exerr/gin.go
13
exerr/gin.go
@@ -57,6 +57,19 @@ func (ee *ExErr) toJson(depth int, applyExtendListener bool, outputMeta bool) la
|
|||||||
return ginJson
|
return ginJson
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ee *ExErr) ToDefaultAPIJson() (string, error) {
|
||||||
|
|
||||||
|
gjr := json.GoJsonRender{Data: ee.ToAPIJson(true, pkgconfig.ExtendedGinOutput, pkgconfig.IncludeMetaInGinOutput), NilSafeSlices: true, NilSafeMaps: true}
|
||||||
|
|
||||||
|
r, err := gjr.RenderString()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ToAPIJson converts the ExError to a json object
|
// ToAPIJson converts the ExError to a json object
|
||||||
// (the same object as used in the Output(gin) method)
|
// (the same object as used in the Output(gin) method)
|
||||||
//
|
//
|
||||||
|
@@ -18,10 +18,12 @@ type GinWrapper struct {
|
|||||||
engine *gin.Engine
|
engine *gin.Engine
|
||||||
SuppressGinLogs bool
|
SuppressGinLogs bool
|
||||||
|
|
||||||
allowCors bool
|
allowCors bool
|
||||||
ginDebug bool
|
ginDebug bool
|
||||||
bufferBody bool
|
bufferBody bool
|
||||||
requestTimeout time.Duration
|
requestTimeout time.Duration
|
||||||
|
listenerBeforeRequest []func(g *gin.Context)
|
||||||
|
listenerAfterRequest []func(g *gin.Context, resp HTTPResponse)
|
||||||
|
|
||||||
routeSpecs []ginRouteSpec
|
routeSpecs []ginRouteSpec
|
||||||
}
|
}
|
||||||
@@ -33,35 +35,41 @@ type ginRouteSpec struct {
|
|||||||
Handler string
|
Handler string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
AllowCors *bool // Add cors handler to allow all CORS requests on the default http methods
|
||||||
|
GinDebug *bool // Set gin.debug to true (adds more logs)
|
||||||
|
BufferBody *bool // Buffers the input body stream, this way the ginext error handler can later include the whole request body
|
||||||
|
Timeout *time.Duration // The default handler timeout
|
||||||
|
ListenerBeforeRequest []func(g *gin.Context) // Register listener that are called before the handler method
|
||||||
|
ListenerAfterRequest []func(g *gin.Context, resp HTTPResponse) // Register listener that are called after the handler method
|
||||||
|
}
|
||||||
|
|
||||||
// NewEngine creates a new (wrapped) ginEngine
|
// NewEngine creates a new (wrapped) ginEngine
|
||||||
// Parameters are:
|
func NewEngine(opt Options) *GinWrapper {
|
||||||
// - [allowCors] Add cors handler to allow all CORS requests on the default http methods
|
|
||||||
// - [ginDebug] Set gin.debug to true (adds more logs)
|
|
||||||
// - [bufferBody] Buffers the input body stream, this way the ginext error handler can later include the whole request body
|
|
||||||
// - [timeout] The default handler timeout
|
|
||||||
func NewEngine(allowCors bool, ginDebug bool, bufferBody bool, timeout time.Duration) *GinWrapper {
|
|
||||||
engine := gin.New()
|
engine := gin.New()
|
||||||
|
|
||||||
wrapper := &GinWrapper{
|
wrapper := &GinWrapper{
|
||||||
engine: engine,
|
engine: engine,
|
||||||
SuppressGinLogs: false,
|
SuppressGinLogs: false,
|
||||||
allowCors: allowCors,
|
allowCors: langext.Coalesce(opt.AllowCors, false),
|
||||||
ginDebug: ginDebug,
|
ginDebug: langext.Coalesce(opt.GinDebug, true),
|
||||||
bufferBody: bufferBody,
|
bufferBody: langext.Coalesce(opt.BufferBody, false),
|
||||||
requestTimeout: timeout,
|
requestTimeout: langext.Coalesce(opt.Timeout, 24*time.Hour),
|
||||||
|
listenerBeforeRequest: opt.ListenerBeforeRequest,
|
||||||
|
listenerAfterRequest: opt.ListenerAfterRequest,
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.RedirectFixedPath = false
|
engine.RedirectFixedPath = false
|
||||||
engine.RedirectTrailingSlash = false
|
engine.RedirectTrailingSlash = false
|
||||||
|
|
||||||
if allowCors {
|
if wrapper.allowCors {
|
||||||
engine.Use(CorsMiddleware())
|
engine.Use(CorsMiddleware())
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not debug-print routes
|
// do not debug-print routes
|
||||||
gin.DebugPrintRouteFunc = func(_, _, _ string, _ int) {}
|
gin.DebugPrintRouteFunc = func(_, _, _ string, _ int) {}
|
||||||
|
|
||||||
if !ginDebug {
|
if !wrapper.ginDebug {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
||||||
ginlogger := gin.Logger()
|
ginlogger := gin.Logger()
|
||||||
@@ -129,7 +137,7 @@ func (w *GinWrapper) DebugPrintRoutes() {
|
|||||||
spec.Method,
|
spec.Method,
|
||||||
spec.URL,
|
spec.URL,
|
||||||
strings.Join(langext.ArrMap(spec.Middlewares, w.cleanMiddlewareName), " -> "),
|
strings.Join(langext.ArrMap(spec.Middlewares, w.cleanMiddlewareName), " -> "),
|
||||||
spec.Handler,
|
w.cleanMiddlewareName(spec.Handler),
|
||||||
}
|
}
|
||||||
|
|
||||||
lines = append(lines, line)
|
lines = append(lines, line)
|
||||||
@@ -140,14 +148,17 @@ func (w *GinWrapper) DebugPrintRoutes() {
|
|||||||
pad[3] = mathext.Max(pad[3], len(line[3]))
|
pad[3] = mathext.Max(pad[3], len(line[3]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Gin-Routes:\n")
|
||||||
|
fmt.Printf("{\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
|
|
||||||
fmt.Printf("Gin-Route: %s %s --> %s --> %s\n",
|
fmt.Printf(" %s %s --> %s --> %s\n",
|
||||||
langext.StrPadRight("["+line[0]+"]", " ", pad[0]+2),
|
langext.StrPadRight("["+line[0]+"]", " ", pad[0]+2),
|
||||||
langext.StrPadRight(line[1], " ", pad[1]),
|
langext.StrPadRight(line[1], " ", pad[1]),
|
||||||
langext.StrPadRight(line[2], " ", pad[2]),
|
langext.StrPadRight(line[2], " ", pad[2]),
|
||||||
langext.StrPadRight(line[3], " ", pad[3]))
|
langext.StrPadRight(line[3], " ", pad[3]))
|
||||||
}
|
}
|
||||||
|
fmt.Printf("}\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *GinWrapper) cleanMiddlewareName(fname string) string {
|
func (w *GinWrapper) cleanMiddlewareName(fname string) string {
|
||||||
@@ -165,7 +176,7 @@ func (w *GinWrapper) cleanMiddlewareName(fname string) string {
|
|||||||
fname = "[BodyBuffer]"
|
fname = "[BodyBuffer]"
|
||||||
}
|
}
|
||||||
|
|
||||||
skipPrefixes := []string{"api.(*Handler).", "api."}
|
skipPrefixes := []string{"api.(*Handler).", "api.", "ginext.", "handler.", "admin-app.", "employee-app.", "employer-app."}
|
||||||
for _, pfx := range skipPrefixes {
|
for _, pfx := range skipPrefixes {
|
||||||
if strings.HasPrefix(fname, pfx) {
|
if strings.HasPrefix(fname, pfx) {
|
||||||
fname = fname[len(pfx):]
|
fname = fname[len(pfx):]
|
||||||
|
@@ -20,6 +20,10 @@ func Wrap(w *GinWrapper, fn WHandlerFunc) gin.HandlerFunc {
|
|||||||
persistantData: &preContextData{},
|
persistantData: &preContextData{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, lstr := range w.listenerBeforeRequest {
|
||||||
|
lstr(g)
|
||||||
|
}
|
||||||
|
|
||||||
wrap, stackTrace, panicObj := callPanicSafe(fn, pctx)
|
wrap, stackTrace, panicObj := callPanicSafe(fn, pctx)
|
||||||
if panicObj != nil {
|
if panicObj != nil {
|
||||||
|
|
||||||
@@ -45,6 +49,10 @@ func Wrap(w *GinWrapper, fn WHandlerFunc) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, lstr := range w.listenerAfterRequest {
|
||||||
|
lstr(g, wrap)
|
||||||
|
}
|
||||||
|
|
||||||
if reqctx.Err() == nil {
|
if reqctx.Err() == nil {
|
||||||
wrap.Write(g)
|
wrap.Write(g)
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,8 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"gogs.mikescher.com/BlackForestBytes/goext/exerr"
|
"gogs.mikescher.com/BlackForestBytes/goext/exerr"
|
||||||
json "gogs.mikescher.com/BlackForestBytes/goext/gojson"
|
json "gogs.mikescher.com/BlackForestBytes/goext/gojson"
|
||||||
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type headerval struct {
|
type headerval struct {
|
||||||
@@ -18,21 +20,34 @@ type HTTPResponse interface {
|
|||||||
IsSuccess() bool
|
IsSuccess() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InspectableHTTPResponse interface {
|
||||||
|
HTTPResponse
|
||||||
|
|
||||||
|
Statuscode() int
|
||||||
|
BodyString(g *gin.Context) *string
|
||||||
|
ContentType() string
|
||||||
|
Headers() []string
|
||||||
|
}
|
||||||
|
|
||||||
type jsonHTTPResponse struct {
|
type jsonHTTPResponse struct {
|
||||||
statusCode int
|
statusCode int
|
||||||
data any
|
data any
|
||||||
headers []headerval
|
headers []headerval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j jsonHTTPResponse) Write(g *gin.Context) {
|
func (j jsonHTTPResponse) jsonRenderer(g *gin.Context) json.GoJsonRender {
|
||||||
for _, v := range j.headers {
|
|
||||||
g.Header(v.Key, v.Val)
|
|
||||||
}
|
|
||||||
var f *string
|
var f *string
|
||||||
if jsonfilter := g.GetString("goext.jsonfilter"); jsonfilter != "" {
|
if jsonfilter := g.GetString("goext.jsonfilter"); jsonfilter != "" {
|
||||||
f = &jsonfilter
|
f = &jsonfilter
|
||||||
}
|
}
|
||||||
g.Render(j.statusCode, json.GoJsonRender{Data: j.data, NilSafeSlices: true, NilSafeMaps: true, Filter: f})
|
return json.GoJsonRender{Data: j.data, NilSafeSlices: true, NilSafeMaps: true, Filter: f}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsonHTTPResponse) Write(g *gin.Context) {
|
||||||
|
for _, v := range j.headers {
|
||||||
|
g.Header(v.Key, v.Val)
|
||||||
|
}
|
||||||
|
g.Render(j.statusCode, j.jsonRenderer(g))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j jsonHTTPResponse) WithHeader(k string, v string) HTTPResponse {
|
func (j jsonHTTPResponse) WithHeader(k string, v string) HTTPResponse {
|
||||||
@@ -44,6 +59,26 @@ func (j jsonHTTPResponse) IsSuccess() bool {
|
|||||||
return j.statusCode >= 200 && j.statusCode <= 399
|
return j.statusCode >= 200 && j.statusCode <= 399
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j jsonHTTPResponse) Statuscode() int {
|
||||||
|
return j.statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsonHTTPResponse) BodyString(g *gin.Context) *string {
|
||||||
|
if str, err := j.jsonRenderer(g).RenderString(); err == nil {
|
||||||
|
return &str
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsonHTTPResponse) ContentType() string {
|
||||||
|
return "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsonHTTPResponse) Headers() []string {
|
||||||
|
return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val })
|
||||||
|
}
|
||||||
|
|
||||||
type emptyHTTPResponse struct {
|
type emptyHTTPResponse struct {
|
||||||
statusCode int
|
statusCode int
|
||||||
headers []headerval
|
headers []headerval
|
||||||
@@ -65,6 +100,22 @@ func (j emptyHTTPResponse) IsSuccess() bool {
|
|||||||
return j.statusCode >= 200 && j.statusCode <= 399
|
return j.statusCode >= 200 && j.statusCode <= 399
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j emptyHTTPResponse) Statuscode() int {
|
||||||
|
return j.statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j emptyHTTPResponse) BodyString(*gin.Context) *string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j emptyHTTPResponse) ContentType() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j emptyHTTPResponse) Headers() []string {
|
||||||
|
return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val })
|
||||||
|
}
|
||||||
|
|
||||||
type textHTTPResponse struct {
|
type textHTTPResponse struct {
|
||||||
statusCode int
|
statusCode int
|
||||||
data string
|
data string
|
||||||
@@ -87,6 +138,22 @@ func (j textHTTPResponse) IsSuccess() bool {
|
|||||||
return j.statusCode >= 200 && j.statusCode <= 399
|
return j.statusCode >= 200 && j.statusCode <= 399
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j textHTTPResponse) Statuscode() int {
|
||||||
|
return j.statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j textHTTPResponse) BodyString(*gin.Context) *string {
|
||||||
|
return langext.Ptr(j.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j textHTTPResponse) ContentType() string {
|
||||||
|
return "text/plain"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j textHTTPResponse) Headers() []string {
|
||||||
|
return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val })
|
||||||
|
}
|
||||||
|
|
||||||
type dataHTTPResponse struct {
|
type dataHTTPResponse struct {
|
||||||
statusCode int
|
statusCode int
|
||||||
data []byte
|
data []byte
|
||||||
@@ -110,6 +177,22 @@ func (j dataHTTPResponse) IsSuccess() bool {
|
|||||||
return j.statusCode >= 200 && j.statusCode <= 399
|
return j.statusCode >= 200 && j.statusCode <= 399
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j dataHTTPResponse) Statuscode() int {
|
||||||
|
return j.statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j dataHTTPResponse) BodyString(*gin.Context) *string {
|
||||||
|
return langext.Ptr(string(j.data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j dataHTTPResponse) ContentType() string {
|
||||||
|
return j.contentType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j dataHTTPResponse) Headers() []string {
|
||||||
|
return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val })
|
||||||
|
}
|
||||||
|
|
||||||
type fileHTTPResponse struct {
|
type fileHTTPResponse struct {
|
||||||
mimetype string
|
mimetype string
|
||||||
filepath string
|
filepath string
|
||||||
@@ -138,6 +221,26 @@ func (j fileHTTPResponse) IsSuccess() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j fileHTTPResponse) Statuscode() int {
|
||||||
|
return 200
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j fileHTTPResponse) BodyString(*gin.Context) *string {
|
||||||
|
data, err := os.ReadFile(j.filepath)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return langext.Ptr(string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j fileHTTPResponse) ContentType() string {
|
||||||
|
return j.mimetype
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j fileHTTPResponse) Headers() []string {
|
||||||
|
return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val })
|
||||||
|
}
|
||||||
|
|
||||||
type downloadDataHTTPResponse struct {
|
type downloadDataHTTPResponse struct {
|
||||||
statusCode int
|
statusCode int
|
||||||
mimetype string
|
mimetype string
|
||||||
@@ -167,6 +270,22 @@ func (j downloadDataHTTPResponse) IsSuccess() bool {
|
|||||||
return j.statusCode >= 200 && j.statusCode <= 399
|
return j.statusCode >= 200 && j.statusCode <= 399
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j downloadDataHTTPResponse) Statuscode() int {
|
||||||
|
return j.statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j downloadDataHTTPResponse) BodyString(*gin.Context) *string {
|
||||||
|
return langext.Ptr(string(j.data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j downloadDataHTTPResponse) ContentType() string {
|
||||||
|
return j.mimetype
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j downloadDataHTTPResponse) Headers() []string {
|
||||||
|
return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val })
|
||||||
|
}
|
||||||
|
|
||||||
type redirectHTTPResponse struct {
|
type redirectHTTPResponse struct {
|
||||||
statusCode int
|
statusCode int
|
||||||
url string
|
url string
|
||||||
@@ -186,6 +305,22 @@ func (j redirectHTTPResponse) IsSuccess() bool {
|
|||||||
return j.statusCode >= 200 && j.statusCode <= 399
|
return j.statusCode >= 200 && j.statusCode <= 399
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j redirectHTTPResponse) Statuscode() int {
|
||||||
|
return j.statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j redirectHTTPResponse) BodyString(*gin.Context) *string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j redirectHTTPResponse) ContentType() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j redirectHTTPResponse) Headers() []string {
|
||||||
|
return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val })
|
||||||
|
}
|
||||||
|
|
||||||
type jsonAPIErrResponse struct {
|
type jsonAPIErrResponse struct {
|
||||||
err *exerr.ExErr
|
err *exerr.ExErr
|
||||||
headers []headerval
|
headers []headerval
|
||||||
@@ -204,6 +339,26 @@ func (j jsonAPIErrResponse) IsSuccess() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j jsonAPIErrResponse) Statuscode() int {
|
||||||
|
return langext.Coalesce(j.err.RecursiveStatuscode(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsonAPIErrResponse) BodyString(*gin.Context) *string {
|
||||||
|
if str, err := j.err.ToDefaultAPIJson(); err == nil {
|
||||||
|
return &str
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsonAPIErrResponse) ContentType() string {
|
||||||
|
return "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsonAPIErrResponse) Headers() []string {
|
||||||
|
return langext.ArrMap(j.headers, func(v headerval) string { return v.Key + "=" + v.Val })
|
||||||
|
}
|
||||||
|
|
||||||
func Status(sc int) HTTPResponse {
|
func Status(sc int) HTTPResponse {
|
||||||
return &emptyHTTPResponse{statusCode: sc}
|
return &emptyHTTPResponse{statusCode: sc}
|
||||||
}
|
}
|
||||||
|
6
go.mod
6
go.mod
@@ -8,7 +8,7 @@ require (
|
|||||||
github.com/rs/xid v1.5.0
|
github.com/rs/xid v1.5.0
|
||||||
github.com/rs/zerolog v1.31.0
|
github.com/rs/zerolog v1.31.0
|
||||||
go.mongodb.org/mongo-driver v1.13.1
|
go.mongodb.org/mongo-driver v1.13.1
|
||||||
golang.org/x/crypto v0.16.0
|
golang.org/x/crypto v0.17.0
|
||||||
golang.org/x/sys v0.15.0
|
golang.org/x/sys v0.15.0
|
||||||
golang.org/x/term v0.15.0
|
golang.org/x/term v0.15.0
|
||||||
)
|
)
|
||||||
@@ -33,7 +33,7 @@ require (
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
@@ -44,6 +44,6 @@ require (
|
|||||||
golang.org/x/net v0.19.0 // indirect
|
golang.org/x/net v0.19.0 // indirect
|
||||||
golang.org/x/sync v0.5.0 // indirect
|
golang.org/x/sync v0.5.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
google.golang.org/protobuf v1.32.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
6
go.sum
6
go.sum
@@ -88,6 +88,8 @@ github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8
|
|||||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
@@ -143,6 +145,8 @@ golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
|||||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||||
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
||||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
|
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||||
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@@ -207,6 +211,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
|||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||||
|
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
package goext
|
package goext
|
||||||
|
|
||||||
const GoextVersion = "0.0.339"
|
const GoextVersion = "0.0.350"
|
||||||
|
|
||||||
const GoextVersionTimestamp = "2023-12-07T10:54:36+0100"
|
const GoextVersionTimestamp = "2023-12-29T18:06:45+0100"
|
||||||
|
@@ -37,6 +37,14 @@ func (r GoJsonRender) Render(w http.ResponseWriter) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r GoJsonRender) RenderString() (string, error) {
|
||||||
|
jsonBytes, err := MarshalSafeCollections(r.Data, r.NilSafeSlices, r.NilSafeMaps, r.Indent, r.Filter)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(jsonBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r GoJsonRender) WriteContentType(w http.ResponseWriter) {
|
func (r GoJsonRender) WriteContentType(w http.ResponseWriter) {
|
||||||
header := w.Header()
|
header := w.Header()
|
||||||
if val := header["Content-Type"]; len(val) == 0 {
|
if val := header["Content-Type"]; len(val) == 0 {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package googleapi
|
package googleapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"gogs.mikescher.com/BlackForestBytes/goext/tst"
|
"gogs.mikescher.com/BlackForestBytes/goext/tst"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -18,7 +17,7 @@ func TestEncodeMimeMail(t *testing.T) {
|
|||||||
MailBody{Plain: "Plain Text"},
|
MailBody{Plain: "Plain Text"},
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
fmt.Printf("%s\n\n", mail)
|
verifyMime(mail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncodeMimeMail2(t *testing.T) {
|
func TestEncodeMimeMail2(t *testing.T) {
|
||||||
@@ -35,7 +34,7 @@ func TestEncodeMimeMail2(t *testing.T) {
|
|||||||
},
|
},
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
fmt.Printf("%s\n\n", mail)
|
verifyMime(mail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncodeMimeMail3(t *testing.T) {
|
func TestEncodeMimeMail3(t *testing.T) {
|
||||||
@@ -53,12 +52,12 @@ func TestEncodeMimeMail3(t *testing.T) {
|
|||||||
{Data: []byte("HelloWorld"), Filename: "test.txt", IsInline: false, ContentType: "text/plain"},
|
{Data: []byte("HelloWorld"), Filename: "test.txt", IsInline: false, ContentType: "text/plain"},
|
||||||
})
|
})
|
||||||
|
|
||||||
fmt.Printf("%s\n\n", mail)
|
verifyMime(mail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncodeMimeMail4(t *testing.T) {
|
func TestEncodeMimeMail4(t *testing.T) {
|
||||||
|
|
||||||
b := tst.Must(os.ReadFile("/home/mike/Pictures/Screenshot_20220706_190205.png"))(t)
|
b := tst.Must(os.ReadFile("test_placeholder.png"))(t)
|
||||||
|
|
||||||
mail := encodeMimeMail(
|
mail := encodeMimeMail(
|
||||||
"noreply@heydyno.de",
|
"noreply@heydyno.de",
|
||||||
@@ -73,5 +72,9 @@ func TestEncodeMimeMail4(t *testing.T) {
|
|||||||
{Data: b, Filename: "img.png", IsInline: true, ContentType: "image/png"},
|
{Data: b, Filename: "img.png", IsInline: true, ContentType: "image/png"},
|
||||||
})
|
})
|
||||||
|
|
||||||
fmt.Printf("%s\n\n", mail)
|
verifyMime(mail)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyMime(mail string) {
|
||||||
|
//fmt.Printf("%s\n\n", mail)
|
||||||
}
|
}
|
||||||
|
@@ -127,7 +127,7 @@ func TestSendMail4(t *testing.T) {
|
|||||||
|
|
||||||
gclient := NewGoogleClient(auth)
|
gclient := NewGoogleClient(auth)
|
||||||
|
|
||||||
b := tst.Must(os.ReadFile("/home/mike/Pictures/Screenshot_20220706_190205.png"))(t)
|
b := tst.Must(os.ReadFile("test_placeholder.png"))(t)
|
||||||
|
|
||||||
mail, err := gclient.SendMail(
|
mail, err := gclient.SendMail(
|
||||||
ctx,
|
ctx,
|
||||||
|
BIN
googleapi/test_placeholder.png
Normal file
BIN
googleapi/test_placeholder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@@ -29,6 +29,14 @@ func ArrToMap[T comparable, V any](a []V, keyfunc func(V) T) map[T]V {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ArrToKVMap[T any, K comparable, V any](a []T, keyfunc func(T) K, valfunc func(T) V) map[K]V {
|
||||||
|
result := make(map[K]V, len(a))
|
||||||
|
for _, v := range a {
|
||||||
|
result[keyfunc(v)] = valfunc(v)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func ArrToSet[T comparable](a []T) map[T]bool {
|
func ArrToSet[T comparable](a []T) map[T]bool {
|
||||||
result := make(map[T]bool, len(a))
|
result := make(map[T]bool, len(a))
|
||||||
for _, v := range a {
|
for _, v := range a {
|
||||||
|
@@ -10,10 +10,23 @@ var PTrue = Ptr(true)
|
|||||||
// PFalse := &false
|
// PFalse := &false
|
||||||
var PFalse = Ptr(false)
|
var PFalse = Ptr(false)
|
||||||
|
|
||||||
|
// PNil := &nil
|
||||||
|
var PNil = Ptr[any](nil)
|
||||||
|
|
||||||
func Ptr[T any](v T) *T {
|
func Ptr[T any](v T) *T {
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DblPtr[T any](v T) **T {
|
||||||
|
v_ := &v
|
||||||
|
return &v_
|
||||||
|
}
|
||||||
|
|
||||||
|
func DblPtrNil[T any]() **T {
|
||||||
|
var v *T = nil
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
func PtrInt32(v int32) *int32 {
|
func PtrInt32(v int32) *int32 {
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
package pagination
|
package pagination
|
||||||
|
|
||||||
type Pagination struct {
|
type Pagination struct {
|
||||||
Page int `json:"page"` // page (first page == 1)
|
Page int `json:"page"` // page (first page == 1)
|
||||||
Limit int `json:"limit"` // max-page-size
|
Limit int `json:"limit"` // max-page-size
|
||||||
TotalPages int `json:"totalPages"` // total page-count
|
TotalPages int `json:"totalPages"` // total page-count
|
||||||
TotalItems int `json:"totalItems"` // total items-count
|
TotalItems int `json:"totalItems"` // total items-count
|
||||||
CurrentPageCount int `json:"currntPageCount"` // item-count in current page ( == len(data) )
|
CurrentPageCount int `json:"currentPageCount"` // item-count in current page ( == len(data) )
|
||||||
}
|
}
|
||||||
|
|
||||||
func CalcPaginationTotalPages(totalItems int, limit int) int {
|
func CalcPaginationTotalPages(totalItems int, limit int) int {
|
||||||
|
32
sq/hasher.go
32
sq/hasher.go
@@ -13,7 +13,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HashSqliteSchema(ctx context.Context, schemaStr string) (string, error) {
|
// HashMattnSqliteSchema
|
||||||
|
// use if github.com/glebarez/go-sqlite
|
||||||
|
func HashMattnSqliteSchema(ctx context.Context, schemaStr string) (string, error) {
|
||||||
dbdir := os.TempDir()
|
dbdir := os.TempDir()
|
||||||
dbfile1 := filepath.Join(dbdir, langext.MustHexUUID()+".sqlite3")
|
dbfile1 := filepath.Join(dbdir, langext.MustHexUUID()+".sqlite3")
|
||||||
|
|
||||||
@@ -39,6 +41,34 @@ func HashSqliteSchema(ctx context.Context, schemaStr string) (string, error) {
|
|||||||
return HashSqliteDatabase(ctx, db)
|
return HashSqliteDatabase(ctx, db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HashGoSqliteSchema
|
||||||
|
// use if mattn/go-sqlite3
|
||||||
|
func HashGoSqliteSchema(ctx context.Context, schemaStr string) (string, error) {
|
||||||
|
dbdir := os.TempDir()
|
||||||
|
dbfile1 := filepath.Join(dbdir, langext.MustHexUUID()+".sqlite3")
|
||||||
|
|
||||||
|
err := os.MkdirAll(dbdir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("file:%s?_pragma=journal_mode(%s)&_pragma=timeout(%d)&_pragma=foreign_keys(%s)&_pragma=busy_timeout(%d)", dbfile1, "DELETE", 1000, "true", 1000)
|
||||||
|
|
||||||
|
xdb, err := sqlx.Open("sqlite", url)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
db := NewDB(xdb)
|
||||||
|
|
||||||
|
_, err = db.Exec(ctx, schemaStr, PP{})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return HashSqliteDatabase(ctx, db)
|
||||||
|
}
|
||||||
|
|
||||||
func HashSqliteDatabase(ctx context.Context, db Queryable) (string, error) {
|
func HashSqliteDatabase(ctx context.Context, db Queryable) (string, error) {
|
||||||
ss, err := CreateSqliteDatabaseSchemaString(ctx, db)
|
ss, err := CreateSqliteDatabaseSchemaString(ctx, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Reference in New Issue
Block a user