Skip to content

Commit 6bf1d80

Browse files
committed
V2 with options
Signed-off-by: Frederic BIDON <[email protected]>
1 parent b41f650 commit 6bf1d80

File tree

14 files changed

+492
-175
lines changed

14 files changed

+492
-175
lines changed

README.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,31 @@ which provides a workable but loose implementation of the RFC for URLs.
2121

2222
## What's new?
2323

24-
### V1.2 announcement
24+
### v2.0.0
25+
26+
**Breaking changes**
27+
28+
Most users should not be affected by these breaking changes.
29+
30+
* `URI` and `Authority` become concrete types. Interfaces are discarded.
31+
* `Parse()` and `ParseReference()` now return a `URI` value, no longer a pointer.
32+
* The `Validate() error` methods have been removed: validation is carried out when parsing only.
33+
* The `Builder` interface and `URI.Builder()` function have been removed.
34+
`URI` exposes fluent builder methods instead.
35+
* `UsesDNSHostValidation()` has been removed and replaced by a private default function.
36+
Override is possible via `Option`. Similar custom behavior may be achieved for `DefaultPort()`.
37+
38+
**Features**
2539

40+
* `Parse(string, ...Option)` and `ParseReference(string, ...Option)` now support options to tune the
41+
`URI` validation.
42+
43+
**Performances**
44+
45+
* perf: massive improvement due to giving up pointers (parsing now is a zero-allocation operation).
46+
This boosts `Parse()` to be even faster than the standard library `net/url.Parse()`.
47+
48+
### V1.2 announcement
2649
To do before I cut a v1.2.0:
2750
* [] handle empty fragment, empty query.
2851
Ex: `https://host?` is not equivalent to `http://host`.

benchmark_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,29 +90,29 @@ func Benchmark_String(b *testing.B) {
9090
var ip ipType
9191
tests := []URI{
9292
{
93-
"foo", "//example.com:8042/over/there", "name=ferret", "nose",
94-
Authority{"//", "", "example.com", "8042", "/over/there", ip, nil},
9593
nil,
94+
"foo", "//example.com:8042/over/there", "name=ferret", "nose",
95+
Authority{nil, "//", "", "example.com", "8042", "/over/there", ip},
9696
},
9797
{
98-
"http", "//httpbin.org/get", "utf8=\xe2\x98\x83", "",
99-
Authority{"//", "", "httpbin.org", "", "/get", ip, nil},
10098
nil,
99+
"http", "//httpbin.org/get", "utf8=\xe2\x98\x83", "",
100+
Authority{nil, "//", "", "httpbin.org", "", "/get", ip},
101101
},
102102
{
103-
"mailto", "[email protected]", "", "",
104-
Authority{"//", "user", "domain.com", "", "", ip, nil},
105103
nil,
104+
"mailto", "[email protected]", "", "",
105+
Authority{nil, "//", "user", "domain.com", "", "", ip},
106106
},
107107
{
108-
"ssh", "//[email protected]:29418/openstack/keystone.git", "", "",
109-
Authority{"//", "user", "git.openstack.org", "29418", "/openstack/keystone.git", ip, nil},
110108
nil,
109+
"ssh", "//[email protected]:29418/openstack/keystone.git", "", "",
110+
Authority{nil, "//", "user", "git.openstack.org", "29418", "/openstack/keystone.git", ip},
111111
},
112112
{
113-
"https", "//willo.io/", "", "yolo",
114-
Authority{"//", "", "willo.io", "", "/", ip, nil},
115113
nil,
114+
"https", "//willo.io/", "", "yolo",
115+
Authority{nil, "//", "", "willo.io", "", "/", ip},
116116
},
117117
}
118118

builder.go

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,99 +2,131 @@ package uri
22

33
// Builder methods
44

5-
func (u URI) WithScheme(scheme string) URI {
5+
func (u URI) WithScheme(scheme string, opts ...Option) URI {
66
if u.Err() != nil {
77
return u
88
}
99

10+
opts = append(opts, withValidationFlags(flagValidateScheme|flagValidateHost))
11+
o, redeem := applyURIOptions(opts)
12+
defer func() { redeem(o) }()
13+
1014
u.scheme = scheme
11-
u.authority.ipType, u.err = u.validate()
15+
u.authority.ipType, u.err = u.validate(o)
1216

1317
return u
1418
}
1519

16-
func (u URI) WithAuthority(authority Authority) URI {
20+
func (u URI) WithAuthority(authority Authority, opts ...Option) URI {
1721
if u.Err() != nil {
1822
return u
1923
}
2024

25+
opts = append(opts, withValidationFlags(flagValidateHost|flagValidatePort|flagValidateUserInfo|flagValidatePath))
26+
o, redeem := applyURIOptions(opts)
27+
defer func() { redeem(o) }()
28+
2129
u.authority = authority
22-
u.authority.ipType, u.err = u.validate()
30+
u.authority.ipType, u.err = u.validate(o)
2331
u.authority.err = u.err
2432

2533
return u
2634
}
2735

28-
func (u URI) WithUserInfo(userinfo string) URI {
36+
func (u URI) WithUserInfo(userinfo string, opts ...Option) URI {
2937
if u.Err() != nil {
3038
return u
3139
}
3240

41+
opts = append(opts, withValidationFlags(flagValidateUserInfo))
42+
o, redeem := applyURIOptions(opts)
43+
defer func() { redeem(o) }()
44+
3345
u.authority = u.authority.withEnsuredAuthority()
3446
u.authority.userinfo = userinfo
35-
u.authority.ipType, u.err = u.validate()
47+
u.authority.ipType, u.err = u.validate(o)
3648
u.authority.err = u.err
3749

3850
return u
3951
}
4052

41-
func (u URI) WithHost(host string) URI {
53+
func (u URI) WithHost(host string, opts ...Option) URI {
4254
if u.Err() != nil {
4355
return u
4456
}
4557

58+
opts = append(opts, withValidationFlags(flagValidateHost|flagValidatePort))
59+
o, redeem := applyURIOptions(opts)
60+
defer func() { redeem(o) }()
61+
4662
u.authority = u.authority.withEnsuredAuthority()
4763
u.authority.host = host
48-
u.authority.ipType, u.err = u.validate()
64+
u.authority.ipType, u.err = u.validate(o)
4965
u.authority.err = u.err
5066

5167
return u
5268
}
5369

54-
func (u URI) WithPort(port string) URI {
70+
func (u URI) WithPort(port string, opts ...Option) URI { // TODO: port as int?
5571
if u.Err() != nil {
5672
return u
5773
}
5874

75+
opts = append(opts, withValidationFlags(flagValidatePort))
76+
o, redeem := applyURIOptions(opts)
77+
defer func() { redeem(o) }()
78+
5979
u.authority = u.authority.withEnsuredAuthority()
6080
u.authority.port = port
61-
u.authority.ipType, u.err = u.validate()
81+
u.authority.ipType, u.err = u.validate(o)
6282
u.authority.err = u.err
6383

6484
return u
6585
}
6686

67-
func (u URI) WithPath(path string) URI {
87+
func (u URI) WithPath(path string, opts ...Option) URI {
6888
if u.Err() != nil {
6989
return u
7090
}
7191

92+
opts = append(opts, withValidationFlags(flagValidatePath))
93+
o, redeem := applyURIOptions(opts)
94+
defer func() { redeem(o) }()
95+
7296
u.authority = u.authority.withEnsuredAuthority()
7397
u.authority.path = path
74-
u.authority.ipType, u.err = u.validate()
98+
u.authority.ipType, u.err = u.validate(o)
7599
u.authority.err = u.err
76100

77101
return u
78102
}
79103

80-
func (u URI) WithQuery(query string) URI {
104+
func (u URI) WithQuery(query string, opts ...Option) URI {
81105
if u.Err() != nil {
82106
return u
83107
}
84108

109+
opts = append(opts, withValidationFlags(flagValidateQuery))
110+
o, redeem := applyURIOptions(opts)
111+
defer func() { redeem(o) }()
112+
85113
u.query = query
86-
u.authority.ipType, u.err = u.validate()
114+
u.authority.ipType, u.err = u.validate(o)
87115

88116
return u
89117
}
90118

91-
func (u URI) WithFragment(fragment string) URI {
119+
func (u URI) WithFragment(fragment string, opts ...Option) URI {
92120
if u.Err() != nil {
93121
return u
94122
}
95123

124+
opts = append(opts, withValidationFlags(flagValidateFragment))
125+
o, redeem := applyURIOptions(opts)
126+
defer func() { redeem(o) }()
127+
96128
u.fragment = fragment
97-
u.authority.ipType, u.err = u.validate()
129+
u.authority.ipType, u.err = u.validate(o)
98130

99131
return u
100132
}

dns.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
// in case you need specific schemes to validate the host as a DNS name.
1515
//
1616
// See: https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
17-
// TODO: now pass it as an option.
17+
// TODO: now pass it as an option. make private
1818
var UsesDNSHostValidation = func(scheme string) bool {
1919
switch scheme {
2020
// prioritize early exit on most commonly used schemes

dns_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ func TestDNSvsHost(t *testing.T) {
1818
}
1919

2020
func TestValidateHostForScheme(t *testing.T) {
21+
o := defaultOptions()
2122
for _, host := range []string{
2223
"a.b.c",
2324
"a",
@@ -32,7 +33,7 @@ func TestValidateHostForScheme(t *testing.T) {
3233
"a.b.c.d%30",
3334
"a.b.c.%55",
3435
} {
35-
require.NoErrorf(t, validateHostForScheme(host, "http"),
36+
require.NoErrorf(t, validateHostForScheme(host, "http", o),
3637
"expected host %q to validate",
3738
host,
3839
)
@@ -62,7 +63,7 @@ func TestValidateHostForScheme(t *testing.T) {
6263
"%",
6364
"%X",
6465
} {
65-
require.Errorf(t, validateHostForScheme(host, "http"),
66+
require.Errorf(t, validateHostForScheme(host, "http", o),
6667
"expected host %q NOT to validate",
6768
host,
6869
)

docs/BENCHMARKS.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,3 +295,31 @@ Benchmark_DNSSchemes/with_switch
295295
Benchmark_DNSSchemes/with_switch-16 1000000000 3.044 ns/op 0 B/op 0 allocs/op
296296
PASS
297297

298+
# V2 with options setup
299+
300+
go test -v -run Bench -benchtime 30s -bench Bench
301+
goos: linux
302+
goarch: amd64
303+
pkg: github.com/fredbi/uri
304+
cpu: AMD Ryzen 7 5800X 8-Core Processor
305+
Benchmark_Parse
306+
Benchmark_Parse/with_URI_simple_payload
307+
Benchmark_Parse/with_URI_simple_payload-16 137914446 257.1 ns/op 0 B/op 0 allocs/op
308+
Benchmark_Parse/with_URL_simple_payload
309+
Benchmark_Parse/with_URL_simple_payload-16 100000000 319.7 ns/op 168 B/op 1 allocs/op
310+
Benchmark_Parse/with_URI_mixed_payload
311+
Benchmark_Parse/with_URI_mixed_payload-16 138111548 262.6 ns/op 0 B/op 0 allocs/op
312+
Benchmark_Parse/with_URL_mixed_payload
313+
Benchmark_Parse/with_URL_mixed_payload-16 100000000 302.5 ns/op 163 B/op 1 allocs/op
314+
Benchmark_Parse/with_URI_payload_with_IPs
315+
Benchmark_Parse/with_URI_payload_with_IPs-16 128662461 282.0 ns/op 0 B/op 0 allocs/op
316+
Benchmark_Parse/with_URL_payload_with_IPs
317+
Benchmark_Parse/with_URL_payload_with_IPs-16 99240152 365.3 ns/op 176 B/op 1 allocs/op
318+
Benchmark_String
319+
Benchmark_String-16 446582586 80.53 ns/op 48 B/op 1 allocs/op
320+
Benchmark_DNSSchemes
321+
Benchmark_DNSSchemes/with_switch
322+
Benchmark_DNSSchemes/with_switch-16 1000000000 3.030 ns/op 0 B/op 0 allocs/op
323+
PASS
324+
325+

docs/TODO.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ v1.2
1313
* [x] fix scheme tolerance to be on ASCII only
1414
* [x] document the choice of a strict % escaping policy regarding char encoding (UTF8 mandatory)
1515
* [] handle empty fragment, empty query
16+
* [] fix scheme tolerance to be on ASCII only
1617
* [] IRI ucs charset compliance (att: perf challenge)
17-
- [] Support IRI iprivate in query
18+
* [] Support IRI iprivate in query
1819
* [] normalizer
1920

2021
v2.0.0

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
module github.com/fredbi/uri
22

3-
go 1.20
3+
go 1.19
44

55
require (
66
github.com/pkg/profile v1.7.0
77
github.com/stretchr/testify v1.8.4
88
golang.org/x/net v0.15.0
9-
golang.org/x/text v0.13.0
109
)
1110

1211
require (
1312
github.com/davecgh/go-spew v1.1.1 // indirect
1413
github.com/felixge/fgprof v0.9.3 // indirect
1514
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
1615
github.com/pmezard/go-difflib v1.0.0 // indirect
16+
golang.org/x/text v0.13.0 // indirect
1717
gopkg.in/yaml.v3 v3.0.1 // indirect
1818
)

0 commit comments

Comments
 (0)