Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial cut of a registry #137

Merged
merged 4 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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