Merge pull request 'chore/internal' (#3) from chore/internal into main

Reviewed-on: #3
This commit is contained in:
Thomas Andrade 2022-05-17 12:21:49 +00:00
commit 084f1b7aa9
7 changed files with 283 additions and 47 deletions

6
go.mod
View File

@ -6,10 +6,12 @@ require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/fasthttp/router v1.4.9
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/valyala/fasthttp v1.36.0
github.com/wolviecb/short/shortie v0.0.0-20220412102501-705312b4dffe
github.com/valyala/fasthttp v1.37.0
internal/shortie v0.0.0-00010101000000-000000000000
)
replace internal/shortie => ./internal/shortie
require (
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/klauspost/compress v1.15.4 // indirect

29
go.sum
View File

@ -1,20 +1,11 @@
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/fasthttp/router v1.4.8 h1:4zj4sAzXibjA6ZW19MdMe3GaYD1SM+TXrMLzHcVMBOI=
github.com/fasthttp/router v1.4.8/go.mod h1:UUtJdXFYlqYRQ32EAtWOvNYIZ1XfyC5JJIknWai6foI=
github.com/fasthttp/router v1.4.9 h1:8s1HEqP+GvsC2B8vPdLAPHJegs4s28z7UsraPuHM1K8=
github.com/fasthttp/router v1.4.9/go.mod h1:oWPrQCi9QOrzxKC+rZuliS1+JhYj2bpR01J6T8vUDUQ=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.6/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A=
github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.4 h1:1kn4/7MepF/CHmYub99/nNX8az0IJjfSOU/jbnTVfqQ=
github.com/klauspost/compress v1.15.4/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
@ -23,27 +14,13 @@ github.com/savsgio/gotils v0.0.0-20220401102855-e56b59f40436 h1:sfTahD3f2BSjx9U3
github.com/savsgio/gotils v0.0.0-20220401102855-e56b59f40436/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.19.0/go.mod h1:jjraHZVbKOXftJfsOYoAjaeygpj5hr8ermTRJNroD7A=
github.com/valyala/fasthttp v1.35.0 h1:wwkR8mZn2NbigFsaw2Zj5r+xkmzjbrA/lyTmiSlal/Y=
github.com/valyala/fasthttp v1.35.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
github.com/valyala/fasthttp v1.36.0 h1:NhqfO/cB7Ajn1czkKnWkMHyPYr5nyND14ZGPk23g0/c=
github.com/valyala/fasthttp v1.36.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/valyala/fasthttp v1.37.0 h1:7WHCyI7EAkQMVmrfBhWTCOaeROb1aCBiTopx63LkMbE=
github.com/valyala/fasthttp v1.37.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/wolviecb/short/shortie v0.0.0-20210110145449-4edfb0344225 h1:BB+2AaTpi+jtEJaNTmoM12ddkgAwMFgTtNXnVWJMtvY=
github.com/wolviecb/short/shortie v0.0.0-20210110145449-4edfb0344225/go.mod h1:MP++yL+vt6bdR5ResHARM9XzrdxEzqYPkyODGTsjFJs=
github.com/wolviecb/short/shortie v0.0.0-20220412102501-705312b4dffe h1:3GiAtzvN0/Xig4LOCI5t6MNDI9CY0YQnNyktrXYF3yM=
github.com/wolviecb/short/shortie v0.0.0-20220412102501-705312b4dffe/go.mod h1:MP++yL+vt6bdR5ResHARM9XzrdxEzqYPkyODGTsjFJs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -51,8 +28,6 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -1,11 +1,11 @@
module github.com/wolviecb/short/shortie
module shortie
go 1.18
require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/valyala/fasthttp v1.36.0
github.com/valyala/fasthttp v1.37.0
)
require (

View File

@ -9,8 +9,8 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.36.0 h1:NhqfO/cB7Ajn1czkKnWkMHyPYr5nyND14ZGPk23g0/c=
github.com/valyala/fasthttp v1.36.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
github.com/valyala/fasthttp v1.37.0 h1:7WHCyI7EAkQMVmrfBhWTCOaeROb1aCBiTopx63LkMbE=
github.com/valyala/fasthttp v1.37.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=

View File

@ -4,11 +4,11 @@ import (
"encoding/json"
"fmt"
"html/template"
"io/ioutil"
"log"
"math/rand"
"net/http"
"net/url"
"os"
"regexp"
"strings"
"time"
@ -57,6 +57,9 @@ var (
Path string = ""
// DumpFile is the file to dump URL data
DumpFile string = "urls.json"
// Error Definitions
ErrBadRequest = fmt.Errorf("bad request")
ErrNotFound = fmt.Errorf("not found")
)
// get executes the GET command
@ -98,7 +101,7 @@ func redirect(k string) (string, error) {
key := rgx.FindString(k)
key, status := get(key)
if !status {
return "", fmt.Errorf("Not Found")
return "", ErrNotFound
}
u, _ := url.Parse(key)
if u.Scheme == "" {
@ -110,13 +113,12 @@ func redirect(k string) (string, error) {
// shortener receive a url, validates the url, generate a random suffix string
// of urlSize size, checks if the suffix string is ensure on the kv database
// and then writes the kv pair (suffix, url) to the database, returning the suffix
func shortener(u []byte, s int) (string, error) {
func shortener(u string, s int) (string, error) {
var su string
us := string(u)
if !govalidator.IsURL(string(us)) {
return su, fmt.Errorf("Bad Request")
if !govalidator.IsURL(string(u)) {
return su, ErrBadRequest
}
pu, _ := url.Parse(us)
pu, _ := url.Parse(u)
for {
su = randStringBytesMaskImprSrc(s)
@ -131,16 +133,19 @@ func shortener(u []byte, s int) (string, error) {
}
// dumpDbToFile dumps the kv pairs from the in memory database to file
func dumpDbTOFile() (int, error) {
func dumpDbTOFile(f *os.File) (int, error) {
i := Pool.Items()
dumpObj, _ := json.Marshal(i)
return len(i), ioutil.WriteFile(DumpFile, dumpObj, 0644)
if _, err := f.Write(dumpObj); err != nil {
return len(i), err
}
return len(i), nil
}
// loadFromFile loads kv pairs from the dumpFile json to the in memory database
func loadFromFile() (int, error) {
dumpObj := make(map[string]cache.Item)
jsonFile, err := ioutil.ReadFile(DumpFile)
jsonFile, err := os.ReadFile(DumpFile)
if err != nil {
return 0, err
}
@ -154,7 +159,7 @@ func loadFromFile() (int, error) {
return len(dumpObj), err
}
// itemsFromPost loads kv pairs from a json POST to the in memory database
// loadFromJSON loads kv pairs from a json to the in memory database
func loadFromJSON(j []byte) (int, error) {
dumpObj := make(map[string]cache.Item)
err := json.Unmarshal(j, &dumpObj)
@ -195,7 +200,7 @@ func IndexHandler(t *template.Template) func(ctx *fasthttp.RequestCtx) {
func Short(t *template.Template) func(ctx *fasthttp.RequestCtx) {
return func(ctx *fasthttp.RequestCtx) {
ctx.Response.Header.SetCanonical([]byte("Content-Type"), []byte("text/html"))
suf, err := shortener(ctx.FormValue("url"), URLSize)
suf, err := shortener(string(ctx.FormValue("url")), URLSize)
if err != nil {
ctx.SetStatusCode(fasthttp.StatusBadRequest)
t.Execute(ctx, body{
@ -247,7 +252,14 @@ func Redir(t *template.Template) func(ctx *fasthttp.RequestCtx) {
// KV db to the DumpFile file
func ToFile(t *template.Template) func(ctx *fasthttp.RequestCtx) {
return func(ctx *fasthttp.RequestCtx) {
i, err := dumpDbTOFile()
f, err := os.Create(DumpFile)
if err != nil {
ctx.SetStatusCode(http.StatusInternalServerError)
ctx.Response.Header.SetCanonical([]byte("Content-Type"), []byte("text/html"))
t.Execute(ctx, internalError("Failed to create DB dump file", err))
return
}
i, err := dumpDbTOFile(f)
if err != nil {
ctx.SetStatusCode(http.StatusInternalServerError)
ctx.Response.Header.SetCanonical([]byte("Content-Type"), []byte("text/html"))

View File

@ -0,0 +1,246 @@
package shortie
import (
"errors"
"fmt"
"os"
"reflect"
"testing"
"github.com/patrickmn/go-cache"
)
var (
store = map[string]cache.Item{"google": {Object: "https://google.com"}}
)
func TestGet(t *testing.T) {
Pool = cache.NewFrom(Exp, Cleanup, store)
tt := []struct {
name string
i string
v string
f bool
}{
{
"Missing",
"test",
"",
false,
},
{
"Found",
"google",
"https://google.com",
true,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
v, f := get(tc.i)
if !reflect.DeepEqual(v, tc.v) {
t.Fatalf("expected: %v, got %v", tc.v, v)
}
if !reflect.DeepEqual(f, tc.f) {
t.Fatalf("expected: %v, got %v", tc.f, f)
}
})
}
Pool.Flush()
}
func TestSet(t *testing.T) {
Pool = cache.New(Exp, Cleanup)
tt := []struct {
name string
k string
v string
f bool
}{
{
"Set",
"Key",
"Value",
true,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
set(tc.v, tc.k)
v, err := get(tc.k)
if !reflect.DeepEqual(tc.v, v) {
t.Fatalf("expected: %v, got %v", tc.v, v)
}
if !reflect.DeepEqual(err, true) {
t.Fatalf("Key %s not found", tc.k)
}
})
}
Pool.Flush()
}
func TestRandStringBytesMaskImprSrc(t *testing.T) {
tt := []struct {
name string
i int
}{
{
"10",
10,
},
{
"1",
1,
},
{
"100",
100,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
s := randStringBytesMaskImprSrc(tc.i)
if len(s) != tc.i {
t.Fatalf("Bad Length, expecting %v got %v", tc.i, len(s))
}
})
}
}
func benchmarkRandStringBytesMasImprSrc(i int, b *testing.B) {
for n := 0; n < b.N; n++ {
randStringBytesMaskImprSrc(i)
}
}
func BenchmarkRandStringBytesMasImprSrc10(b *testing.B) {
benchmarkRandStringBytesMasImprSrc(10, b)
}
func BenchmarkRandStringBytesMasImprSrc100(b *testing.B) {
benchmarkRandStringBytesMasImprSrc(100, b)
}
func BenchmarkRandStringBytesMasImprSrc1000(b *testing.B) {
benchmarkRandStringBytesMasImprSrc(1000, b)
}
func TestRedirect(t *testing.T) {
Pool = cache.NewFrom(Exp, Cleanup, store)
tt := []struct {
name string
i string
r string
err error
}{
{
"Not Found",
"badkey",
"",
ErrNotFound,
},
{
"Found",
"google",
"https://google.com",
nil,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
r, err := redirect(tc.i)
if !errors.Is(tc.err, err) {
t.Fatalf("Bad error, expected %v, got %v", tc.err, err)
}
if !reflect.DeepEqual(tc.r, r) {
t.Fatalf("Bad value, expected %v, got %v", tc.r, r)
}
})
}
}
func TestShortener(t *testing.T) {
tt := []struct {
name string
i string
s int
rs int
err error
}{
{
"Bad Request",
"badurl",
10,
0,
ErrBadRequest,
},
{
"Good Request",
"https://www.google.com",
10,
10,
nil,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
r, err := shortener(tc.i, tc.s)
if !errors.Is(tc.err, err) {
t.Fatalf("Bad error, expected %v, got %v", tc.err, err)
}
if len(r) != tc.rs {
t.Fatalf("Bad value, expected %v, got %v (%s)", tc.s, len(r), r)
}
})
Pool.Flush()
}
}
func TestDumpDbTOFile(t *testing.T) {
tt := []struct {
name string
p *cache.Cache
r int
f *os.File
err error
}{
{
"Error",
cache.New(Exp, Cleanup),
0,
func() *os.File { f, _ := os.Create(fmt.Sprintf("%s/.json", randStringBytesMaskImprSrc(5))); return f }(),
os.ErrInvalid,
},
{
"0-v",
cache.New(Exp, Cleanup),
0,
func() *os.File { f, _ := os.Create(fmt.Sprintf("%s.json", randStringBytesMaskImprSrc(5))); return f }(),
nil,
},
{
"1-v",
cache.NewFrom(Exp, Cleanup, store),
1,
func() *os.File { f, _ := os.Create(fmt.Sprintf("%s.json", randStringBytesMaskImprSrc(5))); return f }(),
nil,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
Pool = tc.p
r, err := dumpDbTOFile(tc.f)
if !errors.Is(tc.err, err) {
t.Fatalf("Bad error, expected %#v, got %#v", tc.err, err)
}
if tc.r != r {
t.Fatalf("Bad value, expected %#v, got %#v", tc.r, r)
}
})
if tc.f != nil {
os.Remove(tc.f.Name())
}
Pool.Flush()
}
}

View File

@ -11,8 +11,9 @@ import (
"strings"
"time"
"internal/shortie"
"github.com/valyala/fasthttp"
"github.com/wolviecb/short/shortie"
"github.com/asaskevich/govalidator"
"github.com/fasthttp/router"