Skip to content
This repository was archived by the owner on Dec 30, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: gomod
directory: /
schedule:
interval: daily
open-pull-requests-limit: 10
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
14 changes: 14 additions & 0 deletions .github/workflows/changelog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: update changelog

on:
release:
types: [released]

permissions: {}

jobs:
changelog:
permissions:
contents: write
secrets: inherit
uses: bavix/.github/.github/workflows/changelog.yml@0.3.3
26 changes: 26 additions & 0 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: golangci-lint
on:
push:
branches:
- master
pull_request:
permissions:
contents: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ '1.25' ]
steps:
- uses: actions/checkout@v5
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: true
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
with:
version: v2.3.0
8 changes: 8 additions & 0 deletions .github/workflows/semgrep.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: semgrep

on:
pull_request:

jobs:
semgrep:
uses: bavix/.github/.github/workflows/go-semgrep.yml@0.3.3
26 changes: 26 additions & 0 deletions .github/workflows/unit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Unit
on:
pull_request:
branches:
- master

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ '1.24', '1.25' ]
steps:
- uses: actions/checkout@v5
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: true
- name: Test with Go
run: go test ./...
- name: Upload Go test results
uses: actions/upload-artifact@v4
with:
name: Go-results-${{ matrix.go-version }}
path: TestResults-${{ matrix.go-version }}.json
35 changes: 35 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
version: "2"
linters:
default: all
disable:
- wsl
- wrapcheck
- revive
settings:
depguard:
rules:
main:
allow:
- $gostd
- github.com
- golang.org
- google.golang.org
lll:
line-length: 140
wsl_v5:
allow-first-in-block: true
allow-whole-block: false
branch-max-lines: 2

formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
settings:
gci:
sections:
- Standard
- Default
- Prefix(github.com/gripmock)
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.PHONY: *

test:
go test -tags mock -race -cover ./...

lint:
go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.3.0 run --color always ${args}

lint-fix:
make lint args=--fix
78 changes: 77 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,77 @@
# types
# Gripmock Types

A package of custom types for [Gripmock](https://github.com/bavix/gripmock) that solves JSON serialization/deserialization problems.

## Problem

In [issue #648](https://github.com/bavix/gripmock/issues/648), a problem was identified with deserializing string time values into `time.Duration`. When trying to use JSON like:

```json
{
"output": {
"delay": "100ms"
}
}
```

The following error occurred:
```
json: cannot unmarshal number { into Go struct field Output.Delay of type time.Duration
```

## Solution

The `types` package provides a custom `Duration` type that correctly handles string time values in JSON.

## Available Types

### Duration

A custom type alias for `time.Duration` that provides JSON marshaling/unmarshaling support for string values.

#### Supported formats

Only string values are supported:
```json
{
"delay": "100ms"
}
```

Valid duration formats:
- `"100ms"` - milliseconds
- `"2s"` - seconds
- `"1m"` - minutes
- `"1h"` - hours
- `"1h30m"` - combined formats

#### Usage

```go
type Output struct {
Delay types.Duration `json:"delay"`
Data map[string]interface{} `json:"data"`
}

var output Output
json.Unmarshal([]byte(`{"delay": "100ms"}`), &output)

// Access the duration value
duration := time.Duration(output.Delay)
```

## Installation

```bash
go get github.com/gripmock/types
```

## Testing

```bash
go test ./...
```

## License

MIT License - see [LICENSE](LICENSE) file.
36 changes: 36 additions & 0 deletions duration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Package types provides custom JSON types for Gripmock.
package types

import (
"encoding/json"
"time"
)

// Duration is a custom type alias for time.Duration that provides
// JSON marshaling/unmarshaling support for string values like "100ms".
type Duration time.Duration

// UnmarshalJSON implements json.Unmarshaler interface.
func (d *Duration) UnmarshalJSON(data []byte) error {
// Try to unmarshal as string first
var s string

err := json.Unmarshal(data, &s)
if err == nil {
duration, err := time.ParseDuration(s)
if err != nil {
return err
}

*d = Duration(duration)

return nil
}

return json.Unmarshal(data, (*time.Duration)(d))
}

// MarshalJSON implements json.Marshaler interface.
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Duration(d).String())
}
78 changes: 78 additions & 0 deletions duration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package types_test

import (
"encoding/json"
"testing"
"time"

"github.com/gripmock/types"
)

func TestDuration_UnmarshalJSON(t *testing.T) {
t.Parallel()

tests := []struct {
name string
input string
want time.Duration
wantErr bool
}{
{
name: "string with ms",
input: `"100ms"`,
want: 100 * time.Millisecond,
wantErr: false,
},
{
name: "string with seconds",
input: `"2s"`,
want: 2 * time.Second,
wantErr: false,
},
{
name: "invalid duration string",
input: `"invalid"`,
want: 0,
wantErr: true,
},
}

for _, testCase := range tests {
// Go 1.22+ runs subtests in parallel safely without copying loop var.
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()

var duration types.Duration

err := json.Unmarshal([]byte(testCase.input), &duration)

if (err != nil) != testCase.wantErr {
t.Errorf("Duration.UnmarshalJSON() error = %v, wantErr %v", err, testCase.wantErr)

return
}

if !testCase.wantErr && time.Duration(duration) != testCase.want {
t.Errorf("Duration.UnmarshalJSON() = %v, want %v", time.Duration(duration), testCase.want)
}
})
}
}

func TestDuration_MarshalJSON(t *testing.T) {
t.Parallel()

duration := types.Duration(100 * time.Millisecond)
expected := `"100ms"`

got, err := json.Marshal(duration)
if err != nil {
t.Errorf("Duration.MarshalJSON() error = %v", err)

return
}

if string(got) != expected {
t.Errorf("Duration.MarshalJSON() = %v, want %v", string(got), expected)
}
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/gripmock/types

go 1.24
Loading