Compare commits

...
4 Commits
Author SHA1 Message Date
warkanum cdd066dafe fix(sqltypes): serialize SqlTimeStamp as RFC3339
Release / test (push) Successful in 2m22s
Release / release (push) Successful in 8m23s
Release / pkg-aur (push) Successful in 48s
Release / pkg-deb (push) Successful in 1m31s
Release / pkg-rpm (push) Successful in 1m37s
Marshal JSON/YAML/XML and driver Value now emit time.RFC3339 instead of
the offset-less "2006-01-02T15:04:05" layout; zero-time guards compare
against the RFC3339 zero value. Parsing is unchanged and still accepts
bare datetimes.
2026-09-03 22:12:36 +02:00
warkanum 5c25f333b7 fix(go.mod): update Go version to 1.25.13 2026-09-03 21:39:56 +02:00
warkanum 77e6e72f5a fix(ci): don't capture tool-download noise as a formatting diff
The gofumpt check merged stderr into the diff variable, so 'go:
downloading ...' lines made it always fail. Move tool installation into
its own step (adding GOPATH/bin to PATH) and capture only stdout.
2026-09-03 21:33:12 +02:00
warkanum 4d4bc09b86 fix(ci): run lint tools via 'go run' to match the toolchain
Installed staticcheck/govulncheck binaries can fail when built with an
older Go than the module targets, and GOPATH/bin is not always on PATH.
Compile them on demand with 'go run <tool>@latest' in both the workflow
and the Makefile.
2026-09-03 21:25:27 +02:00
6 changed files with 29 additions and 30 deletions
+10 -8
View File
@@ -23,10 +23,16 @@ jobs:
- name: go vet - name: go vet
run: go vet ./... run: go vet ./...
- name: gofumpt (golangci-lint fmt) - name: Install lint tools
run: | run: |
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
diff=$(golangci-lint fmt --diff 2>&1) go install honnef.co/go/tools/cmd/staticcheck@latest
go install golang.org/x/vuln/cmd/govulncheck@latest
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
- name: gofumpt (golangci-lint fmt)
run: |
diff=$(golangci-lint fmt --diff)
if [ -n "$diff" ]; then if [ -n "$diff" ]; then
echo "$diff" echo "$diff"
echo "Formatting issues found. Run: make fmt" echo "Formatting issues found. Run: make fmt"
@@ -34,14 +40,10 @@ jobs:
fi fi
- name: staticcheck - name: staticcheck
run: | run: staticcheck ./...
go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck ./...
- name: govulncheck - name: govulncheck
run: | run: govulncheck ./...
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
- name: Test - name: Test
run: go test ./... run: go test ./...
+8 -11
View File
@@ -14,9 +14,10 @@ GOGET=$(GOCMD) get
GOMOD=$(GOCMD) mod GOMOD=$(GOCMD) mod
GOCLEAN=$(GOCMD) clean GOCLEAN=$(GOCMD) clean
# Resolve Go tool binaries (GOPATH/bin may not be on PATH in CI) # Tool versions (compiled on demand via `go run` so they match the local toolchain)
GOBIN_DIR := $(shell $(GOCMD) env GOPATH)/bin GOLANGCI_LINT = go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
TOOL = $(if $(shell command -v $(1) 2>/dev/null),$(1),$(GOBIN_DIR)/$(1)) STATICCHECK = go run honnef.co/go/tools/cmd/staticcheck@latest
GOVULNCHECK = go run golang.org/x/vuln/cmd/govulncheck@latest
# Version information # Version information
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
@@ -53,24 +54,20 @@ vet: ## Run go vet
fmt: ## Format code (gofumpt + goimports via golangci-lint) fmt: ## Format code (gofumpt + goimports via golangci-lint)
@echo "Formatting..." @echo "Formatting..."
@command -v golangci-lint > /dev/null || test -x $(GOBIN_DIR)/golangci-lint || $(GOCMD) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest $(GOLANGCI_LINT) fmt --config=.golangci.json
$(call TOOL,golangci-lint) fmt --config=.golangci.json
fmt-check: ## Check formatting (gofumpt + goimports via golangci-lint) fmt-check: ## Check formatting (gofumpt + goimports via golangci-lint)
@echo "Checking formatting..." @echo "Checking formatting..."
@command -v golangci-lint > /dev/null || test -x $(GOBIN_DIR)/golangci-lint || $(GOCMD) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest @diff=$$($(GOLANGCI_LINT) fmt --diff --config=.golangci.json); \
@diff=$$($(call TOOL,golangci-lint) fmt --diff --config=.golangci.json 2>&1); \
if [ -n "$$diff" ]; then echo "$$diff"; echo "Run: make fmt"; exit 1; fi if [ -n "$$diff" ]; then echo "$$diff"; echo "Run: make fmt"; exit 1; fi
staticcheck: ## Run staticcheck staticcheck: ## Run staticcheck
@echo "Running staticcheck..." @echo "Running staticcheck..."
@command -v staticcheck > /dev/null || test -x $(GOBIN_DIR)/staticcheck || $(GOCMD) install honnef.co/go/tools/cmd/staticcheck@latest $(STATICCHECK) ./...
$(call TOOL,staticcheck) ./...
govulncheck: ## Run govulncheck govulncheck: ## Run govulncheck
@echo "Running govulncheck..." @echo "Running govulncheck..."
@command -v govulncheck > /dev/null || test -x $(GOBIN_DIR)/govulncheck || $(GOCMD) install golang.org/x/vuln/cmd/govulncheck@latest $(GOVULNCHECK) ./...
$(call TOOL,govulncheck) ./...
build: deps ## Build the binary build: deps ## Build the binary
@echo "Building $(BINARY_NAME) $(VERSION)..." @echo "Building $(BINARY_NAME) $(VERSION)..."
+1 -1
View File
@@ -1,6 +1,6 @@
module git.warky.dev/wdevs/relspecgo module git.warky.dev/wdevs/relspecgo
go 1.25.7 go 1.25.13
require ( require (
github.com/gdamore/tcell/v2 v2.13.9 github.com/gdamore/tcell/v2 v2.13.9
+8 -8
View File
@@ -445,21 +445,21 @@ type (
SqlUUID = SqlNull[uuid.UUID] SqlUUID = SqlNull[uuid.UUID]
) )
// SqlTimeStamp - Timestamp with custom formatting (YYYY-MM-DDTHH:MM:SS). // SqlTimeStamp - Timestamp serialized as RFC3339.
type SqlTimeStamp struct{ SqlNull[time.Time] } type SqlTimeStamp struct{ SqlNull[time.Time] }
func (t SqlTimeStamp) MarshalJSON() ([]byte, error) { func (t SqlTimeStamp) MarshalJSON() ([]byte, error) {
if !t.Valid || t.Val.IsZero() || t.Val.Before(time.Date(0o002, 1, 1, 0, 0, 0, 0, time.UTC)) { if !t.Valid || t.Val.IsZero() || t.Val.Before(time.Date(0o002, 1, 1, 0, 0, 0, 0, time.UTC)) {
return []byte("null"), nil return []byte("null"), nil
} }
return fmt.Appendf(nil, `"%s"`, t.Val.Format("2006-01-02T15:04:05")), nil return fmt.Appendf(nil, `"%s"`, t.Val.Format(time.RFC3339)), nil
} }
func (t *SqlTimeStamp) UnmarshalJSON(b []byte) error { func (t *SqlTimeStamp) UnmarshalJSON(b []byte) error {
if err := t.SqlNull.UnmarshalJSON(b); err != nil { if err := t.SqlNull.UnmarshalJSON(b); err != nil {
return err return err
} }
if t.Valid && (t.Val.IsZero() || t.Val.Format("2006-01-02T15:04:05") == "0001-01-01T00:00:00") { if t.Valid && (t.Val.IsZero() || t.Val.Format(time.RFC3339) == "0001-01-01T00:00:00Z") {
t.Valid = false t.Valid = false
} }
return nil return nil
@@ -469,21 +469,21 @@ func (t SqlTimeStamp) Value() (driver.Value, error) {
if !t.Valid || t.Val.IsZero() || t.Val.Before(time.Date(0o002, 1, 1, 0, 0, 0, 0, time.UTC)) { if !t.Valid || t.Val.IsZero() || t.Val.Before(time.Date(0o002, 1, 1, 0, 0, 0, 0, time.UTC)) {
return nil, nil return nil, nil
} }
return t.Val.Format("2006-01-02T15:04:05"), nil return t.Val.Format(time.RFC3339), nil
} }
func (t SqlTimeStamp) MarshalYAML() (any, error) { func (t SqlTimeStamp) MarshalYAML() (any, error) {
if !t.Valid || t.Val.IsZero() || t.Val.Before(time.Date(0o002, 1, 1, 0, 0, 0, 0, time.UTC)) { if !t.Valid || t.Val.IsZero() || t.Val.Before(time.Date(0o002, 1, 1, 0, 0, 0, 0, time.UTC)) {
return nil, nil return nil, nil
} }
return t.Val.Format("2006-01-02T15:04:05"), nil return t.Val.Format(time.RFC3339), nil
} }
func (t *SqlTimeStamp) UnmarshalYAML(value *yaml.Node) error { func (t *SqlTimeStamp) UnmarshalYAML(value *yaml.Node) error {
if err := t.SqlNull.UnmarshalYAML(value); err != nil { if err := t.SqlNull.UnmarshalYAML(value); err != nil {
return err return err
} }
if t.Valid && (t.Val.IsZero() || t.Val.Format("2006-01-02T15:04:05") == "0001-01-01T00:00:00") { if t.Valid && (t.Val.IsZero() || t.Val.Format(time.RFC3339) == "0001-01-01T00:00:00Z") {
t.Valid = false t.Valid = false
} }
return nil return nil
@@ -493,7 +493,7 @@ func (t SqlTimeStamp) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if !t.Valid || t.Val.IsZero() || t.Val.Before(time.Date(0o002, 1, 1, 0, 0, 0, 0, time.UTC)) { if !t.Valid || t.Val.IsZero() || t.Val.Before(time.Date(0o002, 1, 1, 0, 0, 0, 0, time.UTC)) {
return e.EncodeElement("", start) return e.EncodeElement("", start)
} }
return e.EncodeElement(t.Val.Format("2006-01-02T15:04:05"), start) return e.EncodeElement(t.Val.Format(time.RFC3339), start)
} }
func (t *SqlTimeStamp) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { func (t *SqlTimeStamp) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
@@ -511,7 +511,7 @@ func (t *SqlTimeStamp) UnmarshalXML(d *xml.Decoder, start xml.StartElement) erro
return err return err
} }
t.Val = tm t.Val = tm
t.Valid = !tm.IsZero() && tm.Format("2006-01-02T15:04:05") != "0001-01-01T00:00:00" t.Valid = !tm.IsZero() && tm.Format(time.RFC3339) != "0001-01-01T00:00:00Z"
return nil return nil
} }
+1 -1
View File
@@ -178,7 +178,7 @@ func TestSqlTimeStamp_JSON(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Marshal failed: %v", err) t.Fatalf("Marshal failed: %v", err)
} }
expected := `"2024-01-15T10:30:45"` expected := `"2024-01-15T10:30:45Z"`
if string(data) != expected { if string(data) != expected {
t.Errorf("expected %s, got %s", expected, string(data)) t.Errorf("expected %s, got %s", expected, string(data))
} }
+1 -1
View File
@@ -195,7 +195,7 @@ func TestSqlTimeStamp_YAML(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Marshal failed: %v", err) t.Fatalf("Marshal failed: %v", err)
} }
if string(data) != "2024-06-15T09:30:00\n" { if string(data) != "\"2024-06-15T09:30:00Z\"\n" {
t.Errorf("unexpected YAML: %q", string(data)) t.Errorf("unexpected YAML: %q", string(data))
} }
var ts2 SqlTimeStamp var ts2 SqlTimeStamp