Compare commits

..

11 Commits

Author SHA1 Message Date
aneurin d3e2b415e4 update package name 2026-07-10 15:42:50 +01:00
Aneurin Barker Snook 727a744d55 fix module name 2024-07-19 14:51:48 +01:00
Aneurin Barker Snook 7d4519f06f add test workflow 2024-07-19 14:47:25 +01:00
Aneurin Barker Snook 2204816567 fix link to license 2024-07-19 14:46:46 +01:00
Aneurin Barker Snook f2bd1255dd ensure go.sum not committed 2024-07-19 14:46:28 +01:00
Aneurin Barker Snook 7e78d5e181 duplicate license to each package 2024-07-11 17:33:18 +01:00
Aneurin Barker Snook 88c2216e76 remove errant print 2024-06-22 17:29:07 +01:00
Aneurin Barker Snook ca9e4765cb add rest authorization header support 2024-06-22 17:12:09 +01:00
Aneurin Barker Snook 480a5c816d add readme for each package 2024-06-21 21:54:44 +01:00
Aneurin Barker Snook 5c0b808dd7 migrate from recipeer org to annybs 2024-04-25 22:00:39 +01:00
Aneurin Barker Snook 60fc78574e fix staticcheck suggestions 2023-11-25 16:51:25 +00:00
8 changed files with 208 additions and 5 deletions
+47
View File
@@ -0,0 +1,47 @@
name: Test
on:
pull_request:
branches:
- develop
push:
branches:
- develop
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ^1.21.0
- name: Display Go version
run: go version
- name: Install dependencies
run: go get
- name: Run tests
run: go test -v
notify:
name: Send Discord workflow notification
runs-on: ubuntu-latest
needs: test
steps:
- name: Send notification
uses: annybs/action-notify-discord@v1
if: ${{ always() }}
with:
repository: ${{ github.repository }}
result: ${{ needs.test.result }}
run-id: ${{ github.run_id }}
run-number: ${{ github.run_number }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK }}
workflow: ${{ github.workflow }}
+1
View File
@@ -0,0 +1 @@
go.sum
+11
View File
@@ -0,0 +1,11 @@
# MIT License
Copyright © 2024 Aneurin Barker Snook a@aneur.in
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
https://mit-license.org/
+44
View File
@@ -0,0 +1,44 @@
# Go REST
Some handy functions for developing JSON-based REST APIs. In particular, it simplifies reading HTTP request bodies, writing HTTP response bodies, and handling errors.
## Error handling
You can use `errors.Is()` to ascertain the type of errors thrown by validation functions, but for the most part, this isn't necessary because the write functions already do that.
## Example
```go
package main
import (
"errors"
"math/rand"
"net/http"
"github.com/annybs/go/rest"
)
type Handler struct{}
func (*Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
n := rand.Intn(3)
if n == 0 {
rest.WriteResponseJSON(w, http.StatusOK, map[string]string{"status": "OK"})
} else if n == 1 {
rest.WriteErrorJSON(w, errors.New("the original error message is added to data.error"))
} else {
rest.WriteErrorJSON(w, rest.ErrNotFound)
}
}
func main() {
http.ListenAndServe("localhost:8000", &Handler{})
}
```
Open <http://localhost:8000> in your browser and refresh a bunch of times to see the different possible responses.
## License
See [LICENSE.md](./LICENSE.md)
+2 -4
View File
@@ -58,10 +58,8 @@ func (e Error) WithData(data map[string]interface{}) Error {
if e.Data == nil { if e.Data == nil {
e.Data = map[string]any{} e.Data = map[string]any{}
} }
if data != nil { for key, value := range data {
for key, value := range data { e.Data[key] = value
e.Data[key] = value
}
} }
return e return e
} }
+1 -1
View File
@@ -1,3 +1,3 @@
module github.com/recipeer/go/rest module code.aneur.in/go/rest
go 1.21 go 1.21
+29
View File
@@ -0,0 +1,29 @@
package rest
import (
"net/http"
)
// IsAuthenticated returns true if the bearer token in a request's authorization is equal to a user-defined token.
// This function always returns true if the user-defined token is empty i.e. no authentication required.
func IsAuthenticated(req *http.Request, token string) bool {
if token == "" {
return true
}
read := ReadBearerToken(req)
return read == token
}
// ReadBearerToken reads the token portion of a bearer token in a request's authorization header.
// This function returns an empty string if the header is not provided or is not a bearer token.
func ReadBearerToken(req *http.Request) string {
header := req.Header.Get("authorization")
if len(header) > 8 {
bearer := header[0:7]
if bearer == "bearer " || bearer == "Bearer " {
return header[7:]
}
}
return ""
}
+73
View File
@@ -0,0 +1,73 @@
package rest
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestIsAuthenticated(t *testing.T) {
type TestCase struct {
Req *http.Request
Token string
Expected bool
}
testCases := []TestCase{}
req := httptest.NewRequest("GET", "/", nil)
req.Header.Add("authorization", "bearer abcd")
testCases = append(testCases, TestCase{Req: req, Token: "abcd", Expected: true})
req = httptest.NewRequest("POST", "/", nil)
req.Header.Add("authorization", "Bearer defg hijk")
testCases = append(testCases, TestCase{Req: req, Token: "defg hijk", Expected: true})
req = httptest.NewRequest("DELETE", "/", nil)
testCases = append(testCases, TestCase{Req: req, Token: "", Expected: true})
req = httptest.NewRequest("GET", "/", nil)
testCases = append(testCases, TestCase{Req: req, Token: "lmno"})
req = httptest.NewRequest("GET", "/", nil)
req.Header.Add("authorization", "Bearer pqrs")
testCases = append(testCases, TestCase{Req: req, Expected: true})
for i, tc := range testCases {
t.Logf("(%d) Testing request authorization header against %q", i, tc.Token)
actual := IsAuthenticated(tc.Req, tc.Token)
if actual != tc.Expected {
t.Errorf("Expected %v, got %v", tc.Expected, actual)
}
}
}
func TestReadBearerToken(t *testing.T) {
type TestCase struct {
Req *http.Request
Expected string
}
testCases := []TestCase{}
req := httptest.NewRequest("GET", "/", nil)
req.Header.Add("authorization", "bearer abcd")
testCases = append(testCases, TestCase{Req: req, Expected: "abcd"})
req = httptest.NewRequest("POST", "/", nil)
req.Header.Add("authorization", "Bearer defg hijk")
testCases = append(testCases, TestCase{Req: req, Expected: "defg hijk"})
req = httptest.NewRequest("DELETE", "/", nil)
testCases = append(testCases, TestCase{Req: req, Expected: ""})
for i, tc := range testCases {
t.Logf("(%d) Testing request authorization header against %q", i, tc.Expected)
actual := ReadBearerToken(tc.Req)
if actual != tc.Expected {
t.Errorf("Expected %q, got %q", tc.Expected, actual)
}
}
}