43 Commits

Author SHA1 Message Date
Colin Henry
3d71b65428 added trie
Some checks failed
Go / build (1.23) (push) Failing after 2m42s
2025-07-05 11:56:37 -07:00
Colin Henry
4cf0b874d6 fixed tio arvelie 2025-07-05 11:56:37 -07:00
Colin Henry
475e091d21 remove drone.yml
All checks were successful
Go / build (1.23) (push) Successful in 2m53s
2025-06-20 00:07:25 -07:00
Colin Henry
56752521c2 added doc
All checks were successful
Go / build (1.23) (push) Successful in 2m31s
2025-03-18 22:28:20 -07:00
Colin Henry
6aa7788fd0 added transactional wrapper, and support for it 2025-03-10 22:59:03 -07:00
Colin Henry
f88f00e78c fixed release target
All checks were successful
Go / build (1.23) (push) Successful in 17s
2024-10-27 12:30:45 -07:00
Colin Henry
b7969f89b5 fixed release target
All checks were successful
Go / build (1.23) (push) Successful in 17s
2024-10-27 12:28:57 -07:00
Colin Henry
7e1daaf7d5 added makefile to do releases
All checks were successful
Go / build (1.23) (push) Successful in 28s
2024-10-27 12:04:01 -07:00
Colin Henry
40bc496b20 added makefile to do releases 2024-10-27 12:03:34 -07:00
Colin Henry
401f5c3848 WIP: added container/graph
All checks were successful
Go / build (1.23) (push) Successful in 2m8s
2024-10-27 11:58:47 -07:00
Colin Henry
f0fd3f8df1 added come comments
Some checks failed
Go / build (1.23) (push) Has been cancelled
2024-10-27 11:58:07 -07:00
Colin Henry
8de3b04032 fix(ci): updated support matrix
All checks were successful
Go / build (1.23) (push) Successful in 3m3s
2024-09-29 12:02:35 -07:00
Colin Henry
f44e43174c added wrapper error that will provide file/line origin of an error
Some checks failed
Go / build (1.19) (push) Failing after 13s
2024-09-29 11:59:01 -07:00
Colin Henry
fe49018479 added new check capability and added assets to a few other packages
Some checks failed
Go / build (1.19) (push) Failing after 22s
2024-09-28 15:19:00 -07:00
Colin Henry
94bc398ea7 added assert function
Some checks failed
Go / build (1.19) (push) Failing after 4m5s
2024-09-06 20:29:29 -07:00
Colin Henry
2973912fde updated minimum go version 2024-09-06 20:28:23 -07:00
Colin Henry
b738f78e6e reomved now unnecessary libraries 2024-09-06 20:27:26 -07:00
Colin Henry
74f9fc64f4 removed deprecated log package 2024-09-05 23:36:49 -07:00
Colin Henry
612a5c8387 removing deprecated rest package 2024-09-05 22:32:04 -07:00
Colin Henry
a1e52a7399 changing module name 2024-09-05 22:30:30 -07:00
Colin Henry
79e3d1f0b9 fixed busted test step
All checks were successful
Go / build (1.19) (push) Successful in 46s
2024-04-27 15:43:57 -07:00
Colin Henry
8b77b5acc8 added gitea action
Some checks failed
Go / build (1.19) (push) Failing after 3m29s
2024-04-26 23:20:51 -07:00
Colin Henry
4665f2ccf9 deprecating packages 2023-08-11 21:59:16 -07:00
Colin Henry
7154029136 linted 2023-04-05 21:20:45 -07:00
Colin Henry
9f88dc9530 added ISO8601 constant 2023-04-04 23:19:22 -07:00
Colin Henry
1ec6f3c5c3 updated doc 2023-04-04 23:19:05 -07:00
Colin Henry
390a54eb96 updated log interface name 2023-04-04 23:17:39 -07:00
Colin Henry
d312b58fdf updated go.mod for compatability with generics, as used by the cache 2023-01-14 00:12:30 -08:00
Colin Henry
b19ba53491 added appropriate attribution
for tierd cache stub
2023-01-14 00:09:00 -08:00
Colin Henry
859849bd47 stub of tiered cache 2022-12-15 22:00:51 -08:00
Colin Henry
7cf53a2f2d sped up mappedParam by removing joins and splits 2022-09-15 21:28:19 -07:00
Colin Henry
593c8db66e fixed unit tests 2022-09-15 21:21:51 -07:00
Colin Henry
74c99d013c well, maybe a little 2022-09-14 15:43:05 -07:00
Colin Henry
618cdcc095 no mo way 2022-09-14 15:41:43 -07:00
Colin Henry
32337f4bc8 fixed non-standard return code 2022-09-14 15:41:15 -07:00
Colin Henry
888e1b9c7d new functions to extract certain types of parameters 2022-09-14 15:39:41 -07:00
Colin Henry
086c404610 changed parameter identifier to brackets over a colon, is more compatable with OpenAPI 2022-08-20 13:43:39 -07:00
Colin Henry
f9a712bc33 added to the logger interface 2022-08-05 22:04:20 -07:00
Colin Henry
dc5f8b6e5a new ServeMux based on Mat Ryer's Way. removed method support in the mux to allow it to be handled by the handlers themselves. Keeps the standard library net/http handler contract and provides better support for the handlers provided by x's net/http. 2022-07-08 19:32:45 -07:00
Colin Henry
62911f042d update library to build for 1.18 2022-06-28 20:23:08 -07:00
Colin Henry
31cb99467c fix incorrect return code 2020-12-10 09:25:23 -08:00
Colin Henry
8a1403a35d nitpicks. 2020-10-23 21:55:48 -07:00
Colin Henry
07db4cf999 dont make it directional 2020-09-04 20:24:37 -07:00
29 changed files with 580 additions and 235 deletions

View File

@@ -1,10 +0,0 @@
kind: pipeline
type: docker
name: default
steps:
- name: test
image: golang
commands:
- go test ./...
- go build ./...

View File

@@ -0,0 +1,18 @@
name: Go
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ '1.23' ]
steps:
- uses: actions/checkout@v4
- name: Setup Go ${{ matrix.go-version }}
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...

19
Makefile Normal file
View File

@@ -0,0 +1,19 @@
# Variables
REPO_REMOTE := origin
# Helper function to get the latest tag and increment the version
define next_version
latest_tag=$$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0"); \
major=$$(echo $$latest_tag | sed -E 's/^v([0-9]+)\.[0-9]+\.[0-9]+/\1/'); \
minor=$$(echo $$latest_tag | sed -E 's/^v[0-9]+\.([0-9]+)\.[0-9]+/\1/'); \
patch=$$(echo $$latest_tag | sed -E 's/^v[0-9]+\.[0-9]+\.([0-9]+)/\1/'); \
next_patch=$$(($$patch + 1)); \
echo "v$$major.$$minor.$$next_patch"
endef
# Target to tag and push the next version
release:
next_version=$$($(next_version)); \
echo "Tagging with version $$next_version"; \
git tag $$next_version; \
git push $(REPO_REMOTE) $$next_version

View File

@@ -7,7 +7,7 @@ A mix of useful packages, some are works in progress.
## Install
```
go get github.com/jchenry/x
go get git.sdf.org/jchenry/x
```
## Contributing

3
cache/doc.go vendored Normal file
View File

@@ -0,0 +1,3 @@
package cache
// Cache package cribbed from https://varunksaini.com/tiered-cache-in-go/ whom i have i high respect for as a former colleague.

6
cache/interface.go vendored Normal file
View File

@@ -0,0 +1,6 @@
package cache
type Interface[K comparable, V any] interface {
Get(key K) V
Put(key K, value V)
}

40
cache/tiered.go vendored Normal file
View File

@@ -0,0 +1,40 @@
package cache
import (
"reflect"
"git.sdf.org/jchenry/x"
)
type tieredCache[K comparable, V any] struct {
inner Interface[K, V]
outer Interface[K, V]
}
func NewTieredCache[K comparable, V any](inner, outer Interface[K, V]) Interface[K, V] {
x.Assert(inner != nil, "cache.NewTieredCache: inner cannot be nil")
x.Assert(outer != nil, "cache.NewTieredCache: outer cannot be nil")
return &tieredCache[K, V]{
inner: inner,
outer: outer,
}
}
func (t *tieredCache[K, V]) Get(key K) V {
var zero, value V
value = t.inner.Get(key)
if reflect.DeepEqual(value, zero) {
value = t.outer.Get(key)
// if required, add value to inner cache for future requests
}
return value
}
func (t *tieredCache[K, V]) Put(key K, value V) {
t.inner.Put(key, value)
// add key to outer cache asynchronously
go func(key K) {
t.outer.Put(key, value)
}(key)
}

41
container/graph/topo.go Normal file
View File

@@ -0,0 +1,41 @@
package sort
// Node is a generic interface representing a graph node.
type Node[T any] interface {
// Value returns the value of the node.
Value() T
// Adjacencies returns a slice of nodes adjacent to this node
Adjacencies() []Node[T]
}
// Topo performs a topological sort on a slice of nodes in place.
func TopoSort[T any](nodes []Node[T]) {
v := make(map[Node[T]]bool)
pos := 0
var dfs func(Node[T])
dfs = func(n Node[T]) {
v[n] = true
for _, e := range n.Adjacencies() {
if !v[e] {
dfs(e)
}
}
nodes[pos] = n
pos++
}
for _, n := range nodes {
if !v[n] {
dfs(n)
}
}
}
// RTopoSort performs a reverse topological sort on a slice of nodes in place.
func RTopoSort[T any](nodes []Node[T]) {
TopoSort(nodes)
for i, j := 0, len(nodes)-1; i < j; i, j = i+1, j-1 {
nodes[i], nodes[j] = nodes[j], nodes[i]
}
}

101
container/trie/trie.go Normal file
View File

@@ -0,0 +1,101 @@
package trie
import (
"sort"
)
// Node represents a node in the trie
type Node struct {
children map[rune]*Node
isEnd bool
}
// New creates a new empty trie root node
func New() *Node {
return &Node{
children: make(map[rune]*Node),
isEnd: false,
}
}
// Add adds a word to the trie
func (n *Node) Add(word string) {
current := n
for _, char := range word {
if _, exists := current.children[char]; !exists {
current.children[char] = &Node{
children: make(map[rune]*Node),
isEnd: false,
}
}
current = current.children[char]
}
current.isEnd = true
}
// countWords recursively counts the number of words in the subtree
func (n *Node) countWords() int {
count := 0
if n.isEnd {
count++
}
for _, child := range n.children {
count += child.countWords()
}
return count
}
// Count returns the number of words that have the given prefix
func (n *Node) Count(prefix string) int {
current := n
for _, char := range prefix {
if _, exists := current.children[char]; !exists {
return 0
}
current = current.children[char]
}
return current.countWords()
}
// PrefixCount represents a prefix and its count
type PrefixCount struct {
Prefix string
Count int
}
// collectPrefixes recursively collects all prefixes and their counts
func collectPrefixes(n *Node, currentPrefix string, results *[]PrefixCount) {
count := n.countWords()
if count > 0 {
*results = append(*results, PrefixCount{
Prefix: currentPrefix,
Count: count,
})
}
// Sort children by rune to ensure consistent traversal order
chars := make([]rune, 0, len(n.children))
for char := range n.children {
chars = append(chars, char)
}
sort.Slice(chars, func(i, j int) bool {
return chars[i] < chars[j]
})
for _, char := range chars {
collectPrefixes(n.children[char], currentPrefix+string(char), results)
}
}
// TopPrefixes returns a sorted list of prefixes by their counts
func TopPrefixes(n *Node) []PrefixCount {
var results []PrefixCount
collectPrefixes(n, "", &results)
sort.Slice(results, func(i, j int) bool {
if results[i].Count == results[j].Count {
return results[i].Prefix < results[j].Prefix
}
return results[i].Count > results[j].Count
})
return results
}

163
container/trie/trie_test.go Normal file
View File

@@ -0,0 +1,163 @@
package trie
import (
"testing"
)
func TestNew(t *testing.T) {
root := New()
if root == nil {
t.Error("New() returned nil")
}
if root.children == nil {
t.Error("New() root node has nil children map")
}
if root.isEnd {
t.Error("New() root node should not be marked as end")
}
}
func TestAddAndCount(t *testing.T) {
tests := []struct {
name string
words []string
prefix string
expected int
}{
{
name: "empty trie",
words: []string{},
prefix: "test",
expected: 0,
},
{
name: "single word",
words: []string{"hello"},
prefix: "hel",
expected: 1,
},
{
name: "multiple words same prefix",
words: []string{"hello", "help", "hell"},
prefix: "hel",
expected: 3,
},
{
name: "prefix not found",
words: []string{"hello", "help", "hell"},
prefix: "xyz",
expected: 0,
},
{
name: "empty prefix",
words: []string{"hello", "help", "hell"},
prefix: "",
expected: 3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
root := New()
for _, word := range tt.words {
root.Add(word)
}
if got := root.Count(tt.prefix); got != tt.expected {
t.Errorf("Count(%q) = %d, want %d", tt.prefix, got, tt.expected)
}
})
}
}
func TestTopPrefixes(t *testing.T) {
tests := []struct {
name string
words []string
expected []PrefixCount
}{
{
name: "empty trie",
words: []string{},
expected: []PrefixCount{},
},
{
name: "single word",
words: []string{"hello"},
expected: []PrefixCount{
{Prefix: "", Count: 1},
{Prefix: "h", Count: 1},
{Prefix: "he", Count: 1},
{Prefix: "hel", Count: 1},
{Prefix: "hell", Count: 1},
{Prefix: "hello", Count: 1},
},
},
{
name: "multiple words",
words: []string{"hello", "help", "hell", "helicopter"},
expected: []PrefixCount{
{Prefix: "", Count: 4},
{Prefix: "h", Count: 4},
{Prefix: "he", Count: 4},
{Prefix: "hel", Count: 4},
{Prefix: "hell", Count: 2},
{Prefix: "hello", Count: 1},
{Prefix: "help", Count: 1},
{Prefix: "heli", Count: 1},
{Prefix: "helic", Count: 1},
{Prefix: "helico", Count: 1},
{Prefix: "helicop", Count: 1},
{Prefix: "helicopt", Count: 1},
{Prefix: "helicopte", Count: 1},
{Prefix: "helicopter", Count: 1},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
root := New()
for _, word := range tt.words {
root.Add(word)
}
got := TopPrefixes(root)
if len(got) != len(tt.expected) {
t.Errorf("TopPrefixes() returned %d results, want %d", len(got), len(tt.expected))
return
}
for i, want := range tt.expected {
if i >= len(got) {
break
}
if got[i].Prefix != want.Prefix || got[i].Count != want.Count {
t.Errorf("TopPrefixes()[%d] = {Prefix: %q, Count: %d}, want {Prefix: %q, Count: %d}",
i, got[i].Prefix, got[i].Count, want.Prefix, want.Count)
}
}
})
}
}
func TestTopPrefixesSorting(t *testing.T) {
root := New()
words := []string{"a", "ab", "abc", "abcd", "abcde"}
for _, word := range words {
root.Add(word)
}
prefixes := TopPrefixes(root)
// Verify sorting by count (descending)
for i := 1; i < len(prefixes); i++ {
if prefixes[i].Count > prefixes[i-1].Count {
t.Errorf("Prefixes not sorted by count: %v", prefixes)
}
}
// Verify alphabetical sorting for equal counts
for i := 1; i < len(prefixes); i++ {
if prefixes[i].Count == prefixes[i-1].Count && prefixes[i].Prefix < prefixes[i-1].Prefix {
t.Errorf("Prefixes with equal counts not sorted alphabetically: %v", prefixes)
}
}
}

View File

@@ -3,20 +3,25 @@ package database
import (
"context"
"database/sql"
"git.sdf.org/jchenry/x"
)
type Func func(db *sql.DB)
type Func func(ctx context.Context, db *sql.DB) error
type Actor struct {
DB *sql.DB
ActionChan <-chan Func
ActionChan chan Func
}
func (a *Actor) Run(ctx context.Context) error {
x.Assert(ctx != nil, "Actor.Run: context cannot be nil")
for {
select {
case f := <-a.ActionChan:
f(a.DB)
if err:= f(ctx, a.DB); err != nil{
return err
}
case <-ctx.Done():
return ctx.Err()
}

View File

@@ -7,9 +7,21 @@ Example usage:
ctx, _ := context.WithCancel(context.Background())
dba = &db.Actor{
DB: s.db,
ActionChan: make(chan db.Func),
ActionChan: make(chan database.Func),
}
go dba.Run(ctx)
*/
/*
Transactor Example:
func insert(ctx context.Context, db *sql.DB) error{
// SQL HERE
}
...
dba.ActionChan <- db.WithTransaction(insert)
*/

29
database/transactor.go Normal file
View File

@@ -0,0 +1,29 @@
package database
import (
"context"
"database/sql"
"fmt"
)
// WithinTransaction is a functional equivalent of the Transactor interface created by Thibaut Rousseau's
// https://blog.thibaut-rousseau.com/blog/sql-transactions-in-go-the-good-way/
func WithinTransaction(f Func) Func {
return func(ctx context.Context, db *sql.DB) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err)
}
if err := f(ctx, db); err != nil {
_ = tx.Rollback()
return err
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("failed to commit transaction: %w", err)
}
return nil
}
}

4
go.mod
View File

@@ -1,3 +1,3 @@
module github.com/jchenry/x
module git.sdf.org/jchenry/x
go 1.13
go 1.22

View File

@@ -1,15 +0,0 @@
package log
// Logger is a loggin interface with only the essentials that a function that needs to log should care about. Compatible with standard Go logger.
type Logger interface {
Print(v ...interface{})
Printf(format string, v ...interface{})
Println(v ...interface{})
}
// None provides a logger that doesnt log anything
type None struct{}
func (n None) Print(v ...interface{}) {}
func (n None) Printf(format string, v ...interface{}) {}
func (n None) Println(v ...interface{}) {}

View File

@@ -6,9 +6,13 @@ import (
"fmt"
"net/http"
"strings"
"git.sdf.org/jchenry/x"
)
func BasicAuth(h http.Handler, htpasswd map[string]string, realm string) http.HandlerFunc {
x.Assert(len(htpasswd) > 0, "http.BasicAuth: htpassword cannot be empty")
x.Assert(len(realm) > 0, "http.BasicAuth: realm cannot be empty")
rlm := fmt.Sprintf(`Basic realm="%s"`, realm)
sha1 := func(password string) string {
s := sha1.New()
@@ -20,7 +24,7 @@ func BasicAuth(h http.Handler, htpasswd map[string]string, realm string) http.Ha
user, pass, _ := r.BasicAuth()
if pw, ok := htpasswd[user]; !ok || !strings.EqualFold(pass, sha1(pw)) {
w.Header().Set("WWW-Authenticate", rlm)
http.Error(w, "Unauthorized.", 401)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
h.ServeHTTP(w, r)

View File

@@ -30,7 +30,7 @@ func MutliHandler(h map[string]http.Handler) (http.HandlerFunc, error) {
if hdlr, ok := h[r.Method]; ok {
hdlr.ServeHTTP(w, r)
} else {
NotFoundHandler.ServeHTTP(w, r)
NotAllowedHandler.ServeHTTP(w, r)
}
}, nil
}

View File

@@ -11,11 +11,12 @@ type StatusHandler int
func (s StatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
code := int(s)
w.WriteHeader(code)
io.WriteString(w, http.StatusText(code))
_, _ = io.WriteString(w, http.StatusText(code))
}
var (
NotFoundHandler = StatusHandler(404)
NotImplementedHandler = StatusHandler(501)
NotLegalHandler = StatusHandler(451)
NotFoundHandler = StatusHandler(http.StatusNotFound)
NotImplementedHandler = StatusHandler(http.StatusNotImplemented)
NotLegalHandler = StatusHandler(http.StatusUnavailableForLegalReasons)
NotAllowedHandler = StatusHandler(http.StatusMethodNotAllowed)
)

96
pkg.go Normal file
View File

@@ -0,0 +1,96 @@
package x
import (
"errors"
"fmt"
"runtime"
)
// Assert evalueates a condition and if it fails, panics.
func Assert(cond bool, msg string) {
if !cond {
panic(errors.New(msg))
}
}
// Check evaluates a condition, adds an error to its list and continues
func Check(cond bool, err error) I {
return new(invariants).Check(cond, err)
}
// func ExampleCheck() {
// }
type I interface {
Check(cond bool, err error) I
Join() error
First() error
All() []error
}
type invariants struct {
errs []error
}
// Check evaluates a condition, adds an error to its list and continues
func (i *invariants) Check(cond bool, err error) I {
if !cond {
i.errs = append(i.errs, err)
}
return i
}
// Join returns all an error wrapping all errors that have been seen by the checks
func (i *invariants) Join() error {
return errors.Join(i.errs...)
}
// First returns the first error found by the checks
func (i *invariants) First() error {
if len(i.errs) > 0 {
return i.errs[0]
}
return nil
}
// All returns all errors found by the checks as a list of errors
func (i *invariants) All() []error {
return i.errs
}
type xError struct {
LineNo int
File string
E error
Debug bool
}
func (e *xError) Error() string {
if e.Debug {
return fmt.Sprintf(
"%s\n\t%s:%d", e.E, e.File, e.LineNo,
)
}
return e.E.Error()
}
func (e *xError) Unwrap() error {
return e.E
}
// NewError return an error that wrapped an error and optionally provides the file and line number the error occured on
func NewError(unwrapped error, debug bool) error {
_, file, line, ok := runtime.Caller(1)
if !ok {
file = "unknown"
line = 0
}
return &xError{
LineNo: line,
File: file,
E: unwrapped,
Debug: debug,
}
}

View File

@@ -1,95 +0,0 @@
package rest
import (
"net/http"
"net/url"
"path/filepath"
"sync"
"github.com/jchenry/x/encoding"
"github.com/jchenry/x/log"
)
// Example: Resource(p, c, JSONEncoder, json.Decode(func()interface{}{return &foo{}}), log.None{})
func Resource(p *sync.Pool, g Gateway, e EntityEncoder, d encoding.Decoder, l log.Logger) http.HandlerFunc {
return restVerbHandler(
GetResource(g, e, l),
PostResource(g, d, p, l),
PutResource(g, e, d, p, l),
DeleteResource(g, l),
)
}
func GetResource(store Readable, encode EntityEncoder, log log.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { // GET
if id := filepath.Base(r.URL.Path); id != "" {
if e, err := store.Read(id); err == nil { // handle individual entity
encode(w, e)
} else {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("Error: %s", err)
}
} else {
if params, err := url.ParseQuery(r.URL.RawQuery); err == nil {
if e, err := store.All(params); err == nil { // handle all entities
encode(w, e)
} else {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("Error: %s", err)
}
} else {
w.WriteHeader(http.StatusBadRequest)
}
}
}
}
func PostResource(store Creatable, decode encoding.Decoder, pool *sync.Pool, log log.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { // POST TODO
e := pool.Get()
defer pool.Put(e)
if err := decode(r.Body, e); err == nil {
if err = store.Create(e); err == nil {
w.WriteHeader(http.StatusCreated)
} else {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("Error: %s", err)
}
} else {
w.WriteHeader(http.StatusBadRequest)
}
}
}
func PutResource(store Updatable, encode EntityEncoder, decode encoding.Decoder, pool *sync.Pool, log log.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { // PUT TODO
e := pool.Get()
defer pool.Put(e)
if err := decode(r.Body, e); err == nil {
if err = store.Update(e); err == nil {
w.WriteHeader(http.StatusAccepted)
encode(w, e)
} else {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("Error: %s", err)
}
} else {
w.WriteHeader(http.StatusBadRequest)
}
}
}
func DeleteResource(store Deletable, log log.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { // DELETE TODO
if id := filepath.Base(r.URL.Path); id != "" {
if err := store.Delete(id); err == nil {
w.WriteHeader(http.StatusNoContent)
} else {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("Error: %s", err)
}
} else {
w.WriteHeader(http.StatusBadRequest)
}
}
}

View File

@@ -1,23 +0,0 @@
package rest
import (
gohttp "net/http"
"github.com/jchenry/x/net/http"
)
// EntityHandler returns a handler that provides restful verbs, following a CRUD model
func restVerbHandler(
get gohttp.Handler,
post gohttp.Handler,
put gohttp.Handler,
delete gohttp.Handler,
) gohttp.HandlerFunc {
h, _ := http.MutliHandler(map[string]gohttp.Handler{
gohttp.MethodGet: get,
gohttp.MethodPost: post,
gohttp.MethodPut: put,
gohttp.MethodDelete: delete,
})
return h
}

View File

@@ -1,25 +0,0 @@
package rest
type Creatable interface {
Create(e interface{}) error
}
type Updatable interface {
Update(e interface{}) error
}
type Deletable interface {
Delete(id string) error
}
type Readable interface {
All(filters map[string][]string) (interface{}, error)
Read(id string) (interface{}, error)
}
type Gateway interface {
Creatable
Updatable
Deletable
Readable
}

View File

@@ -1,32 +0,0 @@
package rest
import (
"net/http"
"github.com/jchenry/x/encoding"
"github.com/jchenry/x/encoding/json"
"github.com/jchenry/x/encoding/xml"
)
type EntityEncoder func(w http.ResponseWriter, e interface{})
func JSONEncoder(w http.ResponseWriter, e interface{}) error {
return EntityResponseEncoder(w, "application/json", json.Encoder, e)
}
func XMLEncoder(w http.ResponseWriter, e interface{}) error {
return EntityResponseEncoder(w, "application/xml", xml.Encoder, e)
}
func EntityResponseEncoder(w http.ResponseWriter, contentType string, encoder encoding.Encoder, e interface{}) error {
w.Header().Set("content-type", contentType)
return encoder(w, e)
}
func ErrorResponseEncoder(w http.ResponseWriter, contentType string, encoder encoding.Encoder, status int, err error) error {
w.WriteHeader(status)
return EntityResponseEncoder(w, contentType, encoder, map[string]interface{}{
"status": status,
"message": err.Error,
})
}

View File

@@ -1,7 +1,6 @@
package snowflake
import (
"fmt"
"hash/fnv"
"math"
"net"
@@ -15,42 +14,48 @@ const (
nodeIDBits = 10
sequenceBits = 12
// Custom Epoch (January 1, 2015 Midnight UTC = 2015-01-01T00:00:00Z)
// Custom Epoch (January 1, 2015 Midnight UTC = 2015-01-01T00:00:00Z) .
customEpoch int64 = 1420070400000
)
var maxNodeID int64
var maxSequence int64
var (
maxNodeID int64
maxSequence int64
timestampMutex sync.Mutex
sequenceMutex sync.Mutex
nodeID int64
lastTimestamp int64 = 0
sequence int64
)
var nodeID int64
var lastTimestamp int64 = 0
var sequence int64
const two = 2
func init() {
maxNodeID = int64(math.Pow(2, nodeIDBits) - 1)
maxSequence = int64(math.Pow(2, sequenceBits) - 1)
maxNodeID = int64(math.Pow(two, nodeIDBits) - 1)
maxSequence = int64(math.Pow(two, sequenceBits) - 1)
nodeID = generateNodeID()
}
func generateNodeID() int64 {
var nodeID int64
if interfaces, err := net.Interfaces(); err == nil {
h := fnv.New32a()
for _, i := range interfaces {
h.Write(i.HardwareAddr)
}
nodeID = int64(h.Sum32())
} else {
panic("interfaces not available")
}
nodeID = nodeID & maxNodeID
return nodeID
}
var timestampMutex sync.Mutex
var sequenceMutex sync.Mutex
// Next returns the next logical snowflake
// Next returns the next logical snowflake.
func Next() int64 {
timestampMutex.Lock()
currentTimestamp := ts()
@@ -76,7 +81,6 @@ func Next() int64 {
id |= (nodeID << sequenceBits)
id |= sequence
fmt.Printf("%b\n", id)
return id
}
@@ -88,5 +92,6 @@ func waitNextMillis(currentTimestamp int64) int64 {
for currentTimestamp == lastTimestamp {
currentTimestamp = ts()
}
return currentTimestamp
}

View File

@@ -9,7 +9,6 @@ func TestNext(t *testing.T) {
fmt.Printf("node id: %b\n", generateNodeID())
fmt.Printf("timestamp: %b\n", ts())
fmt.Printf("full token: %b\n", Next())
// t.Fail()
}
func BenchmarkNext(b *testing.B) {

View File

@@ -10,7 +10,7 @@ import (
type Arvelie string
func (a *Arvelie) isValid() bool {
func (a *Arvelie) IsValid() bool {
if a != nil {
return strings.EqualFold(string(*a), string(FromDate(ToDate(*a))))
}
@@ -29,7 +29,7 @@ func ToDate(a Arvelie) time.Time {
mon = (int(m[0]) - 65)
}
doty := (math.Floor(float64(mon)*14) + math.Floor(float64(d)) - 1)
doty := (math.Floor(float64(mon)*14) + float64(d) - 1)
yr, _ := strconv.Atoi(fmt.Sprintf("20%s", y))
return time.Date(yr, 1, 1, 0, 0, 0, 0, time.UTC).AddDate(0, 0, int(doty))
}
@@ -42,17 +42,15 @@ func FromDate(date time.Time) Arvelie {
if doty == 365 || doty == 366 {
m = "+"
} else {
m = strings.ToUpper(string([]byte{byte(97 + math.Floor(float64(doty/14)))}))
m = strings.ToUpper(string([]byte{byte(97 + math.Floor(float64(doty)/14))}))
}
var d string
switch doty {
case 365:
d = fmt.Sprintf("%02d", 1)
break
case 366:
d = fmt.Sprintf("%02d", 2)
break
default:
d = fmt.Sprintf("%02d", (doty % 14))
}

View File

@@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/jchenry/x/time/arvelie"
"git.sdf.org/jchenry/x/time/arvelie"
)
func TestFromDate(t *testing.T) {

5
time/format.go Normal file
View File

@@ -0,0 +1,5 @@
package time
import "time"
const ISO8601 = time.RFC3339

View File

@@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/jchenry/x/time/neralie"
"git.sdf.org/jchenry/x/time/neralie"
)
func TestFromTime(t *testing.T) {