Skip to content

Commit c7411eb

Browse files
author
Dean Karn
authored
Add some Generics (#11)
1 parent 5d992dd commit c7411eb

File tree

14 files changed

+661
-34
lines changed

14 files changed

+661
-34
lines changed

.github/workflows/go.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
test:
99
strategy:
1010
matrix:
11-
go-version: [1.17.x,1.16.x]
11+
go-version: [1.18.x,1.17.x]
1212
os: [ubuntu-latest, macos-latest, windows-latest]
1313
runs-on: ${{ matrix.os }}
1414
steps:

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ test:
44
$(GOCMD) test -cover -race ./...
55

66
bench:
7-
$(GOCMD) test -bench=. -benchmem ./...
7+
$(GOCMD) test -run=NONE -bench=. -benchmem ./...
88

99
.PHONY: linters-install lint test bench

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# pkg
22

3-
![Project status](https://img.shields.io/badge/version-5.3.0-green.svg)
3+
![Project status](https://img.shields.io/badge/version-5.4.0-green.svg)
44
[![Build Status](https://travis-ci.org/go-playground/pkg.svg?branch=master)](https://travis-ci.org/go-playground/pkg)
55
[![Coverage Status](https://coveralls.io/repos/github/go-playground/pkg/badge.svg?branch=master)](https://coveralls.io/github/go-playground/pkg?branch=master)
66
[![GoDoc](https://godoc.org/github.com/go-playground/pkg?status.svg)](https://pkg.go.dev/mod/github.com/go-playground/pkg/v5)

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ require (
55
github.com/go-playground/form/v4 v4.2.0
66
)
77

8-
go 1.17
8+
go 1.18

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
22
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
3-
github.com/go-playground/form/v4 v4.1.1 h1:1T9lGt3WRHuDwT5uwx7RYQZfxVwWZhF0DC1ovKyNnWY=
4-
github.com/go-playground/form/v4 v4.1.1/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U=
53
github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic=
64
github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U=

net/http/helpers.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ func DecodeMultipartForm(r *http.Request, qp QueryParamsOption, maxMemory int64,
216216
// The Content-Type e.g. "application/json" and http method are not checked.
217217
//
218218
// NOTE: when includeQueryParams=true query params will be parsed and included eg. route /user?test=true 'test'
219-
// is added to parsed JSON and replaces any value that may have been present
219+
// is added to parsed JSON and replaces any values that may have been present
220220
func DecodeJSON(r *http.Request, qp QueryParamsOption, maxMemory int64, v interface{}) (err error) {
221221
var body io.Reader = r.Body
222222
if encoding := r.Header.Get(ContentEncoding); encoding == Gzip {
@@ -243,7 +243,7 @@ func DecodeJSON(r *http.Request, qp QueryParamsOption, maxMemory int64, v interf
243243
// The Content-Type e.g. "application/xml" and http method are not checked.
244244
//
245245
// NOTE: when includeQueryParams=true query params will be parsed and included eg. route /user?test=true 'test'
246-
// is added to parsed XML and replaces any value that may have been present
246+
// is added to parsed XML and replaces any values that may have been present
247247
func DecodeXML(r *http.Request, qp QueryParamsOption, maxMemory int64, v interface{}) (err error) {
248248
var body io.Reader = r.Body
249249
if encoding := r.Header.Get(ContentEncoding); encoding == Gzip {
@@ -283,7 +283,7 @@ const (
283283
// This default to parsing query params if includeQueryParams=true and no other content type matches.
284284
//
285285
// NOTE: when includeQueryParams=true query params will be parsed and included eg. route /user?test=true 'test'
286-
// is added to parsed XML and replaces any value that may have been present
286+
// is added to parsed XML and replaces any values that may have been present
287287
func Decode(r *http.Request, qp QueryParamsOption, maxMemory int64, v interface{}) (err error) {
288288
typ := r.Header.Get(ContentType)
289289
if idx := strings.Index(typ, ";"); idx != -1 {

net/http/helpers_test.go

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ func TestDecode(t *testing.T) {
284284

285285
// test Form decode
286286
form := url.Values{}
287-
form.Add("Posted", "value")
287+
form.Add("Posted", "values")
288288

289289
test = new(TestStruct)
290290
r, _ = http.NewRequest(http.MethodPost, "/decode-query?id=13", strings.NewReader(form.Encode()))
@@ -293,7 +293,7 @@ func TestDecode(t *testing.T) {
293293
mux.ServeHTTP(w, r)
294294
Equal(t, w.Code, http.StatusOK)
295295
Equal(t, test.ID, 13)
296-
Equal(t, test.Posted, "value")
296+
Equal(t, test.Posted, "values")
297297
Equal(t, test.MultiPartPosted, "")
298298

299299
test = new(TestStruct)
@@ -303,14 +303,14 @@ func TestDecode(t *testing.T) {
303303
mux.ServeHTTP(w, r)
304304
Equal(t, w.Code, http.StatusOK)
305305
Equal(t, test.ID, 0)
306-
Equal(t, test.Posted, "value")
306+
Equal(t, test.Posted, "values")
307307
Equal(t, test.MultiPartPosted, "")
308308

309309
// test MultipartForm
310310
body := &bytes.Buffer{}
311311
writer := multipart.NewWriter(body)
312312

313-
err := writer.WriteField("MultiPartPosted", "value")
313+
err := writer.WriteField("MultiPartPosted", "values")
314314
Equal(t, err, nil)
315315

316316
// Don't forget to close the multipart writer.
@@ -326,12 +326,12 @@ func TestDecode(t *testing.T) {
326326
Equal(t, w.Code, http.StatusOK)
327327
Equal(t, test.ID, 12)
328328
Equal(t, test.Posted, "")
329-
Equal(t, test.MultiPartPosted, "value")
329+
Equal(t, test.MultiPartPosted, "values")
330330

331331
body = &bytes.Buffer{}
332332
writer = multipart.NewWriter(body)
333333

334-
err = writer.WriteField("MultiPartPosted", "value")
334+
err = writer.WriteField("MultiPartPosted", "values")
335335
Equal(t, err, nil)
336336

337337
// Don't forget to close the multipart writer.
@@ -347,19 +347,19 @@ func TestDecode(t *testing.T) {
347347
Equal(t, w.Code, http.StatusOK)
348348
Equal(t, test.ID, 0)
349349
Equal(t, test.Posted, "")
350-
Equal(t, test.MultiPartPosted, "value")
350+
Equal(t, test.MultiPartPosted, "values")
351351

352352
// test JSON
353-
jsonBody := `{"ID":13,"Posted":"value","MultiPartPosted":"value"}`
353+
jsonBody := `{"ID":13,"Posted":"values","MultiPartPosted":"values"}`
354354
test = new(TestStruct)
355355
r, _ = http.NewRequest(http.MethodPost, "/decode-query?id=13", strings.NewReader(jsonBody))
356356
r.Header.Set(ContentType, ApplicationJSON)
357357
w = httptest.NewRecorder()
358358
mux.ServeHTTP(w, r)
359359
Equal(t, w.Code, http.StatusOK)
360360
Equal(t, test.ID, 13)
361-
Equal(t, test.Posted, "value")
362-
Equal(t, test.MultiPartPosted, "value")
361+
Equal(t, test.Posted, "values")
362+
Equal(t, test.MultiPartPosted, "values")
363363

364364
var buff bytes.Buffer
365365
gzw := gzip.NewWriter(&buff)
@@ -380,8 +380,8 @@ func TestDecode(t *testing.T) {
380380
mux.ServeHTTP(w, r)
381381
Equal(t, w.Code, http.StatusOK)
382382
Equal(t, test.ID, 14)
383-
Equal(t, test.Posted, "value")
384-
Equal(t, test.MultiPartPosted, "value")
383+
Equal(t, test.Posted, "values")
384+
Equal(t, test.MultiPartPosted, "values")
385385

386386
test = new(TestStruct)
387387
r, _ = http.NewRequest(http.MethodPost, "/decode-noquery?id=14", strings.NewReader(jsonBody))
@@ -390,20 +390,20 @@ func TestDecode(t *testing.T) {
390390
mux.ServeHTTP(w, r)
391391
Equal(t, w.Code, http.StatusOK)
392392
Equal(t, test.ID, 13)
393-
Equal(t, test.Posted, "value")
394-
Equal(t, test.MultiPartPosted, "value")
393+
Equal(t, test.Posted, "values")
394+
Equal(t, test.MultiPartPosted, "values")
395395

396396
// test XML
397-
xmlBody := `<TestStruct><ID>13</ID><Posted>value</Posted><MultiPartPosted>value</MultiPartPosted></TestStruct>`
397+
xmlBody := `<TestStruct><ID>13</ID><Posted>values</Posted><MultiPartPosted>values</MultiPartPosted></TestStruct>`
398398
test = new(TestStruct)
399399
r, _ = http.NewRequest(http.MethodPost, "/decode-noquery?id=14", strings.NewReader(xmlBody))
400400
r.Header.Set(ContentType, ApplicationXML)
401401
w = httptest.NewRecorder()
402402
mux.ServeHTTP(w, r)
403403
Equal(t, w.Code, http.StatusOK)
404404
Equal(t, test.ID, 13)
405-
Equal(t, test.Posted, "value")
406-
Equal(t, test.MultiPartPosted, "value")
405+
Equal(t, test.Posted, "values")
406+
Equal(t, test.MultiPartPosted, "values")
407407

408408
test = new(TestStruct)
409409
r, _ = http.NewRequest(http.MethodPost, "/decode-query?id=14", strings.NewReader(xmlBody))
@@ -412,8 +412,8 @@ func TestDecode(t *testing.T) {
412412
mux.ServeHTTP(w, r)
413413
Equal(t, w.Code, http.StatusOK)
414414
Equal(t, test.ID, 14)
415-
Equal(t, test.Posted, "value")
416-
Equal(t, test.MultiPartPosted, "value")
415+
Equal(t, test.Posted, "values")
416+
Equal(t, test.MultiPartPosted, "values")
417417

418418
buff.Reset()
419419
gzw = gzip.NewWriter(&buff)
@@ -434,6 +434,6 @@ func TestDecode(t *testing.T) {
434434
mux.ServeHTTP(w, r)
435435
Equal(t, w.Code, http.StatusOK)
436436
Equal(t, test.ID, 13)
437-
Equal(t, test.Posted, "value")
438-
Equal(t, test.MultiPartPosted, "value")
437+
Equal(t, test.Posted, "values")
438+
Equal(t, test.MultiPartPosted, "values")
439439
}

net/http/quality_value.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ const (
77
QualityValueFormat = "%s;q=%1.3g"
88
)
99

10-
// QualityValue accepts a value to add/concatenate a quality value to and
11-
// the quality value itself.
10+
// QualityValue accepts a values to add/concatenate a quality values to and
11+
// the quality values itself.
1212
func QualityValue(v string, qv float32) string {
1313
if qv > 1 {
14-
qv = 1 // highest possible value
14+
qv = 1 // highest possible values
1515
}
1616
if qv < 0.001 {
17-
qv = 0.001 // lowest possible value
17+
qv = 0.001 // lowest possible values
1818
}
1919
return fmt.Sprintf(QualityValueFormat, v, qv)
2020
}

sync/mutex.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//go:build go1.18
2+
3+
package sync
4+
5+
import (
6+
"sync"
7+
8+
"github.com/go-playground/pkg/v5/values/result"
9+
)
10+
11+
// NewMutex creates a new Mutex for use.
12+
func NewMutex[T any](value T) *Mutex[T] {
13+
return &Mutex[T]{
14+
value: value,
15+
}
16+
}
17+
18+
// Mutex creates a type safe mutex wrapper ensuring one cannot access the values of a locked values
19+
// without first gaining a lock.
20+
type Mutex[T any] struct {
21+
m sync.Mutex
22+
value T
23+
}
24+
25+
// PerformMut safely locks and unlocks the Mutex values and performs the provided function.
26+
//
27+
// Too bad Go doesn't support PerformMut[R any](func(T) R) R syntax :(
28+
func (m *Mutex[T]) PerformMut(f func(T)) {
29+
m.Lock()
30+
defer m.Unlock()
31+
f(m.value)
32+
}
33+
34+
// Lock locks the Mutex and returns value for mutable use.
35+
// If the lock is already in use, the calling goroutine blocks until the mutex is available.
36+
func (m *Mutex[T]) Lock() T {
37+
m.m.Lock()
38+
return m.value
39+
}
40+
41+
// Unlock unlocks the Mutex. It is a run-time error if the Mutex is not locked on entry to Unlock.
42+
func (m *Mutex[T]) Unlock() {
43+
m.m.Unlock()
44+
}
45+
46+
// TryLock tries to lock Mutex and reports whether it succeeded.
47+
// If it does the value is returned for use in the Ok result otherwise Err with empty value.
48+
func (m *Mutex[T]) TryLock() result.Result[T, struct{}] {
49+
if m.m.TryLock() {
50+
return result.Ok[T, struct{}](m.value)
51+
} else {
52+
return result.Err[T, struct{}](struct{}{})
53+
}
54+
}
55+
56+
// NewRWMutex creates a new RWMutex for use.
57+
func NewRWMutex[T any](value T) *RWMutex[T] {
58+
return &RWMutex[T]{
59+
value: value,
60+
}
61+
}
62+
63+
// RWMutex creates a type safe RWMutex wrapper ensuring one cannot access the values of a locked values
64+
// without first gaining a lock.
65+
type RWMutex[T any] struct {
66+
rw sync.RWMutex
67+
value T
68+
}
69+
70+
// PerformMut safely locks and unlocks the RWMutex mutable values and performs the provided function.
71+
//
72+
// Too bad Go doesn't support PerformMut[R any](func(T) R) R syntax :(
73+
func (m *RWMutex[T]) PerformMut(f func(T)) {
74+
m.Lock()
75+
defer m.Unlock()
76+
f(m.value)
77+
}
78+
79+
// Lock locks mutex and returns values for mutable use.
80+
func (m *RWMutex[T]) Lock() T {
81+
m.rw.Lock()
82+
return m.value
83+
}
84+
85+
// Unlock unlocks mutable lock for values.
86+
func (m *RWMutex[T]) Unlock() {
87+
m.rw.Unlock()
88+
}
89+
90+
// TryLock tries to lock RWMutex and returns the value in the Ok result if successful.
91+
// If it does the value is returned for use in the Ok result otherwise Err with empty value.
92+
func (m *RWMutex[T]) TryLock() result.Result[T, struct{}] {
93+
if m.rw.TryLock() {
94+
return result.Ok[T, struct{}](m.value)
95+
} else {
96+
return result.Err[T, struct{}](struct{}{})
97+
}
98+
}
99+
100+
// Perform safely locks and unlocks the RWMutex read-only values and performs the provided function.
101+
//
102+
// Too bad Go doesn't support Perform[R any](func(T) R) R syntax :(
103+
func (m *RWMutex[T]) Perform(f func(T)) {
104+
m.RLock()
105+
defer m.RUnlock()
106+
f(m.value)
107+
}
108+
109+
// RLock locks the RWMutex for reading and returns the value for read-only use.
110+
// It should not be used for recursive read locking; a blocked Lock call excludes new readers from acquiring the lock
111+
func (m *RWMutex[T]) RLock() T {
112+
m.rw.RLock()
113+
return m.value
114+
}
115+
116+
// RUnlock undoes a single RLock call; it does not affect other simultaneous readers.
117+
// It is a run-time error if rw is not locked for reading on entry to RUnlock.
118+
func (m *RWMutex[T]) RUnlock() {
119+
m.rw.RUnlock()
120+
}
121+
122+
// TryRLock tries to lock RWMutex for reading and returns the value in the Ok result if successful.
123+
// If it does the value is returned for use in the Ok result otherwise Err with empty value.
124+
func (m *RWMutex[T]) TryRLock() result.Result[T, struct{}] {
125+
if m.rw.TryRLock() {
126+
return result.Ok[T, struct{}](m.value)
127+
} else {
128+
return result.Err[T, struct{}](struct{}{})
129+
}
130+
}

sync/mutex_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//go:build go1.18
2+
3+
package sync
4+
5+
import (
6+
"testing"
7+
8+
. "github.com/go-playground/assert/v2"
9+
)
10+
11+
func TestMutex(t *testing.T) {
12+
m := NewMutex(make(map[string]int))
13+
m.Lock()["foo"] = 1
14+
m.Unlock()
15+
16+
m.PerformMut(func(m map[string]int) {
17+
m["boo"] = 1
18+
})
19+
20+
myMap := m.Lock()
21+
Equal(t, 2, len(myMap))
22+
m.Unlock()
23+
}
24+
25+
func TestRWMutex(t *testing.T) {
26+
m := NewRWMutex(make(map[string]int))
27+
m.Lock()["foo"] = 1
28+
Equal(t, m.TryLock().IsOk(), false)
29+
Equal(t, m.TryRLock().IsOk(), false)
30+
m.Unlock()
31+
32+
m.PerformMut(func(m map[string]int) {
33+
m["boo"] = 2
34+
})
35+
36+
myMap := m.RLock()
37+
Equal(t, len(myMap), 2)
38+
Equal(t, m.TryRLock().IsOk(), true)
39+
m.RUnlock()
40+
41+
m.Perform(func(m map[string]int) {
42+
Equal(t, 1, m["foo"])
43+
Equal(t, 2, m["boo"])
44+
})
45+
}

0 commit comments

Comments
 (0)