From 2e84c00707fb5e618d9832b54890435260c6f513 Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Sun, 17 Jul 2016 16:15:37 +0100 Subject: [PATCH] Turn shortener.Shortener into an interface And have the default shortener be Base58Shortener. --- shortener/base58_shortener.go | 87 ++++++++++++++++++ ...tener_test.go => base58_shortener_test.go} | 22 ++--- shortener/new.go | 8 ++ shortener/shortener.go | 88 +------------------ 4 files changed, 110 insertions(+), 95 deletions(-) create mode 100644 shortener/base58_shortener.go rename shortener/{shortener_test.go => base58_shortener_test.go} (83%) create mode 100644 shortener/new.go diff --git a/shortener/base58_shortener.go b/shortener/base58_shortener.go new file mode 100644 index 0000000..7409e13 --- /dev/null +++ b/shortener/base58_shortener.go @@ -0,0 +1,87 @@ +package shortener + +import ( + "crypto/sha1" + "fmt" + + "github.com/jimeh/go-base58" + "github.com/jimeh/ozu.io/storage" +) + +var urlKeyPrefix = []byte("url:") +var uidKeyPrefix = []byte("uid:") + +// NewBase58 returns a new *Base58Shortner that uses the given storage.Store. +func NewBase58(store storage.Store) *Base58Shortener { + return &Base58Shortener{Store: store} +} + +// Base58Shortener shortens URLs via base 58 encoding. +type Base58Shortener struct { + Store storage.Store +} + +// Shorten a given URL. +func (s *Base58Shortener) Shorten(rawURL []byte) (uid []byte, url []byte, err error) { + url, err = NormalizeURL(rawURL) + if err != nil { + return nil, nil, err + } + + urlKey := s.makeURLKey(url) + uid, err = s.Store.Get(urlKey) + + if uid != nil && err == nil { + return uid, url, nil + } else if err != nil && err.Error() != "not found" { + return nil, nil, err + } + + uid, err = s.newUID() + if err != nil { + return nil, nil, err + } + + err = s.Store.Set(urlKey, uid) + if err != nil { + return nil, nil, err + } + + uidKey := s.makeUIDKey(uid) + err = s.Store.Set(uidKey, url) + if err != nil { + return nil, nil, err + } + + return uid, url, nil +} + +// Lookup the URL of a given UID. +func (s *Base58Shortener) Lookup(uid []byte) ([]byte, error) { + uidKey := s.makeUIDKey(uid) + + url, err := s.Store.Get(uidKey) + if err != nil { + return nil, err + } + + return url, nil +} + +func (s *Base58Shortener) newUID() ([]byte, error) { + index, err := s.Store.NextSequence() + if err != nil { + return nil, err + } + + return base58.Encode(index), nil +} + +func (s *Base58Shortener) makeUIDKey(uid []byte) []byte { + return append(uidKeyPrefix, uid...) +} + +func (s *Base58Shortener) makeURLKey(rawURL []byte) []byte { + urlSHA := fmt.Sprintf("%x", sha1.Sum(rawURL)) + return append(urlKeyPrefix, urlSHA...) +} diff --git a/shortener/shortener_test.go b/shortener/base58_shortener_test.go similarity index 83% rename from shortener/shortener_test.go rename to shortener/base58_shortener_test.go index cb0a121..d3cf7bb 100644 --- a/shortener/shortener_test.go +++ b/shortener/base58_shortener_test.go @@ -17,22 +17,22 @@ import ( // Suite Setup -type ShortenerSuite struct { +type Base58ShortenerSuite struct { suite.Suite store *mocks.Store - shortener *Shortener + shortener *Base58Shortener errNotFound error } -func (s *ShortenerSuite) SetupTest() { +func (s *Base58ShortenerSuite) SetupTest() { s.store = new(mocks.Store) - s.shortener = New(s.store) + s.shortener = NewBase58(s.store) s.errNotFound = errors.New("not found") } // Tests -func (s *ShortenerSuite) TestShortenExisting() { +func (s *Base58ShortenerSuite) TestShortenExisting() { rawURL := []byte("http://google.com/") uid := []byte("ig") urlSHA := fmt.Sprintf("%x", sha1.Sum(rawURL)) @@ -46,7 +46,7 @@ func (s *ShortenerSuite) TestShortenExisting() { s.store.AssertExpectations(s.T()) } -func (s *ShortenerSuite) TestShortenNew() { +func (s *Base58ShortenerSuite) TestShortenNew() { rawURL := []byte("https://google.com") url := []byte("https://google.com/") uid := []byte("ig") @@ -65,7 +65,7 @@ func (s *ShortenerSuite) TestShortenNew() { s.store.AssertExpectations(s.T()) } -func (s *ShortenerSuite) TestShortenInvalidURL() { +func (s *Base58ShortenerSuite) TestShortenInvalidURL() { examples := []struct { url string error string @@ -100,7 +100,7 @@ func (s *ShortenerSuite) TestShortenInvalidURL() { } } -func (s *ShortenerSuite) TestShortenStoreError() { +func (s *Base58ShortenerSuite) TestShortenStoreError() { url := []byte("https://google.com/") storeErr := errors.New("leveldb: something wrong") urlKey := append([]byte("url:"), fmt.Sprintf("%x", sha1.Sum(url))...) @@ -113,7 +113,7 @@ func (s *ShortenerSuite) TestShortenStoreError() { s.EqualError(err, storeErr.Error()) } -func (s *ShortenerSuite) TestLookupExisting() { +func (s *Base58ShortenerSuite) TestLookupExisting() { url := []byte("https://google.com/") uid := []byte("ig") @@ -126,7 +126,7 @@ func (s *ShortenerSuite) TestLookupExisting() { s.store.AssertExpectations(s.T()) } -func (s *ShortenerSuite) TestLookupNonExistant() { +func (s *Base58ShortenerSuite) TestLookupNonExistant() { uid := []byte("ig") s.store.On("Get", append([]byte("uid:"), uid...)).Return(nil, s.errNotFound) @@ -141,5 +141,5 @@ func (s *ShortenerSuite) TestLookupNonExistant() { // Run Suite func TestShortenerSuite(t *testing.T) { - suite.Run(t, new(ShortenerSuite)) + suite.Run(t, new(Base58ShortenerSuite)) } diff --git a/shortener/new.go b/shortener/new.go new file mode 100644 index 0000000..dc1b4e0 --- /dev/null +++ b/shortener/new.go @@ -0,0 +1,8 @@ +package shortener + +import "github.com/jimeh/ozu.io/storage" + +// New returns a new *Base58Shortner that uses the given storage.Store. +func New(store storage.Store) Shortener { + return NewBase58(store) +} diff --git a/shortener/shortener.go b/shortener/shortener.go index aae4a27..4679e54 100644 --- a/shortener/shortener.go +++ b/shortener/shortener.go @@ -1,87 +1,7 @@ package shortener -import ( - "crypto/sha1" - "fmt" - - "github.com/jimeh/go-base58" - "github.com/jimeh/ozu.io/storage" -) - -// New returns a new *Shortner that uses the given storage.Store. -func New(store storage.Store) *Shortener { - return &Shortener{Store: store} -} - -var urlKeyPrefix = []byte("url:") -var uidKeyPrefix = []byte("uid:") - -// Shortner interface -type Shortener struct { - Store storage.Store -} - -// Shorten a given URL. -func (s *Shortener) Shorten(rawURL []byte) (uid []byte, url []byte, err error) { - url, err = NormalizeURL(rawURL) - if err != nil { - return nil, nil, err - } - - urlKey := s.makeURLKey(url) - uid, err = s.Store.Get(urlKey) - - if uid != nil && err == nil { - return uid, url, nil - } else if err != nil && err.Error() != "not found" { - return nil, nil, err - } - - uid, err = s.newUID() - if err != nil { - return nil, nil, err - } - - err = s.Store.Set(urlKey, uid) - if err != nil { - return nil, nil, err - } - - uidKey := s.makeUIDKey(uid) - err = s.Store.Set(uidKey, url) - if err != nil { - return nil, nil, err - } - - return uid, url, nil -} - -// Lookup the URL of a given UID. -func (s *Shortener) Lookup(uid []byte) ([]byte, error) { - uidKey := s.makeUIDKey(uid) - - url, err := s.Store.Get(uidKey) - if err != nil { - return nil, err - } - - return url, nil -} - -func (s *Shortener) newUID() ([]byte, error) { - index, err := s.Store.NextSequence() - if err != nil { - return nil, err - } - - return base58.Encode(index), nil -} - -func (s *Shortener) makeUIDKey(uid []byte) []byte { - return append(uidKeyPrefix, uid...) -} - -func (s *Shortener) makeURLKey(rawURL []byte) []byte { - urlSHA := fmt.Sprintf("%x", sha1.Sum(rawURL)) - return append(urlKeyPrefix, urlSHA...) +// Shortener defines a shortener interface for shortening URLs. +type Shortener interface { + Shorten([]byte) ([]byte, []byte, error) + Lookup([]byte) ([]byte, error) }