Skip to content

Commit 0efb11a

Browse files
feat: cleanup API
1 parent cbc4268 commit 0efb11a

File tree

12 files changed

+187
-127
lines changed

12 files changed

+187
-127
lines changed

shield.go

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package shield
22

33
import (
44
"errors"
5+
"log/slog"
6+
"os"
57

68
"github.com/google/uuid"
79
)
@@ -19,6 +21,8 @@ var (
1921
ErrUserNotFound = errors.New("shield: user not found")
2022
)
2123

24+
var DefaultLogger = slog.New(slog.NewTextHandler(os.Stdout, nil))
25+
2226
type User[T any] struct {
2327
// ID is the user ID.
2428
ID uuid.UUID

shieldpassword/form_handler.go

+36-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package shieldpassword
22

33
import (
4+
"cmp"
45
"errors"
56
"fmt"
67
"net/http"
@@ -9,6 +10,7 @@ import (
910
"github.com/go-playground/mold/v4/scrubbers"
1011
"github.com/go-playground/validator/v10"
1112
"github.com/jackc/pgx/v5/pgxpool"
13+
"go.inout.gg/foundations/debug"
1214
"go.inout.gg/foundations/http/httperror"
1315
"go.inout.gg/shield"
1416
)
@@ -35,29 +37,30 @@ type FormConfig[T any] struct {
3537
PasswordFieldName string // optional (default: DefaultFieldNamePassword)
3638
}
3739

38-
// NewFormConfig[T] creates a new FormConfig[T] with the given configuration options.
39-
func NewFormConfig[T any](config ...func(*FormConfig[T])) *FormConfig[T] {
40-
cfg := &FormConfig[T]{
41-
FirstNameFieldName: DefaultFieldNameFirstName,
42-
LastNameFieldName: DefaultFieldNameLastName,
43-
EmailFieldName: DefaultFieldNameEmail,
44-
PasswordFieldName: DefaultFieldNamePassword,
40+
func (c *FormConfig[T]) defaults() {
41+
c.FirstNameFieldName = cmp.Or(c.FirstNameFieldName, DefaultFieldNameEmail)
42+
c.LastNameFieldName = cmp.Or(c.LastNameFieldName, DefaultFieldNameEmail)
43+
c.EmailFieldName = cmp.Or(c.EmailFieldName, DefaultFieldNameEmail)
44+
c.PasswordFieldName = cmp.Or(c.PasswordFieldName, DefaultFieldNameEmail)
45+
if c.Config == nil {
46+
c.Config = NewConfig[T]()
4547
}
48+
}
4649

47-
for _, f := range config {
48-
f(cfg)
49-
}
50+
func (c *FormConfig[T]) assert() {
51+
debug.Assert(c.Config != nil, "Config must be set")
52+
}
5053

51-
// Set defaults.
52-
if cfg.Config == nil {
53-
cfg.Config = NewConfig[T]()
54+
// NewFormConfig[T] creates a new FormConfig[T] with the given configuration options.
55+
func NewFormConfig[T any](opts ...func(*FormConfig[T])) *FormConfig[T] {
56+
var config FormConfig[T]
57+
for _, opt := range opts {
58+
opt(&config)
5459
}
5560

56-
return cfg
57-
}
61+
config.defaults()
5862

59-
func WithConfig[T any](config *Config[T]) func(*FormConfig[T]) {
60-
return func(cfg *FormConfig[T]) { cfg.Config = config }
63+
return &config
6164
}
6265

6366
// FormHandler[T] is a wrapper around Handler handling HTTP form requests.
@@ -67,14 +70,25 @@ type FormHandler[T any] struct {
6770
}
6871

6972
// NewFormHandler[T] creates a new FormHandler[T] with the given configuration.
73+
//
74+
// If config is nil, the default config is used.
7075
func NewFormHandler[T any](pool *pgxpool.Pool, config *FormConfig[T]) *FormHandler[T] {
71-
return &FormHandler[T]{
72-
&Handler[T]{
73-
config: config.Config,
74-
pool: pool,
75-
},
76+
if config == nil {
77+
config = NewFormConfig[T]()
78+
}
79+
config.assert()
80+
81+
h := FormHandler[T]{
82+
NewHandler(pool, config.Config),
7683
config,
7784
}
85+
h.assert()
86+
87+
return &h
88+
}
89+
90+
func (h *FormHandler[T]) assert() {
91+
debug.Assert(h.handler != nil, "handler must be set")
7892
}
7993

8094
// userRegistrationForm is the form for user login.

shieldpassword/handler.go

+42-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package shieldpassword
22

33
import (
4+
"cmp"
45
"context"
56
"fmt"
67
"log/slog"
@@ -24,9 +25,20 @@ var (
2425

2526
// Config is the configuration for the password handler.
2627
type Config[T any] struct {
27-
Logger *slog.Logger
28-
PasswordHasher PasswordHasher
29-
Hijacker Hijacker[T]
28+
Logger *slog.Logger
29+
PasswordHasher PasswordHasher
30+
PasswordVerifier shieldpasswordverifier.PasswordVerifier
31+
Hijacker Hijacker[T]
32+
}
33+
34+
func (c *Config[T]) defaults() {
35+
c.Logger = cmp.Or(c.Logger, shield.DefaultLogger)
36+
c.PasswordHasher = cmp.Or(c.PasswordHasher, DefaultPasswordHasher)
37+
}
38+
39+
func (c *Config[T]) assert() {
40+
debug.Assert(c.PasswordHasher != nil, "PasswordHasher must be set")
41+
debug.Assert(c.Logger != nil, "Logger must be set")
3042
}
3143

3244
// Hijacker also to hijack into the user registration and logging in sessions
@@ -46,42 +58,49 @@ type Hijacker[T any] interface {
4658
// NewConfig creates a new config.
4759
//
4860
// If no password hasher is configured, the DefaultPasswordHasher will be used.
49-
func NewConfig[T any](config ...func(*Config[T])) *Config[T] {
50-
cfg := Config[T]{}
51-
52-
for _, c := range config {
53-
c(&cfg)
54-
}
55-
56-
if cfg.PasswordHasher == nil {
57-
cfg.PasswordHasher = DefaultPasswordHasher
61+
func NewConfig[T any](opts ...func(*Config[T])) *Config[T] {
62+
config := Config[T]{}
63+
for _, opt := range opts {
64+
opt(&config)
5865
}
5966

60-
debug.Assert(cfg.PasswordHasher != nil, "PasswordHasher must be set")
67+
config.defaults()
68+
config.assert()
6169

62-
return &cfg
70+
return &config
6371
}
6472

6573
// WithPasswordHasher configures the password hasher.
6674
//
6775
// When setting a password hasher make sure to set it across all modules,
68-
// such as user registration, password reset and password verification.
76+
// i.e., user registration, password reset and password verification.
6977
func WithPasswordHasher[T any](hasher PasswordHasher) func(*Config[T]) {
7078
return func(cfg *Config[T]) { cfg.PasswordHasher = hasher }
7179
}
7280

73-
func WithLogger[T any](logger *slog.Logger) func(*Config[T]) {
74-
return func(cfg *Config[T]) { cfg.Logger = logger }
75-
}
76-
7781
func WithHijacker[T any](hijacker Hijacker[T]) func(*Config[T]) {
7882
return func(cfg *Config[T]) { cfg.Hijacker = hijacker }
7983
}
8084

8185
type Handler[T any] struct {
82-
config *Config[T]
83-
pool *pgxpool.Pool
84-
PasswordVerifier shieldpasswordverifier.PasswordVerifier
86+
pool *pgxpool.Pool
87+
config *Config[T]
88+
}
89+
90+
func NewHandler[T any](pool *pgxpool.Pool, config *Config[T]) *Handler[T] {
91+
if config == nil {
92+
config = NewConfig[T]()
93+
}
94+
config.assert()
95+
96+
h := Handler[T]{pool, config}
97+
h.assert()
98+
99+
return &h
100+
}
101+
102+
func (h *Handler[T]) assert() {
103+
debug.Assert(h.pool != nil, "Logger must be set")
85104
}
86105

87106
func (h *Handler[T]) HandleUserRegistration(
@@ -147,6 +166,7 @@ func (h *Handler[T]) handleUserRegistrationTx(
147166
Email: email,
148167
}); err != nil {
149168
if sqldb.IsUniqueViolationError(err) {
169+
d("unique error violation")
150170
return uid, ErrEmailAlreadyTaken
151171
}
152172

shieldpassword/handler_test.go

+2-12
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package shieldpassword
22

33
import (
44
"context"
5-
"log/slog"
65
"testing"
76
"time"
87

@@ -14,18 +13,9 @@ import (
1413
func TestUserRegistration(t *testing.T) {
1514
ctx := context.Background()
1615
db := testutil.MustDB(ctx, t)
17-
logger := slog.Default()
18-
config := &Config[any]{
19-
PasswordHasher: DefaultPasswordHasher,
20-
Logger: logger,
21-
}
16+
config := NewConfig[any]()
2217
pool := db.Pool()
23-
h := &Handler[any]{
24-
config: config,
25-
pool: pool,
26-
PasswordVerifier: nil,
27-
}
28-
18+
h := NewHandler(pool, config)
2919
t.Run("register user", func(t *testing.T) {
3020
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
3121
defer cancel()

shieldpassword/shieldpassword.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ type PasswordHasher interface {
99
Verify(hashedPassword string, password string) (bool, error)
1010
}
1111

12-
var (
13-
// DefaultPasswordHasher is the default password hashing algorithm used across.
14-
DefaultPasswordHasher = NewBcryptPasswordHasher(BcryptDefaultCost)
15-
)
12+
// DefaultPasswordHasher is the default password hashing algorithm used across.
13+
var DefaultPasswordHasher = NewBcryptPasswordHasher(BcryptDefaultCost)
1614

1715
var d = debug.Debuglog("shield/password")

shieldpasswordreset/form_handler.go

+16-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/http"
77

88
"github.com/jackc/pgx/v5/pgxpool"
9+
"go.inout.gg/foundations/debug"
910
"go.inout.gg/foundations/http/httperror"
1011
"go.inout.gg/shield"
1112
"go.inout.gg/shield/shieldpassword"
@@ -29,8 +30,8 @@ type FormConfig struct {
2930

3031
// FormHandler is a wrapper around Handler handling HTTP form requests.
3132
type FormHandler struct {
32-
config *FormConfig
3333
handler *Handler
34+
config *FormConfig
3435
}
3536

3637
// NewFormConfig creates a new FormConfig with the given configuration options.
@@ -72,18 +73,27 @@ type confirmForm struct {
7273
}
7374

7475
// NewFormHandler creates a new FormHandler with the given configuration.
76+
//
77+
// If config is nil, it
7578
func NewFormHandler(
7679
pool *pgxpool.Pool,
7780
sender shieldsender.Sender,
7881
config *FormConfig,
7982
) *FormHandler {
80-
handler := &Handler{
81-
pool,
82-
config.Config,
83-
sender,
83+
if config == nil {
84+
config = NewFormConfig()
8485
}
86+
config.assert()
87+
88+
h := FormHandler{NewHandler(pool, sender, config.Config), config}
89+
h.assert()
90+
91+
return &h
92+
}
8593

86-
return &FormHandler{config, handler}
94+
func (h *FormHandler) assert() {
95+
debug.Assert(h.config != nil, "config must be set")
96+
debug.Assert(h.handler != nil, "handler must be set")
8797
}
8898

8999
func (h *FormHandler) parsePasswordResetRequestForm(

0 commit comments

Comments
 (0)