-
Notifications
You must be signed in to change notification settings - Fork 0
foundation/serialization impl #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
afeae42
3764104
20803f0
a11fc16
992c913
a9178bf
11cbc5d
fdea843
a0f532e
c816f03
6381c6c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| name: CI | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ main, 'release/v*' ] | ||
| pull_request: | ||
| branches: [ main, 'release/v*' ] | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Set up Go | ||
| uses: actions/setup-go@v5 | ||
| with: | ||
| go-version: '1.22' | ||
| - name: Install dependencies | ||
| run: go mod tidy | ||
| - name: Run tests | ||
| run: make check-coverage |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| name: Release | ||
|
|
||
| on: | ||
| push: | ||
| tags: | ||
| - 'v*.*.*' | ||
|
|
||
| jobs: | ||
| release: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Set up Go | ||
| uses: actions/setup-go@v5 | ||
| with: | ||
| go-version: '1.22' | ||
| - name: Install dependencies | ||
| run: go mod tidy | ||
| - name: Run tests | ||
| run: make check-coverage | ||
| - name: Announce release | ||
| run: echo "Release ${{ github.ref_name }} is ready." |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # Go build and test artifacts | ||
| bin/ | ||
| *.exe | ||
| *.exe~ | ||
| *.dll | ||
| *.so | ||
| *.dylib | ||
| *.test | ||
| coverage.out | ||
|
|
||
| # IDE/editor files | ||
| .vscode/ | ||
| .idea/ | ||
| *.swp | ||
| *.swo | ||
| .DS_Store | ||
|
|
||
| # Dependency directories | ||
| vendor/ | ||
|
|
||
| # Logs | ||
| *.log |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| COVERAGE_FILE=coverage.out | ||
| MIN_COVERAGE=80 | ||
|
|
||
| .PHONY: test coverage check-coverage clean release | ||
|
|
||
| test: | ||
| go test ./... | ||
|
|
||
| coverage: | ||
| go test -coverprofile=$(COVERAGE_FILE) ./... | ||
| go tool cover -func=$(COVERAGE_FILE) | ||
|
|
||
| check-coverage: | ||
| go test -coverprofile=$(COVERAGE_FILE) ./... | ||
| go tool cover -func=$(COVERAGE_FILE) | ||
| @cov=$(shell go tool cover -func=$(COVERAGE_FILE) | awk '/^total:/ {print $$3+0}') ; \ | ||
| if [ $$(echo "$$cov < $(MIN_COVERAGE)" | bc) -eq 1 ]; then \ | ||
| echo "FAIL: coverage below $(MIN_COVERAGE)% ($$cov%)"; exit 1; \ | ||
| else \ | ||
| echo "PASS: coverage is $$cov%"; \ | ||
| fi | ||
|
Comment on lines
+14
to
+21
|
||
|
|
||
| clean: | ||
| rm -f $(COVERAGE_FILE) | ||
|
|
||
| release: | ||
| git tag v$(VERSION) | ||
| git push origin v$(VERSION) | ||
|
Comment on lines
+26
to
+28
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,17 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package foundation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package foundation | |
| package serialization |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FromData unmarshals into &j.Value (a *interface{}), which causes encoding/json to replace Value with generic map[string]any/[]any instead of populating the concrete destination. This breaks the intended round-trip and will make the test’s *testStruct type assertion fail. Unmarshal into the concrete destination stored in j.Value (and require it to be a non-nil pointer), or change the type to a generic JSONSerializable[T any] that holds *T so the destination type is explicit.
| ) | |
| type JSONSerializable struct { | |
| Value interface{} | |
| } | |
| func (j *JSONSerializable) ToData() ([]byte, error) { | |
| return json.Marshal(j.Value) | |
| } | |
| func (j *JSONSerializable) FromData(data []byte) error { | |
| return json.Unmarshal(data, &j.Value) | |
| "fmt" | |
| "reflect" | |
| ) | |
| type JSONSerializable struct { | |
| Value interface{} | |
| } | |
| func (j *JSONSerializable) ToData() ([]byte, error) { | |
| return json.Marshal(j.Value) | |
| } | |
| func (j *JSONSerializable) FromData(data []byte) error { | |
| if j == nil { | |
| return fmt.Errorf("JSONSerializable receiver is nil") | |
| } | |
| if j.Value == nil { | |
| return fmt.Errorf("JSONSerializable.Value must be a non-nil pointer; got <nil>") | |
| } | |
| v := reflect.ValueOf(j.Value) | |
| if v.Kind() != reflect.Ptr || v.IsNil() { | |
| return fmt.Errorf("JSONSerializable.Value must be a non-nil pointer; got %T", j.Value) | |
| } | |
| return json.Unmarshal(data, j.Value) |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package foundation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
gbrennon marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "testing" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type testStruct struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Name string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Age int | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func TestJSONSerializable_ToDataAndFromData(t *testing.T) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| original := &JSONSerializable{Value: testStruct{Name: "Alice", Age: 30}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data, err := original.ToData() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.Fatalf("ToData failed: %v", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| copy := &JSONSerializable{Value: &testStruct{}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| err = copy.FromData(data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.Fatalf("FromData failed: %v", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result, ok := copy.Value.(*testStruct) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !ok { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.Fatalf("Type assertion failed") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if result.Name != "Alice" || result.Age != 30 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.Errorf("Expected Alice, 30; got %s, %d", result.Name, result.Age) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+4
to
+31
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "testing" | |
| ) | |
| type testStruct struct { | |
| Name string | |
| Age int | |
| } | |
| func TestJSONSerializable_ToDataAndFromData(t *testing.T) { | |
| original := &JSONSerializable{Value: testStruct{Name: "Alice", Age: 30}} | |
| data, err := original.ToData() | |
| if err != nil { | |
| t.Fatalf("ToData failed: %v", err) | |
| } | |
| copy := &JSONSerializable{Value: &testStruct{}} | |
| err = copy.FromData(data) | |
| if err != nil { | |
| t.Fatalf("FromData failed: %v", err) | |
| } | |
| result, ok := copy.Value.(*testStruct) | |
| if !ok { | |
| t.Fatalf("Type assertion failed") | |
| } | |
| if result.Name != "Alice" || result.Age != 30 { | |
| t.Errorf("Expected Alice, 30; got %s, %d", result.Name, result.Age) | |
| } | |
| "testing" | |
| ) | |
| type testStruct struct { | |
| Name string | |
| Age int | |
| } | |
| func TestJSONSerializable_ToDataAndFromData(t *testing.T) { | |
| original := &JSONSerializable{Value: testStruct{Name: "Alice", Age: 30}} | |
| data, err := original.ToData() | |
| if err != nil { | |
| t.Fatalf("ToData failed: %v", err) | |
| } | |
| copy := &JSONSerializable{Value: &testStruct{}} | |
| err = copy.FromData(data) | |
| if err != nil { | |
| t.Fatalf("FromData failed: %v", err) | |
| } | |
| result, ok := copy.Value.(*testStruct) | |
| if !ok { | |
| t.Fatalf("Type assertion failed") | |
| } | |
| if result.Name != "Alice" || result.Age != 30 { | |
| t.Errorf("Expected Alice, 30; got %s, %d", result.Name, result.Age) | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package serialization | ||
|
|
||
| type Serializable interface { | ||
| ToData() ([]byte, error) | ||
| FromData([]byte) error | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| #!/bin/bash | ||
| set -e | ||
|
|
||
| # Get latest tag from remote | ||
| git fetch --tags | ||
| LATEST_TAG=$(git tag --sort=-v:refname | head -n1) | ||
|
|
||
| if [ -z "$LATEST_TAG" ]; then | ||
| echo "0.1.0" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Parse version | ||
| IFS='.' read -r MAJOR MINOR PATCH <<< "${LATEST_TAG#v}" | ||
|
|
||
| # Bump patch version by default | ||
| PATCH=$((PATCH+1)) | ||
| NEXT_VERSION="$MAJOR.$MINOR.$PATCH" | ||
| echo "$NEXT_VERSION" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| #!/bin/bash | ||
| # | ||
| # Automated release script for the Forging toolkit | ||
| # | ||
| # Usage: | ||
| # ./scripts/release.sh [--dry-run] | ||
| # | ||
| # - Must be run from the main branch after all implementation PRs are merged. | ||
| # - Determines the next version automatically. | ||
| # - Generates and commits the changelog. | ||
| # - Creates a release branch from main. | ||
| # - Pushes the release branch to origin. | ||
| # - Opens a PR to main with the changelog as the PR body (requires GitHub CLI). | ||
| # - In --dry-run mode, prints actions without making changes. | ||
| # | ||
| # Prerequisites: | ||
| # - git-chglog (https://github.com/git-chglog/git-chglog) | ||
| # - GitHub CLI (https://cli.github.com/) | ||
| # | ||
| # Example: | ||
| # ./scripts/release.sh # Real release flow | ||
| # ./scripts/release.sh --dry-run # Preview actions only | ||
| set -e | ||
|
|
||
| DRY_RUN=false | ||
| while [[ "$1" =~ ^- ]]; do | ||
| case $1 in | ||
| --dry-run) DRY_RUN=true ;; | ||
| *) echo "Unknown option: $1"; exit 1 ;; | ||
| esac | ||
| shift | ||
| done | ||
|
|
||
| # Check for required tools | ||
| gh_installed=$(command -v gh || true) | ||
| chglog_installed=$(command -v git-chglog || true) | ||
| if [ -z "$gh_installed" ]; then | ||
| echo "Error: GitHub CLI (gh) is required." | ||
| exit 1 | ||
| fi | ||
| if [ -z "$chglog_installed" ]; then | ||
| echo "Error: git-chglog is required." | ||
| exit 1 | ||
| fi | ||
|
|
||
| CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) | ||
| if [ "$CURRENT_BRANCH" != "main" ]; then | ||
| echo "Error: Release script must be run from the main branch." | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "Syncing with remote..." | ||
| $DRY_RUN || git pull | ||
|
|
||
| VERSION=$(./scripts/next_version.sh) | ||
| BRANCH="release/v$VERSION" | ||
|
|
||
| echo "Creating release branch $BRANCH..." | ||
| $DRY_RUN || git checkout -b "$BRANCH" | ||
|
|
||
| echo "Generating changelog for v$VERSION..." | ||
| $DRY_RUN || git-chglog -o CHANGELOG.md "$VERSION" | ||
|
|
||
| $DRY_RUN || git add CHANGELOG.md | ||
| $DRY_RUN || git commit -m "chore: update CHANGELOG for v$VERSION" | ||
|
|
||
| $DRY_RUN || git push origin "$BRANCH" | ||
|
|
||
| CHANGELOG_BODY=$($DRY_RUN && echo "[DRY RUN]" || awk '/^## /{flag=1;next}/^$/{flag=0}flag' CHANGELOG.md | head -n -1) | ||
|
|
||
| echo "Opening PR to main..." | ||
| $DRY_RUN || gh pr create --base main --head "$BRANCH" --title "Release v$VERSION" --body "$CHANGELOG_BODY" | ||
|
|
||
| echo "Release branch and PR created for v$VERSION. Merge the PR to main, then tag the release." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
check-coveragetarget depends onbcbeing installed (echo ... | bc). That tool isn’t available by default in some environments/CI images, which can make coverage checks fail unexpectedly. Consider doing the comparison inawk(or Go) to avoid external dependencies, or documentbcas a prerequisite.