diff --git a/.version b/.version new file mode 100644 index 0000000..a1c2c6a --- /dev/null +++ b/.version @@ -0,0 +1 @@ +v0.1.1 \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3fd9306 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +vet: + go vet ./... + +run-example: + go run internal/examples/*.go + +goimports: + goimports -w -local github.com/pubgo/opendoc . diff --git a/docs/docs.go b/docs/_docs.go similarity index 79% rename from docs/docs.go rename to docs/_docs.go index 3f6405a..855beb7 100644 --- a/docs/docs.go +++ b/docs/_docs.go @@ -1,4 +1,4 @@ -package templates +package docs // https://spec.openapis.org/oas/latest.html // https://swagger.io/specification/ @@ -13,3 +13,6 @@ package templates // https://github.com/swaggest/jsonschema-go // https://github.com/swaggest/openapi-go // https://github.com/santhosh-tekuri/jsonschema + +// https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/ +// https://rapidocweb.com/api.html#att-general diff --git a/go.mod b/go.mod index 7a8e22b..36beb49 100644 --- a/go.mod +++ b/go.mod @@ -1,37 +1,26 @@ module github.com/pubgo/opendoc -go 1.20 +go 1.23 require ( github.com/fatih/structtag v1.2.0 - github.com/getkin/kin-openapi v0.124.0 - github.com/goccy/go-json v0.10.2 + github.com/getkin/kin-openapi v0.131.0 github.com/invopop/yaml v0.3.1 - github.com/pubgo/funk v0.5.41 + github.com/samber/lo v1.52.0 github.com/stretchr/testify v1.9.0 ) require ( - github.com/alecthomas/repr v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-openapi/jsonpointer v0.20.2 // indirect - github.com/go-openapi/swag v0.22.8 // indirect - github.com/golang/protobuf v1.5.4 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/k0kubun/pp/v3 v3.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/phuslu/goid v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rs/zerolog v1.29.0 // indirect - golang.org/x/exp v0.0.0-20221114191408-850992195362 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c // indirect - google.golang.org/grpc v1.51.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/text v0.22.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3eb1f37..0112da4 100644 --- a/go.sum +++ b/go.sum @@ -1,76 +1,47 @@ -github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= -github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M= -github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= -github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= -github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= -github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw= -github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= +github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE= +github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs= -github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/phuslu/goid v1.0.0 h1:Cgcvd/R54UO1fCtyt+iKXAi+yZQ/KWlAm6MmZNizCLM= -github.com/phuslu/goid v1.0.0/go.mod h1:txc2fUIdrdnn+v9Vq+QpiPQ3dnrXEchjoVDgic+r+L0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pubgo/funk v0.5.41 h1:vcDD6SWsz6g9CoD/whMIDgcfnGBfvCoZa6OTJ9iB9Tk= -github.com/pubgo/funk v0.5.41/go.mod h1:gKCw72+MK7xPiUGY1Z/bdGJMrSfVi87r0x/7d1GtKU4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= -github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= -github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= -golang.org/x/exp v0.0.0-20221114191408-850992195362 h1:NoHlPRbyl1VFI6FjwHtPQCN7wAMXI6cKcqrmXhOOfBQ= -golang.org/x/exp v0.0.0-20221114191408-850992195362/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c h1:QgY/XxIAIeccR+Ca/rDdKubLIU9rcJ3xfy1DC/Wd2Oo= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= -google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/examples/main.go b/internal/examples/main.go index 1f04162..63e5a69 100644 --- a/internal/examples/main.go +++ b/internal/examples/main.go @@ -3,11 +3,13 @@ package main import ( "fmt" "net/http" + "os" + + "github.com/samber/lo" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/recovery" "github.com/pubgo/opendoc/opendoc" "github.com/pubgo/opendoc/security" + "github.com/pubgo/opendoc/templates" ) type TestQueryReqAAA struct { @@ -19,10 +21,8 @@ type TestQueryReqAAA struct { } func main() { - defer recovery.Exit() - doc := opendoc.New(func(swag *opendoc.Swagger) { - swag.Config.Title = "this service web title " + swag.Title = "this service web title " swag.Description = "this is description" swag.License = &opendoc.License{ Name: "Apache License 2.0", @@ -65,11 +65,12 @@ func main() { }) }) - // data := assert.Must1(doc.MarshalYAML()) - // assert.Exit(os.WriteFile("openapi.yaml", data, 0644)) + data := lo.Must1(doc.MarshalYAML()) + lo.Must0(os.WriteFile("internal/examples/openapi.yaml", data, 0644)) app := http.NewServeMux() - doc.InitRouter(app) - fmt.Println("http://localhost:8080/debug/apidocs") - assert.Exit(http.ListenAndServe("localhost:8080", app)) + templates.InitRouter(app, doc, templates.DefaultCfg()) + + fmt.Println("http://localhost:8082/debug/apidocs") + lo.Must0(http.ListenAndServe("localhost:8082", app)) } diff --git a/internal/examples/openapi.yaml b/internal/examples/openapi.yaml index 75aada2..c3babfe 100644 --- a/internal/examples/openapi.yaml +++ b/internal/examples/openapi.yaml @@ -98,8 +98,8 @@ info: name: Apache License 2.0 url: https://github.com/pubgo/opendoc/blob/master/LICENSE termsOfService: https://github.com/pubgo - title: 'this service web title ' - version: v0.0.1-dev-99 + title: "" + version: "" openapi: 3.0.0 paths: /api/v1/articles: diff --git a/opendoc/operation.go b/opendoc/operation.go index 2cba5e9..8cf164c 100644 --- a/opendoc/operation.go +++ b/opendoc/operation.go @@ -1,12 +1,13 @@ package opendoc import ( + "log" "net/http" "path/filepath" "strings" "github.com/getkin/kin-openapi/openapi3" - "github.com/pubgo/funk/assert" + "github.com/samber/lo" "github.com/pubgo/opendoc/security" ) @@ -34,6 +35,11 @@ func (op *Operation) AddSecurity(security ...security.Security) *Operation { return op } +func (op *Operation) AddTags(tags ...string) *Operation { + op.tags = append(op.tags, tags...) + return op +} + func (op *Operation) SetExclude(exclude bool) *Operation { op.exclude = exclude return op @@ -73,7 +79,9 @@ func (op *Operation) SetSummary(summary string) *Operation { } func (op *Operation) SetPath(path string) *Operation { - assert.If(path == "", "path should not be null") + if path == "" { + log.Panic("path should not be null") + } path = strings.TrimSpace(path) path = strings.Trim(path, "/") @@ -82,7 +90,10 @@ func (op *Operation) SetPath(path string) *Operation { } func (op *Operation) SetOperation(operationID string) *Operation { - assert.If(operationID == "", "operationID should not be nil") + if operationID == "" { + log.Panic("operationID should not be nil") + } + op.operationID = operationID return op } @@ -110,7 +121,7 @@ func (op *Operation) Openapi(item *openapi3.PathItem) { } operation := &openapi3.Operation{ - Tags: op.tags, + Tags: lo.Uniq(op.tags), OperationID: op.operationID, Summary: op.summary, Description: op.description, diff --git a/opendoc/service.go b/opendoc/service.go index 35bd71d..50c70fc 100644 --- a/opendoc/service.go +++ b/opendoc/service.go @@ -28,6 +28,11 @@ func (s *Service) SetName(name string) *Service { return s } +func (s *Service) AddTags(tags ...string) *Service { + s.tags = append(s.tags, tags...) + return s +} + func (s *Service) AddContentType(contentType ...string) *Service { s.contentType = append(s.contentType, contentType...) return s diff --git a/opendoc/swagger.go b/opendoc/swagger.go index 2fc71cb..5ec9278 100644 --- a/opendoc/swagger.go +++ b/opendoc/swagger.go @@ -1,22 +1,16 @@ package opendoc import ( - "fmt" - "net/http" + "log" "strings" "github.com/getkin/kin-openapi/openapi3" "github.com/invopop/yaml" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/version" - - "github.com/pubgo/opendoc/templates" ) type Swagger struct { - rootPath string - - Config *Config + rootPath string + Title string Description string Version string TermsOfService string @@ -27,7 +21,10 @@ type Swagger struct { } func (s *Swagger) SetRootPath(path string) { - assert.If(path == "", "path should not be null") + if path == "" { + log.Panic("path should not be null") + } + s.rootPath = "/" + strings.Trim(strings.TrimSpace(path), "/") } @@ -46,16 +43,11 @@ func (s *Swagger) WithService() *Service { } func (s *Swagger) buildSwagger() *openapi3.T { - if s.Config == nil { - s.Config = DefaultCfg() - } - t := &openapi3.T{ OpenAPI: "3.0.0", Servers: s.Servers, Components: &components, Info: &openapi3.Info{ - Title: s.Config.Title, Description: s.Description, TermsOfService: s.TermsOfService, Contact: s.Contact, @@ -79,20 +71,6 @@ func (s *Swagger) buildSwagger() *openapi3.T { return t } -func (s *Swagger) InitRouter(r *http.ServeMux) { - r.Handle(s.Config.OpenapiRouter, templates.SwaggerHandler(s.Config.Title, s.Config.OpenapiUrl)) - r.Handle(s.Config.OpenapiRedocRouter, templates.ReDocHandler(s.Config.Title, s.Config.OpenapiUrl)) - r.Handle(s.Config.OpenapiRApiDocRouter, templates.RApiDocHandler(s.Config.OpenapiUrl)) - r.Handle(s.Config.OpenapiUrl, s.OpenapiDataHandler()) -} - -func (s *Swagger) OpenapiDataHandler() http.HandlerFunc { - bytes := assert.Must1(s.MarshalYAML()) - return func(writer http.ResponseWriter, request *http.Request) { - assert.Must1(writer.Write(bytes)) - } -} - func (s *Swagger) MarshalJSON() ([]byte, error) { return s.buildSwagger().MarshalJSON() } @@ -107,15 +85,9 @@ func (s *Swagger) MarshalYAML() ([]byte, error) { } func New(handles ...func(swag *Swagger)) *Swagger { - swagger := &Swagger{ - Config: DefaultCfg(), - Description: fmt.Sprintf("project:%s version:%s commit:%s", version.Project(), version.Version(), version.CommitID()), - Version: version.Version(), - } - + swagger := &Swagger{} for i := range handles { handles[i](swagger) } - return swagger } diff --git a/opendoc/swagger_test.go b/opendoc/swagger_test.go index ef07567..df21e5a 100644 --- a/opendoc/swagger_test.go +++ b/opendoc/swagger_test.go @@ -1,10 +1,10 @@ package opendoc import ( + "encoding/json" "testing" "github.com/getkin/kin-openapi/openapi3" - "github.com/goccy/go-json" "github.com/stretchr/testify/assert" ) diff --git a/opendoc/util.go b/opendoc/util.go index 9889945..b3c92e5 100644 --- a/opendoc/util.go +++ b/opendoc/util.go @@ -1,7 +1,10 @@ package opendoc import ( + "encoding/json" "fmt" + "log" + "log/slog" "mime/multipart" "net" "net/http" @@ -12,9 +15,8 @@ import ( "github.com/fatih/structtag" "github.com/getkin/kin-openapi/openapi3" - "github.com/goccy/go-json" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/log" + "github.com/samber/lo" + "github.com/pubgo/opendoc/security" ) @@ -37,7 +39,9 @@ func checkModelType(model interface{}) { t = t.Elem() } - assert.If(t.Kind() != reflect.Struct, "The native type of model should be struct") + if t.Kind() != reflect.Struct { + panic("The native type of model should be struct") + } } func getSchemaName(val interface{}) string { @@ -76,8 +80,8 @@ func GetCanonicalTypeName(val interface{}) string { func getSecurityRequirements(securities []security.Security) *openapi3.SecurityRequirements { securityRequirements := openapi3.NewSecurityRequirements() for _, s := range securities { - securityRequirements.With(openapi3.NewSecurityRequirement().Authenticate(s.Provider())) - components.SecuritySchemes[s.Provider()] = &openapi3.SecuritySchemeRef{Value: s.Scheme()} + securityRequirements.With(openapi3.NewSecurityRequirement().Authenticate(string(s.Provider()))) + components.SecuritySchemes[string(s.Provider())] = &openapi3.SecuritySchemeRef{Value: s.Scheme()} } return securityRequirements } @@ -94,7 +98,9 @@ func genSchema(val interface{}) (ref string, schema *openapi3.Schema) { model = model.Elem() } - assert.If(model.Kind() == reflect.Interface, "type:%s kind should not be interface", model) + if model.Kind() == reflect.Interface { + panic(fmt.Sprintf("type:%s kind should not be interface", model)) + } switch model { case reflect.TypeOf([]byte{}): @@ -175,7 +181,7 @@ func genSchema(val interface{}) (ref string, schema *openapi3.Schema) { for i := 0; i < model.NumField(); i++ { field := model.Field(i) - tags := assert.Must1(structtag.Parse(string(field.Tag))) + tags := lo.Must1(structtag.Parse(string(field.Tag))) if isParameter(tags) { continue } @@ -209,7 +215,7 @@ func genSchema(val interface{}) (ref string, schema *openapi3.Schema) { getTag(tags, defaultName, func(tag *structtag.Tag) { fieldSchema.Default = tag.Name }) getTag(tags, example, func(tag *structtag.Tag) { if err := json.Unmarshal([]byte(tag.Value()), &fieldSchema.Example); err != nil { - log.Err(err).Str("tag-value", tag.Value()).Msg("failed to unmarshal example") + slog.Error("failed to unmarshal example", "tag-value", tag.Value()) } }) getTag(tags, validate, func(tag *structtag.Tag) { @@ -224,6 +230,8 @@ func genSchema(val interface{}) (ref string, schema *openapi3.Schema) { schema.Properties[tag.Name] = openapi3.NewSchemaRef("", fieldSchema) } return getComponentName(schemaName), schema + default: + panic("unhandled default case") } return "", schema @@ -273,7 +281,9 @@ func isParameter(val *structtag.Tags) bool { } func genParameters(val interface{}) openapi3.Parameters { - assert.If(val == nil, "val is nil") + if val == nil { + log.Panicln("val is nil") + } var model reflect.Type if _t, ok := val.(reflect.Type); ok { @@ -286,12 +296,14 @@ func genParameters(val interface{}) openapi3.Parameters { model = model.Elem() } - assert.If(model.Kind() != reflect.Struct, "type:%s kind should be struct", model) + if model.Kind() != reflect.Struct { + log.Panicf("type:%s kind should be struct", model) + } parameters := openapi3.NewParameters() for i := 0; i < model.NumField(); i++ { field := model.Field(i) - tags := assert.Must1(structtag.Parse(string(field.Tag))) + tags := lo.Must1(structtag.Parse(string(field.Tag))) if !isParameter(tags) { continue } diff --git a/security/aaa.go b/security/aaa.go index c9e24a4..6c4bb4b 100644 --- a/security/aaa.go +++ b/security/aaa.go @@ -5,6 +5,16 @@ import ( ) type Security interface { - Provider() string + Provider() AuthProviderType Scheme() *openapi3.SecurityScheme } + +type AuthProviderType string + +const ( + AuthProviderTypeBasic AuthProviderType = "Basic" + AuthProviderTypeApiKey AuthProviderType = "ApiKey" + AuthProviderTypeBearer AuthProviderType = "Bearer" + AuthProviderTypeOAuth2 AuthProviderType = "OAuth2" + AuthProviderTypeOIDC AuthProviderType = "OIDC" +) diff --git a/security/apikey.go b/security/apikey.go index 09a2cdd..730bb61 100644 --- a/security/apikey.go +++ b/security/apikey.go @@ -5,12 +5,11 @@ import ( ) type ApiKey struct { + // Name of the header to be used. Name string } -func (k ApiKey) Provider() string { - return "ApiKey" -} +func (k ApiKey) Provider() AuthProviderType { return AuthProviderTypeApiKey } func (k ApiKey) Scheme() *openapi3.SecurityScheme { return &openapi3.SecurityScheme{ diff --git a/security/basic.go b/security/basic.go index 7501066..02f1f95 100644 --- a/security/basic.go +++ b/security/basic.go @@ -7,13 +7,14 @@ import ( type Basic struct{} type User struct { + // Username basic auth username Username string + + // Password basic auth password Password string } -func (b Basic) Provider() string { - return "Basic" -} +func (b Basic) Provider() AuthProviderType { return AuthProviderTypeBasic } func (b Basic) Scheme() *openapi3.SecurityScheme { return &openapi3.SecurityScheme{ diff --git a/security/bearer.go b/security/bearer.go index ae8f9e2..803715e 100644 --- a/security/bearer.go +++ b/security/bearer.go @@ -6,9 +6,7 @@ import ( type Bearer struct{} -func (b Bearer) Provider() string { - return "Bearer" -} +func (b Bearer) Provider() AuthProviderType { return AuthProviderTypeBearer } func (b Bearer) Scheme() *openapi3.SecurityScheme { return &openapi3.SecurityScheme{ diff --git a/security/oauth2.go b/security/oauth2.go index 8eb0b68..8ee214c 100644 --- a/security/oauth2.go +++ b/security/oauth2.go @@ -11,9 +11,7 @@ type OAuth2 struct { Scopes map[string]string } -func (i OAuth2) Provider() string { - return "OAuth2" -} +func (i OAuth2) Provider() AuthProviderType { return AuthProviderTypeOAuth2 } func (i OAuth2) Scheme() *openapi3.SecurityScheme { return &openapi3.SecurityScheme{ diff --git a/security/openid.go b/security/openid.go index d8cf232..4b8f750 100644 --- a/security/openid.go +++ b/security/openid.go @@ -8,9 +8,7 @@ type OpenID struct { ConnectUrl string } -func (i OpenID) Provider() string { - return "OpenIdConnect" -} +func (i OpenID) Provider() AuthProviderType { return AuthProviderTypeOIDC } func (i OpenID) Scheme() *openapi3.SecurityScheme { return &openapi3.SecurityScheme{ diff --git a/opendoc/config.go b/templates/config.go similarity index 76% rename from opendoc/config.go rename to templates/config.go index 2f4a004..c7145f1 100644 --- a/opendoc/config.go +++ b/templates/config.go @@ -1,12 +1,9 @@ -package opendoc - -import "github.com/pubgo/funk/version" +package templates // https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/ // https://rapidocweb.com/api.html#att-general type Config struct { - Title string `yaml:"title"` OpenapiRouter string `yaml:"path"` OpenapiRedocRouter string `yaml:"redoc-path"` OpenapiRApiDocRouter string `yaml:"rapidoc-path"` @@ -14,9 +11,8 @@ type Config struct { OpenapiOpt map[string]interface{} `yaml:"options"` } -func DefaultCfg() *Config { - return &Config{ - Title: version.Project() + " openapi docs", +func DefaultCfg() Config { + return Config{ OpenapiRouter: "/debug/docs", OpenapiRedocRouter: "/debug/redocs", OpenapiRApiDocRouter: "/debug/apidocs", diff --git a/templates/template.go b/templates/template.go index 444a97c..31ab5d3 100644 --- a/templates/template.go +++ b/templates/template.go @@ -5,7 +5,9 @@ import ( "html/template" "net/http" - "github.com/pubgo/funk/assert" + "github.com/samber/lo" + + "github.com/pubgo/opendoc/opendoc" ) //go:embed redoc.html @@ -18,15 +20,15 @@ var swaggerFile string var rApiDocFile string var ( - reDocTemplate = assert.Exit1(template.New("").Parse(reDocFile)) - swaggerTemplate = assert.Exit1(template.New("").Parse(swaggerFile)) - rApiDocFileTemplate = assert.Exit1(template.New("").Parse(rApiDocFile)) + reDocTemplate = lo.Must(template.New("").Parse(reDocFile)) + swaggerTemplate = lo.Must(template.New("").Parse(swaggerFile)) + rApiDocFileTemplate = lo.Must(template.New("").Parse(rApiDocFile)) ) func RApiDocHandler(url string) http.HandlerFunc { return func(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Content-Type", "text/html") - assert.Must(rApiDocFileTemplate.Execute(writer, map[string]string{ + lo.Must0(rApiDocFileTemplate.Execute(writer, map[string]string{ "openapi_url": url, "openapi_options": `{}`, })) @@ -36,7 +38,7 @@ func RApiDocHandler(url string) http.HandlerFunc { func ReDocHandler(title, url string) http.HandlerFunc { return func(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Content-Type", "text/html") - assert.Must(reDocTemplate.Execute(writer, map[string]string{ + lo.Must0(reDocTemplate.Execute(writer, map[string]string{ "title": title, "openapi_url": url, "openapi_options": `{}`, @@ -47,10 +49,25 @@ func ReDocHandler(title, url string) http.HandlerFunc { func SwaggerHandler(title, url string) http.HandlerFunc { return func(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Content-Type", "text/html") - assert.Must(swaggerTemplate.Execute(writer, map[string]string{ + lo.Must0(swaggerTemplate.Execute(writer, map[string]string{ "title": title, "openapi_url": url, "openapi_options": `{}`, })) } } + +func InitRouter(r *http.ServeMux, s *opendoc.Swagger, cfg Config) { + title := s.Title + r.Handle(cfg.OpenapiRouter, SwaggerHandler(title, cfg.OpenapiUrl)) + r.Handle(cfg.OpenapiRedocRouter, ReDocHandler(title, cfg.OpenapiUrl)) + r.Handle(cfg.OpenapiRApiDocRouter, RApiDocHandler(cfg.OpenapiUrl)) + r.Handle(cfg.OpenapiUrl, openapiDataHandler(s)) +} + +func openapiDataHandler(s *opendoc.Swagger) http.HandlerFunc { + bytes := lo.Must1(s.MarshalYAML()) + return func(writer http.ResponseWriter, request *http.Request) { + lo.Must1(writer.Write(bytes)) + } +}