Skip to content

Commit

Permalink
initial cut of a registry (#137)
Browse files Browse the repository at this point in the history
* initial cut of a registry

* go mod tidy

* fixup generate with memory loader, add tests

Signed-off-by: Sarah Funkhouser <[email protected]>

---------

Signed-off-by: Matt Anderson <[email protected]>
Signed-off-by: Sarah Funkhouser <[email protected]>
Co-authored-by: Sarah Funkhouser <[email protected]>
  • Loading branch information
matoszz and golanglemonade authored Oct 16, 2024
1 parent b0a8d12 commit 1edf628
Show file tree
Hide file tree
Showing 10 changed files with 1,771 additions and 2 deletions.
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.23.2

require (
ariga.io/entcache v0.1.0
dario.cat/mergo v1.0.1
entgo.io/contrib v0.6.0
entgo.io/ent v0.14.1
github.com/99designs/gqlgen v0.17.54
Expand All @@ -15,7 +16,9 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.27.43
github.com/aws/aws-sdk-go-v2/credentials v1.17.41
github.com/aws/aws-sdk-go-v2/service/s3 v1.65.3
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df
github.com/brianvoe/gofakeit/v7 v7.0.4
github.com/danielgtaylor/huma/v2 v2.23.0
github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0
github.com/gertd/go-pluralize v0.2.1
github.com/getkin/kin-openapi v0.128.0
Expand Down Expand Up @@ -50,6 +53,7 @@ require (
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.13.0
github.com/rs/zerolog v1.33.0
github.com/samber/lo v1.47.0
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1
github.com/sebdah/goldie/v2 v2.5.5
github.com/spf13/cobra v1.8.1
github.com/stoewer/go-strcase v1.3.0
Expand All @@ -66,6 +70,7 @@ require (
github.com/theopenlane/riverboat v0.0.7
github.com/theopenlane/utils v0.3.0
github.com/vektah/gqlparser/v2 v2.5.17
github.com/wk8/go-ordered-map/v2 v2.1.8
github.com/wundergraph/graphql-go-tools v1.67.4
gocloud.dev v0.40.0
golang.org/x/crypto v0.28.0
Expand Down Expand Up @@ -251,13 +256,11 @@ require (
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/urfave/cli/v2 v2.27.4 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ cloud.google.com/go/kms v1.18.5 h1:75LSlVs60hyHK3ubs2OHd4sE63OAMcM2BdSJc2bkuM4=
cloud.google.com/go/kms v1.18.5/go.mod h1:yXunGUGzabH8rjUPImp2ndHiGolHeWJJ0LODLedicIY=
cloud.google.com/go/longrunning v0.5.12 h1:5LqSIdERr71CqfUsFlJdBpOkBH8FBCFD7P1nTWy3TYE=
cloud.google.com/go/longrunning v0.5.12/go.mod h1:S5hMV8CDJ6r50t2ubVJSKQVv5u0rmik5//KgLO3k4lU=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
entgo.io/contrib v0.6.0 h1:xfo4TbJE7sJZWx7BV7YrpSz7IPFvS8MzL3fnfzZjKvQ=
entgo.io/contrib v0.6.0/go.mod h1:3qWIseJ/9Wx2Hu5zVh15FDzv7d/UvKNcYKdViywWCQg=
entgo.io/ent v0.14.1 h1:fUERL506Pqr92EPHJqr8EYxbPioflJo6PudkrEA8a/s=
Expand Down Expand Up @@ -102,6 +104,8 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df h1:GSoSVRLoBaFpOOds6QyY1L8AX7uoY+Ln3BHc22W40X0=
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df/go.mod h1:hiVxq5OP2bUGBRNS3Z/bt/reCLFNbdcST6gISi1fiOM=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
Expand Down Expand Up @@ -140,6 +144,8 @@ github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/danielgtaylor/huma/v2 v2.23.0 h1:0Q3Mq+KTYr6shFqx3gQulDTVwR9xa6/SmSmbDJCRyMI=
github.com/danielgtaylor/huma/v2 v2.23.0/go.mod h1:2NZmGf/A+SstJYQlq0Xp4nsTDCmPvKS2w9vI8c9sf1A=
github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -191,6 +197,8 @@ github.com/getkin/kin-openapi v0.128.0 h1:jqq3D9vC9pPq1dGcOCv7yOp1DaEe7c/T1vzcLb
github.com/getkin/kin-openapi v0.128.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
github.com/go-faster/jx v1.1.0 h1:ZsW3wD+snOdmTDy9eIVgQdjUpXRRV4rqW8NS3t+20bg=
Expand Down Expand Up @@ -515,6 +523,8 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY=
github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
Expand Down
2 changes: 2 additions & 0 deletions pkg/registry/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package registry provides a basic jsonschema registry for creating / viewing jsonschema definitions
package registry
279 changes: 279 additions & 0 deletions pkg/registry/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
package registry

import (
"encoding/base64"
"fmt"
"net"
"net/http"
"net/url"
"regexp"
"time"
)

// FormatFunc validates the customized format in json schema
type FormatFunc func(v interface{}) error

var (
formatsFuncs = map[string]FormatFunc{
"urlname": urlName,
"httpmethod": httpMethod,
"httpmethod-array": httpMethodArray,
"httpcode": httpCode,
"httpcode-array": httpCodeArray,
"timerfc3339": timerfc3339,
"duration": duration,
"ipcidr": ipcidr,
"ipcidr-array": ipcidrArray,
"hostport": hostport,
"regexp": _regexp,
"base64": _base64,
"url": _url,
}

urlCharsRegexp = regexp.MustCompile(`^[\p{L}0-9\-_\.~]{1,253}$`)
)

func getFormatFunc(format string) (FormatFunc, bool) {
switch format {
case "date-time", "email", "hostname", "ipv4", "ipv6", "uri":
return standardFormat, true

case "":
// NOTICE: Empty format does nothing like standard format.
return standardFormat, true
}

if fn, exists := formatsFuncs[format]; exists {
return fn, true
}

return nil, false
}

func standardFormat(v interface{}) error {
// errors will be reported by standard json schema validation
return nil
}

var ErrInvalidName = fmt.Errorf("invalid name format")

func urlName(v interface{}) error {
url, ok := v.(string)
if !ok {
return fmt.Errorf("%w: %v", ErrInvalidName, v)
}

if urlCharsRegexp.MatchString(url) {
return nil
}

return fmt.Errorf("%w: %s", ErrInvalidName, url)
}

var ErrInvalidHTTPMethod = fmt.Errorf("invalid http method")

func httpMethod(v interface{}) error {
method, ok := v.(string)
if !ok {
return fmt.Errorf("%w", ErrInvalidHTTPMethod)
}

switch method {
case http.MethodGet,
http.MethodHead,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
http.MethodConnect,
http.MethodOptions,
http.MethodTrace:
return nil
default:
return fmt.Errorf("%w", ErrInvalidHTTPMethod)
}
}

func httpMethodArray(v interface{}) error {
methods, ok := v.([]string)
if !ok {
return fmt.Errorf("%w", ErrInvalidHTTPMethod)
}

for _, method := range methods {
err := httpMethod(method)
if err != nil {
return err
}
}

return nil
}

var ErrInvalidHTTPCode = fmt.Errorf("invalid http code")

func httpCode(v interface{}) error {
code, ok := v.(int)
if !ok {
return fmt.Errorf("%w", ErrInvalidHTTPCode)
}

// Reference: https://tools.ietf.org/html/rfc7231#section-6
if code < 100 || code >= 600 {
return fmt.Errorf("%w", ErrInvalidHTTPCode)
}

return nil
}

func httpCodeArray(v interface{}) error {
codeArray, ok := v.([]int)
if !ok {
return fmt.Errorf("%w", ErrInvalidHTTPCode)
}

for _, method := range codeArray {
if err := httpCode(method); err != nil {
return err
}
}

return nil
}

// ErrInvalidRFC3339Time is returned when the time is not in RFC3339 format
var ErrInvalidRFC3339Time = fmt.Errorf("invalid RFC3339 time")

func timerfc3339(v interface{}) error {
s, ok := v.(string)
if !ok {
return ErrInvalidRFC3339Time
}

if _, err := time.Parse(time.RFC3339, s); err != nil {
return fmt.Errorf("%w: %v", ErrInvalidRFC3339Time, err)
}

return nil
}

// ErrInvalidDuration is returned when the duration is not in valid format
var ErrInvalidDuration = fmt.Errorf("invalid duration")

func duration(v interface{}) error {
s, ok := v.(string)
if !ok {
return ErrInvalidDuration
}

if _, err := time.ParseDuration(s); err != nil {
return fmt.Errorf("%w: %v", ErrInvalidDuration, err)
}

return nil
}

// ErrInvalidIPCIDR is returned when the ip or cidr is not in valid format
var ErrInvalidIPCIDR = fmt.Errorf("invalid ip or cidr")

func ipcidr(v interface{}) error {
s, ok := v.(string)
if !ok {
return ErrInvalidIPCIDR
}

ip := net.ParseIP(s)
if ip != nil {
return nil
}

if _, _, err := net.ParseCIDR(s); err != nil {
return fmt.Errorf("%w: %w", ErrInvalidIPCIDR, err)
}

return nil
}

func ipcidrArray(v interface{}) error {
cidrs, ok := v.([]string)
if !ok {
return ErrInvalidIPCIDR
}

for _, ic := range cidrs {
if err := ipcidr(ic); err != nil {
return err
}
}

return nil
}

var ErrInvalidHostport = fmt.Errorf("invalid hostport")

func hostport(v interface{}) error {
s, ok := v.(string)
if !ok {
return ErrInvalidHostport
}

if _, _, err := net.SplitHostPort(s); err != nil {
return fmt.Errorf("%w: %v", ErrInvalidHostport, err)
}

return nil
}

var ErrInvalidRegexp = fmt.Errorf("invalid regular expression")

func _regexp(v interface{}) error {
s, ok := v.(string)
if !ok {
return ErrInvalidRegexp
}

if _, err := regexp.Compile(s); err != nil {
return fmt.Errorf("%w: %v", ErrInvalidRegexp, err)
}

return nil
}

var ErrInvalidBase64 = fmt.Errorf("invalid base64")

func _base64(v interface{}) error {
s, ok := v.(string)
if !ok {
return ErrInvalidBase64
}

if _, err := base64.StdEncoding.DecodeString(s); err != nil {
return fmt.Errorf("%w: %v", ErrInvalidBase64, err)
}

return nil
}

var ErrInvalidURL = fmt.Errorf("invalid url")

func _url(v interface{}) error {
s, ok := v.(string)
if !ok {
return ErrInvalidURL
}

url, err := url.Parse(s)
if err != nil {
return fmt.Errorf("%w: %v", ErrInvalidURL, err)
}

if url.Host == "" {
return fmt.Errorf("%w: host is empty", ErrInvalidURL)
}

if url.Scheme == "" {
return fmt.Errorf("%w: scheme is empty", ErrInvalidURL)
}

return nil
}
Loading

0 comments on commit 1edf628

Please sign in to comment.