Skip to content

Commit 2dc0401

Browse files
authored
Merge pull request #44 from gdholtslander/error-matching
Add defined errors to the package
2 parents b32d4fb + 5dd62b9 commit 2dc0401

File tree

4 files changed

+143
-36
lines changed

4 files changed

+143
-36
lines changed

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
## v1.1
1+
## v1.2.0
2+
3+
### Added
4+
5+
- ErrorDetails to the Root object. This will contain the templated error messages that used to be returned by Error
6+
7+
## Changed
8+
9+
- Error will now be one of a standard set of errors defined by the package. Details about the error message have been moved
10+
to the ErrorDetails property of Root.
11+
12+
## v1.1.0
213

314
### Added
415

examples/errors/errors.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Errors happen. This example shows how to detect and handle some of them.
2+
3+
package main
4+
5+
import (
6+
"fmt"
7+
"log"
8+
9+
"github.com/anaskhan96/soup"
10+
)
11+
12+
func main() {
13+
_, err := soup.Get("this url isn't real!")
14+
if err != nil && err.(soup.Error).Type == soup.ErrInGetRequest {
15+
// Handle as required!
16+
}
17+
18+
url := fmt.Sprintf("https://xkcd.com/50")
19+
xkcd, err := soup.Get(url)
20+
if err != nil {
21+
// Handle it
22+
}
23+
xkcdSoup := soup.HTMLParse(xkcd)
24+
links := xkcdSoup.Find("div", "id", "linkz")
25+
if links.Error != nil && links.Error.(soup.Error).Type == soup.ErrElementNotFound {
26+
log.Printf("Element not found: %v", links.Error)
27+
}
28+
// These error types were introduced in version 1.2.0, but just checking for err still works:
29+
links = xkcdSoup.Find("div", "id", "links2")
30+
if links.Error != nil {
31+
log.Printf("Something happened: %s", links.Error)
32+
}
33+
}

soup.go

Lines changed: 79 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package soup
66

77
import (
88
"bytes"
9-
"errors"
9+
"fmt"
1010
"io/ioutil"
1111
"net/http"
1212
"regexp"
@@ -15,20 +15,67 @@ import (
1515
"golang.org/x/net/html"
1616
)
1717

18-
// Root is a structure containing a pointer to an html node, the node value, and an error variable to return an error if occurred
18+
// ErrorType defines types of errors that are possible from soup
19+
type ErrorType int
20+
21+
const (
22+
// ErrUnableToParse will be returned when the HTML could not be parsed
23+
ErrUnableToParse ErrorType = iota
24+
// ErrElementNotFound will be returned when element was not found
25+
ErrElementNotFound
26+
// ErrNoNextSibling will be returned when no next sibling can be found
27+
ErrNoNextSibling
28+
// ErrNoPreviousSibling will be returned when no previous sibling can be found
29+
ErrNoPreviousSibling
30+
// ErrNoNextElementSibling will be returned when no next element sibling can be found
31+
ErrNoNextElementSibling
32+
// ErrNoPreviousElementSibling will be returned when no previous element sibling can be found
33+
ErrNoPreviousElementSibling
34+
// ErrCreatingGetRequest will be returned when the get request couldn't be created
35+
ErrCreatingGetRequest
36+
// ErrInGetRequest will be returned when there was an error during the get request
37+
ErrInGetRequest
38+
// ErrReadingResponse will be returned if there was an error reading the response to our get request
39+
ErrReadingResponse
40+
)
41+
42+
// Error allows easier introspection on the type of error returned.
43+
// If you know you have a Error, you can compare the Type to one of the exported types
44+
// from this package to see what kind of error it is, then further inspect the Error() method
45+
// to see if it has more specific details for you, like in the case of a ErrElementNotFound
46+
// type of error.
47+
type Error struct {
48+
Type ErrorType
49+
msg string
50+
}
51+
52+
func (se Error) Error() string {
53+
return se.msg
54+
}
55+
56+
func newError(t ErrorType, msg string) Error {
57+
return Error{Type: t, msg: msg}
58+
}
59+
60+
// Root is a structure containing a pointer to an html node, the node value, and an error variable to return an error if one occurred
1961
type Root struct {
2062
Pointer *html.Node
2163
NodeValue string
2264
Error error
2365
}
2466

25-
var debug = false
67+
// Init a new HTTP client for use when the client doesn't want to use their own.
68+
var (
69+
defaultClient = &http.Client{}
2670

27-
// Headers contains all HTTP headers to send
28-
var Headers = make(map[string]string)
71+
debug = false
2972

30-
// Cookies contains all HTTP cookies to send
31-
var Cookies = make(map[string]string)
73+
// Headers contains all HTTP headers to send
74+
Headers = make(map[string]string)
75+
76+
// Cookies contains all HTTP cookies to send
77+
Cookies = make(map[string]string)
78+
)
3279

3380
// SetDebug sets the debug status
3481
// Setting this to true causes the panics to be thrown and logged onto the console.
@@ -42,6 +89,7 @@ func Header(n string, v string) {
4289
Headers[n] = v
4390
}
4491

92+
// Cookie sets a cookie for http requests
4593
func Cookie(n string, v string) {
4694
Cookies[n] = v
4795
}
@@ -53,7 +101,7 @@ func GetWithClient(url string, client *http.Client) (string, error) {
53101
if debug {
54102
panic("Couldn't perform GET request to " + url)
55103
}
56-
return "", errors.New("couldn't perform GET request to " + url)
104+
return "", newError(ErrCreatingGetRequest, "error creating get request to "+url)
57105
}
58106
// Set headers
59107
for hName, hValue := range Headers {
@@ -72,24 +120,22 @@ func GetWithClient(url string, client *http.Client) (string, error) {
72120
if debug {
73121
panic("Couldn't perform GET request to " + url)
74122
}
75-
return "", errors.New("couldn't perform GET request to " + url)
123+
return "", newError(ErrInGetRequest, "couldn't perform GET request to "+url)
76124
}
77125
defer resp.Body.Close()
78126
bytes, err := ioutil.ReadAll(resp.Body)
79127
if err != nil {
80128
if debug {
81129
panic("Unable to read the response body")
82130
}
83-
return "", errors.New("unable to read the response body")
131+
return "", newError(ErrReadingResponse, "unable to read the response body")
84132
}
85133
return string(bytes), nil
86134
}
87135

88136
// Get returns the HTML returned by the url in string using the default HTTP client
89137
func Get(url string) (string, error) {
90-
// Init a new HTTP client
91-
client := &http.Client{}
92-
return GetWithClient(url, client)
138+
return GetWithClient(url, defaultClient)
93139
}
94140

95141
// HTMLParse parses the HTML returning a start pointer to the DOM
@@ -99,7 +145,7 @@ func HTMLParse(s string) Root {
99145
if debug {
100146
panic("Unable to parse the HTML")
101147
}
102-
return Root{nil, "", errors.New("unable to parse the HTML")}
148+
return Root{Error: newError(ErrUnableToParse, "unable to parse the HTML")}
103149
}
104150
for r.Type != html.ElementNode {
105151
switch r.Type {
@@ -111,7 +157,7 @@ func HTMLParse(s string) Root {
111157
r = r.NextSibling
112158
}
113159
}
114-
return Root{r, r.Data, nil}
160+
return Root{Pointer: r, NodeValue: r.Data}
115161
}
116162

117163
// Find finds the first occurrence of the given tag name,
@@ -123,9 +169,9 @@ func (r Root) Find(args ...string) Root {
123169
if debug {
124170
panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")
125171
}
126-
return Root{nil, "", errors.New("element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")}
172+
return Root{Error: newError(ErrElementNotFound, fmt.Sprintf("element `%s` with attributes `%s` not found", args[0], strings.Join(args[1:], " ")))}
127173
}
128-
return Root{temp, temp.Data, nil}
174+
return Root{Pointer: temp, NodeValue: temp.Data}
129175
}
130176

131177
// FindAll finds all occurrences of the given tag name,
@@ -142,7 +188,7 @@ func (r Root) FindAll(args ...string) []Root {
142188
}
143189
pointers := make([]Root, 0, len(temp))
144190
for i := 0; i < len(temp); i++ {
145-
pointers = append(pointers, Root{temp[i], temp[i].Data, nil})
191+
pointers = append(pointers, Root{Pointer: temp[i], NodeValue: temp[i].Data})
146192
}
147193
return pointers
148194
}
@@ -155,9 +201,9 @@ func (r Root) FindStrict(args ...string) Root {
155201
if debug {
156202
panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")
157203
}
158-
return Root{nil, "", errors.New("element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")}
204+
return Root{nil, "", newError(ErrElementNotFound, fmt.Sprintf("element `%s` with attributes `%s` not found", args[0], strings.Join(args[1:], " ")))}
159205
}
160-
return Root{temp, temp.Data, nil}
206+
return Root{Pointer: temp, NodeValue: temp.Data}
161207
}
162208

163209
// FindAllStrict finds all occurrences of the given tag name
@@ -172,7 +218,7 @@ func (r Root) FindAllStrict(args ...string) []Root {
172218
}
173219
pointers := make([]Root, 0, len(temp))
174220
for i := 0; i < len(temp); i++ {
175-
pointers = append(pointers, Root{temp[i], temp[i].Data, nil})
221+
pointers = append(pointers, Root{Pointer: temp[i], NodeValue: temp[i].Data})
176222
}
177223
return pointers
178224
}
@@ -185,9 +231,9 @@ func (r Root) FindNextSibling() Root {
185231
if debug {
186232
panic("No next sibling found")
187233
}
188-
return Root{nil, "", errors.New("no next sibling found")}
234+
return Root{Error: newError(ErrNoNextSibling, "no next sibling found")}
189235
}
190-
return Root{nextSibling, nextSibling.Data, nil}
236+
return Root{Pointer: nextSibling, NodeValue: nextSibling.Data}
191237
}
192238

193239
// FindPrevSibling finds the previous sibling of the pointer in the DOM
@@ -198,9 +244,10 @@ func (r Root) FindPrevSibling() Root {
198244
if debug {
199245
panic("No previous sibling found")
200246
}
201-
return Root{nil, "", errors.New("no previous sibling found")}
247+
248+
return Root{Error: newError(ErrNoPreviousSibling, "no previous sibling found")}
202249
}
203-
return Root{prevSibling, prevSibling.Data, nil}
250+
return Root{Pointer: prevSibling, NodeValue: prevSibling.Data}
204251
}
205252

206253
// FindNextElementSibling finds the next element sibling of the pointer in the DOM
@@ -211,12 +258,12 @@ func (r Root) FindNextElementSibling() Root {
211258
if debug {
212259
panic("No next element sibling found")
213260
}
214-
return Root{nil, "", errors.New("no next element sibling found")}
261+
return Root{Error: newError(ErrNoNextElementSibling, "no next element sibling found")}
215262
}
216263
if nextSibling.Type == html.ElementNode {
217-
return Root{nextSibling, nextSibling.Data, nil}
264+
return Root{Pointer: nextSibling, NodeValue: nextSibling.Data}
218265
}
219-
p := Root{nextSibling, nextSibling.Data, nil}
266+
p := Root{Pointer: nextSibling, NodeValue: nextSibling.Data}
220267
return p.FindNextElementSibling()
221268
}
222269

@@ -228,12 +275,12 @@ func (r Root) FindPrevElementSibling() Root {
228275
if debug {
229276
panic("No previous element sibling found")
230277
}
231-
return Root{nil, "", errors.New("no previous element sibling found")}
278+
return Root{Error: newError(ErrNoPreviousElementSibling, "no previous element sibling found")}
232279
}
233280
if prevSibling.Type == html.ElementNode {
234-
return Root{prevSibling, prevSibling.Data, nil}
281+
return Root{Pointer: prevSibling, NodeValue: prevSibling.Data}
235282
}
236-
p := Root{prevSibling, prevSibling.Data, nil}
283+
p := Root{Pointer: prevSibling, NodeValue: prevSibling.Data}
237284
return p.FindPrevElementSibling()
238285
}
239286

@@ -242,7 +289,7 @@ func (r Root) Children() []Root {
242289
child := r.Pointer.FirstChild
243290
var children []Root
244291
for child != nil {
245-
children = append(children, Root{child, child.Data, nil})
292+
children = append(children, Root{Pointer: child, NodeValue: child.Data})
246293
child = child.NextSibling
247294
}
248295
return children

soup_test.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"strconv"
66
"strings"
77
"testing"
8+
9+
"github.com/stretchr/testify/assert"
810
)
911

1012
const testHTML = `
@@ -205,10 +207,24 @@ func TestFullText(t *testing.T) {
205207
}
206208

207209
func TestFullTextEmpty(t *testing.T) {
208-
// <div id="5"><h1><span></span></h1></div>
209-
h1 := doc.Find("div", "id", "5").Find("h1")
210+
// <div id="5"><h1><span></span></h1></div>
211+
h1 := doc.Find("div", "id", "5").Find("h1")
210212

211-
if h1.FullText() != "" {
213+
if h1.FullText() != "" {
212214
t.Errorf("Wrong text: %s", h1.FullText())
213215
}
214216
}
217+
218+
func TestNewErrorReturnsInspectableError(t *testing.T) {
219+
err := newError(ErrElementNotFound, "element not found")
220+
assert.NotNil(t, err)
221+
assert.Equal(t, ErrElementNotFound, err.Type)
222+
assert.Equal(t, "element not found", err.Error())
223+
}
224+
225+
func TestFindReturnsInspectableError(t *testing.T) {
226+
r := doc.Find("bogus", "thing")
227+
assert.IsType(t, Error{}, r.Error)
228+
assert.Equal(t, "element `bogus` with attributes `thing` not found", r.Error.Error())
229+
assert.Equal(t, ErrElementNotFound, r.Error.(Error).Type)
230+
}

0 commit comments

Comments
 (0)