Bump github.com/getkin/kin-openapi from 0.76.0 to 0.131.0

Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.76.0 to 0.131.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.76.0...v0.131.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-version: 0.131.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot] 2025-05-15 16:49:42 +00:00 committed by GitHub
parent 79b63a80c1
commit 92169241a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
140 changed files with 25239 additions and 3637 deletions

14
go.mod
View File

@ -1,6 +1,6 @@
module github.com/slimtoolkit/slim
go 1.21
go 1.22.5
require (
github.com/armon/go-radix v1.0.0
@ -15,7 +15,7 @@ require (
github.com/dustin/go-humanize v1.0.0
github.com/fatih/color v1.13.0
github.com/fsouza/go-dockerclient v1.10.0
github.com/getkin/kin-openapi v0.76.0
github.com/getkin/kin-openapi v0.131.0
github.com/ghodss/yaml v1.0.0
github.com/gocolly/colly/v2 v2.1.0
github.com/google/go-containerregistry v0.19.0
@ -59,9 +59,9 @@ require (
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.1 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
@ -87,10 +87,14 @@ require (
github.com/moby/sys/user v0.2.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
@ -147,7 +151,7 @@ require (
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/pkg/term v0.0.0-20200520122047-c3ffed290a03 // indirect
github.com/stretchr/testify v1.8.4
github.com/stretchr/testify v1.9.0
github.com/ulikunitz/xz v0.5.7 // indirect
k8s.io/klog/v2 v2.90.1 // indirect
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect

42
go.sum
View File

@ -124,8 +124,8 @@ github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBd
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsouza/go-dockerclient v1.10.0 h1:ppSBsbR60I1DFbV4Ag7LlHlHakHFRNLk9XakATW1yVQ=
github.com/fsouza/go-dockerclient v1.10.0/go.mod h1:+iNzAW78AzClIBTZ6WFjkaMvOgz68GyCJ236b1opLTs=
github.com/getkin/kin-openapi v0.76.0 h1:j77zg3Ec+k+r+GA3d8hBoXpAc6KX9TbBPrwQGBIy2sY=
github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE=
github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@ -139,18 +139,20 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
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/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8=
github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
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-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA=
@ -246,16 +248,14 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
@ -300,6 +300,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
@ -308,6 +310,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
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/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk=
github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo=
github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E=
@ -317,6 +323,8 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -341,8 +349,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
@ -376,8 +384,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@ -386,13 +395,15 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg=
github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4=
github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
@ -600,7 +611,6 @@ google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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=

View File

@ -1,2 +0,0 @@
// Package jsoninfo provides information and functions for marshalling/unmarshalling JSON.
package jsoninfo

View File

@ -1,121 +0,0 @@
package jsoninfo
import (
"reflect"
"strings"
"unicode"
"unicode/utf8"
)
// FieldInfo contains information about JSON serialization of a field.
type FieldInfo struct {
MultipleFields bool // Whether multiple Go fields share this JSON name
HasJSONTag bool
TypeIsMarshaller bool
TypeIsUnmarshaller bool
JSONOmitEmpty bool
JSONString bool
Index []int
Type reflect.Type
JSONName string
}
func AppendFields(fields []FieldInfo, parentIndex []int, t reflect.Type) []FieldInfo {
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
// For each field
numField := t.NumField()
iteration:
for i := 0; i < numField; i++ {
f := t.Field(i)
index := make([]int, 0, len(parentIndex)+1)
index = append(index, parentIndex...)
index = append(index, i)
// See whether this is an embedded field
if f.Anonymous {
if f.Tag.Get("json") == "-" {
continue
}
fields = AppendFields(fields, index, f.Type)
continue iteration
}
// Ignore certain types
switch f.Type.Kind() {
case reflect.Func, reflect.Chan:
continue iteration
}
// Is it a private (lowercase) field?
firstRune, _ := utf8.DecodeRuneInString(f.Name)
if unicode.IsLower(firstRune) {
continue iteration
}
// Declare a field
field := FieldInfo{
Index: index,
Type: f.Type,
JSONName: f.Name,
}
// Read "json" tag
jsonTag := f.Tag.Get("json")
// Read our custom "multijson" tag that
// allows multiple fields with the same name.
if v := f.Tag.Get("multijson"); v != "" {
field.MultipleFields = true
jsonTag = v
}
// Handle "-"
if jsonTag == "-" {
continue
}
// Parse the tag
if jsonTag != "" {
field.HasJSONTag = true
for i, part := range strings.Split(jsonTag, ",") {
if i == 0 {
if part != "" {
field.JSONName = part
}
} else {
switch part {
case "omitempty":
field.JSONOmitEmpty = true
case "string":
field.JSONString = true
}
}
}
}
_, field.TypeIsMarshaller = field.Type.MethodByName("MarshalJSON")
_, field.TypeIsUnmarshaller = field.Type.MethodByName("UnmarshalJSON")
// Field is done
fields = append(fields, field)
}
return fields
}
type sortableFieldInfos []FieldInfo
func (list sortableFieldInfos) Len() int {
return len(list)
}
func (list sortableFieldInfos) Less(i, j int) bool {
return list[i].JSONName < list[j].JSONName
}
func (list sortableFieldInfos) Swap(i, j int) {
a, b := list[i], list[j]
list[i], list[j] = b, a
}

View File

@ -1,162 +0,0 @@
package jsoninfo
import (
"encoding/json"
"fmt"
"reflect"
)
// MarshalStrictStruct function:
// * Marshals struct fields, ignoring MarshalJSON() and fields without 'json' tag.
// * Correctly handles StrictStruct semantics.
func MarshalStrictStruct(value StrictStruct) ([]byte, error) {
encoder := NewObjectEncoder()
if err := value.EncodeWith(encoder, value); err != nil {
return nil, err
}
return encoder.Bytes()
}
type ObjectEncoder struct {
result map[string]json.RawMessage
}
func NewObjectEncoder() *ObjectEncoder {
return &ObjectEncoder{
result: make(map[string]json.RawMessage, 8),
}
}
// Bytes returns the result of encoding.
func (encoder *ObjectEncoder) Bytes() ([]byte, error) {
return json.Marshal(encoder.result)
}
// EncodeExtension adds a key/value to the current JSON object.
func (encoder *ObjectEncoder) EncodeExtension(key string, value interface{}) error {
data, err := json.Marshal(value)
if err != nil {
return err
}
encoder.result[key] = data
return nil
}
// EncodeExtensionMap adds all properties to the result.
func (encoder *ObjectEncoder) EncodeExtensionMap(value map[string]json.RawMessage) error {
if value != nil {
result := encoder.result
for k, v := range value {
result[k] = v
}
}
return nil
}
func (encoder *ObjectEncoder) EncodeStructFieldsAndExtensions(value interface{}) error {
reflection := reflect.ValueOf(value)
// Follow "encoding/json" semantics
if reflection.Kind() != reflect.Ptr {
// Panic because this is a clear programming error
panic(fmt.Errorf("value %s is not a pointer", reflection.Type().String()))
}
if reflection.IsNil() {
// Panic because this is a clear programming error
panic(fmt.Errorf("value %s is nil", reflection.Type().String()))
}
// Take the element
reflection = reflection.Elem()
// Obtain typeInfo
typeInfo := GetTypeInfo(reflection.Type())
// Declare result
result := encoder.result
// Supported fields
iteration:
for _, field := range typeInfo.Fields {
// Fields without JSON tag are ignored
if !field.HasJSONTag {
continue
}
// Marshal
fieldValue := reflection.FieldByIndex(field.Index)
if v, ok := fieldValue.Interface().(json.Marshaler); ok {
if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
if field.JSONOmitEmpty {
continue iteration
}
result[field.JSONName] = []byte("null")
continue
}
fieldData, err := v.MarshalJSON()
if err != nil {
return err
}
result[field.JSONName] = fieldData
continue
}
switch fieldValue.Kind() {
case reflect.Ptr, reflect.Interface:
if fieldValue.IsNil() {
if field.JSONOmitEmpty {
continue iteration
}
result[field.JSONName] = []byte("null")
continue
}
case reflect.Struct:
case reflect.Map:
if field.JSONOmitEmpty && (fieldValue.IsNil() || fieldValue.Len() == 0) {
continue iteration
}
case reflect.Slice:
if field.JSONOmitEmpty && fieldValue.Len() == 0 {
continue iteration
}
case reflect.Bool:
x := fieldValue.Bool()
if field.JSONOmitEmpty && !x {
continue iteration
}
s := "false"
if x {
s = "true"
}
result[field.JSONName] = []byte(s)
continue iteration
case reflect.Int64, reflect.Int, reflect.Int32:
if field.JSONOmitEmpty && fieldValue.Int() == 0 {
continue iteration
}
case reflect.Uint64, reflect.Uint, reflect.Uint32:
if field.JSONOmitEmpty && fieldValue.Uint() == 0 {
continue iteration
}
case reflect.Float64:
if field.JSONOmitEmpty && fieldValue.Float() == 0.0 {
continue iteration
}
case reflect.String:
if field.JSONOmitEmpty && len(fieldValue.String()) == 0 {
continue iteration
}
default:
panic(fmt.Errorf("field %q has unsupported type %s", field.JSONName, field.Type.String()))
}
// No special treament is needed
// Use plain old "encoding/json".Marshal
fieldData, err := json.Marshal(fieldValue.Addr().Interface())
if err != nil {
return err
}
result[field.JSONName] = fieldData
}
return nil
}

View File

@ -1,30 +0,0 @@
package jsoninfo
import (
"encoding/json"
)
func MarshalRef(value string, otherwise interface{}) ([]byte, error) {
if len(value) > 0 {
return json.Marshal(&refProps{
Ref: value,
})
}
return json.Marshal(otherwise)
}
func UnmarshalRef(data []byte, destRef *string, destOtherwise interface{}) error {
refProps := &refProps{}
if err := json.Unmarshal(data, refProps); err == nil {
ref := refProps.Ref
if len(ref) > 0 {
*destRef = ref
return nil
}
}
return json.Unmarshal(data, destOtherwise)
}
type refProps struct {
Ref string `json:"$ref,omitempty"`
}

View File

@ -1,6 +0,0 @@
package jsoninfo
type StrictStruct interface {
EncodeWith(encoder *ObjectEncoder, value interface{}) error
DecodeWith(decoder *ObjectDecoder, value interface{}) error
}

View File

@ -1,68 +0,0 @@
package jsoninfo
import (
"reflect"
"sort"
"sync"
)
var (
typeInfos = map[reflect.Type]*TypeInfo{}
typeInfosMutex sync.RWMutex
)
// TypeInfo contains information about JSON serialization of a type
type TypeInfo struct {
Type reflect.Type
Fields []FieldInfo
}
func GetTypeInfoForValue(value interface{}) *TypeInfo {
return GetTypeInfo(reflect.TypeOf(value))
}
// GetTypeInfo returns TypeInfo for the given type.
func GetTypeInfo(t reflect.Type) *TypeInfo {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
typeInfosMutex.RLock()
typeInfo, exists := typeInfos[t]
typeInfosMutex.RUnlock()
if exists {
return typeInfo
}
if t.Kind() != reflect.Struct {
typeInfo = &TypeInfo{
Type: t,
}
} else {
// Allocate
typeInfo = &TypeInfo{
Type: t,
Fields: make([]FieldInfo, 0, 16),
}
// Add fields
typeInfo.Fields = AppendFields(nil, nil, t)
// Sort fields
sort.Sort(sortableFieldInfos(typeInfo.Fields))
}
// Publish
typeInfosMutex.Lock()
typeInfos[t] = typeInfo
typeInfosMutex.Unlock()
return typeInfo
}
// FieldNames returns all field names
func (typeInfo *TypeInfo) FieldNames() []string {
fields := typeInfo.Fields
names := make([]string, 0, len(fields))
for _, field := range fields {
names = append(names, field.JSONName)
}
return names
}

View File

@ -1,121 +0,0 @@
package jsoninfo
import (
"encoding/json"
"fmt"
"reflect"
)
// UnmarshalStrictStruct function:
// * Unmarshals struct fields, ignoring UnmarshalJSON(...) and fields without 'json' tag.
// * Correctly handles StrictStruct
func UnmarshalStrictStruct(data []byte, value StrictStruct) error {
decoder, err := NewObjectDecoder(data)
if err != nil {
return err
}
return value.DecodeWith(decoder, value)
}
type ObjectDecoder struct {
Data []byte
remainingFields map[string]json.RawMessage
}
func NewObjectDecoder(data []byte) (*ObjectDecoder, error) {
var remainingFields map[string]json.RawMessage
if err := json.Unmarshal(data, &remainingFields); err != nil {
return nil, fmt.Errorf("failed to unmarshal extension properties: %v (%s)", err, data)
}
return &ObjectDecoder{
Data: data,
remainingFields: remainingFields,
}, nil
}
// DecodeExtensionMap returns all properties that were not decoded previously.
func (decoder *ObjectDecoder) DecodeExtensionMap() map[string]json.RawMessage {
return decoder.remainingFields
}
func (decoder *ObjectDecoder) DecodeStructFieldsAndExtensions(value interface{}) error {
reflection := reflect.ValueOf(value)
if reflection.Kind() != reflect.Ptr {
panic(fmt.Errorf("value %T is not a pointer", value))
}
if reflection.IsNil() {
panic(fmt.Errorf("value %T is nil", value))
}
reflection = reflection.Elem()
for (reflection.Kind() == reflect.Interface || reflection.Kind() == reflect.Ptr) && !reflection.IsNil() {
reflection = reflection.Elem()
}
reflectionType := reflection.Type()
if reflectionType.Kind() != reflect.Struct {
panic(fmt.Errorf("value %T is not a struct", value))
}
typeInfo := GetTypeInfo(reflectionType)
// Supported fields
fields := typeInfo.Fields
remainingFields := decoder.remainingFields
for fieldIndex, field := range fields {
// Fields without JSON tag are ignored
if !field.HasJSONTag {
continue
}
// Get data
fieldData, exists := remainingFields[field.JSONName]
if !exists {
continue
}
// Unmarshal
if field.TypeIsUnmarshaller {
fieldType := field.Type
isPtr := false
if fieldType.Kind() == reflect.Ptr {
fieldType = fieldType.Elem()
isPtr = true
}
fieldValue := reflect.New(fieldType)
if err := fieldValue.Interface().(json.Unmarshaler).UnmarshalJSON(fieldData); err != nil {
if field.MultipleFields {
i := fieldIndex + 1
if i < len(fields) && fields[i].JSONName == field.JSONName {
continue
}
}
return fmt.Errorf("failed to unmarshal property %q (%s): %v",
field.JSONName, fieldValue.Type().String(), err)
}
if !isPtr {
fieldValue = fieldValue.Elem()
}
reflection.FieldByIndex(field.Index).Set(fieldValue)
// Remove the field from remaining fields
delete(remainingFields, field.JSONName)
} else {
fieldPtr := reflection.FieldByIndex(field.Index)
if fieldPtr.Kind() != reflect.Ptr || fieldPtr.IsNil() {
fieldPtr = fieldPtr.Addr()
}
if err := json.Unmarshal(fieldData, fieldPtr.Interface()); err != nil {
if field.MultipleFields {
i := fieldIndex + 1
if i < len(fields) && fields[i].JSONName == field.JSONName {
continue
}
}
return fmt.Errorf("failed to unmarshal property %q (%s): %v",
field.JSONName, fieldPtr.Type().String(), err)
}
// Remove the field from remaining fields
delete(remainingFields, field.JSONName)
}
}
return nil
}

View File

@ -1,42 +0,0 @@
package jsoninfo
import (
"encoding/json"
"fmt"
"sort"
)
// UnsupportedPropertiesError is a helper for extensions that want to refuse
// unsupported JSON object properties.
//
// It produces a helpful error message.
type UnsupportedPropertiesError struct {
Value interface{}
UnsupportedProperties map[string]json.RawMessage
}
func NewUnsupportedPropertiesError(v interface{}, m map[string]json.RawMessage) error {
return &UnsupportedPropertiesError{
Value: v,
UnsupportedProperties: m,
}
}
func (err *UnsupportedPropertiesError) Error() string {
m := err.UnsupportedProperties
typeInfo := GetTypeInfoForValue(err.Value)
if m == nil || typeInfo == nil {
return fmt.Sprintf("invalid %T", *err)
}
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
supported := typeInfo.FieldNames()
if len(supported) == 0 {
return fmt.Sprintf("type \"%T\" doesn't take any properties. Unsupported properties: %+v",
err.Value, keys)
}
return fmt.Sprintf("unsupported properties: %+v (supported properties are: %+v)", keys, supported)
}

View File

@ -0,0 +1,15 @@
package openapi2
type Header struct {
Parameter
}
// MarshalJSON returns the JSON encoding of Header.
func (header Header) MarshalJSON() ([]byte, error) {
return header.Parameter.MarshalJSON()
}
// UnmarshalJSON sets Header to a copy of data.
func (header *Header) UnmarshalJSON(data []byte) error {
return header.Parameter.UnmarshalJSON(data)
}

View File

@ -0,0 +1,15 @@
package openapi2
import (
"net/url"
)
// copyURI makes a copy of the pointer.
func copyURI(u *url.URL) *url.URL {
if u == nil {
return nil
}
c := *u // shallow-copy
return &c
}

34
vendor/github.com/getkin/kin-openapi/openapi2/marsh.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
package openapi2
import (
"encoding/json"
"fmt"
"strings"
"github.com/oasdiff/yaml"
)
func unmarshalError(jsonUnmarshalErr error) error {
if before, after, found := strings.Cut(jsonUnmarshalErr.Error(), "Bis"); found && before != "" && after != "" {
before = strings.ReplaceAll(before, " Go struct ", " ")
return fmt.Errorf("%s%s", before, strings.ReplaceAll(after, "Bis", ""))
}
return jsonUnmarshalErr
}
func unmarshal(data []byte, v any) error {
var jsonErr, yamlErr error
// See https://github.com/getkin/kin-openapi/issues/680
if jsonErr = json.Unmarshal(data, v); jsonErr == nil {
return nil
}
// UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys
if yamlErr = yaml.Unmarshal(data, v); yamlErr == nil {
return nil
}
// If both unmarshaling attempts fail, return a new error that includes both errors
return fmt.Errorf("failed to unmarshal data: json error: %v, yaml error: %v", jsonErr, yamlErr)
}

View File

@ -1,270 +1,120 @@
package openapi2
import (
"fmt"
"net/http"
"sort"
"encoding/json"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/getkin/kin-openapi/openapi3"
)
// T is the root of an OpenAPI v2 document
type T struct {
openapi3.ExtensionProps
Swagger string `json:"swagger" yaml:"swagger"`
Info openapi3.Info `json:"info" yaml:"info"`
Extensions map[string]any `json:"-" yaml:"-"`
Swagger string `json:"swagger" yaml:"swagger"` // required
Info openapi3.Info `json:"info" yaml:"info"` // required
ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
Host string `json:"host,omitempty" yaml:"host,omitempty"`
BasePath string `json:"basePath,omitempty" yaml:"basePath,omitempty"`
Paths map[string]*PathItem `json:"paths,omitempty" yaml:"paths,omitempty"`
Definitions map[string]*openapi3.SchemaRef `json:"definitions,omitempty,noref" yaml:"definitions,omitempty,noref"`
Parameters map[string]*Parameter `json:"parameters,omitempty,noref" yaml:"parameters,omitempty,noref"`
Responses map[string]*Response `json:"responses,omitempty,noref" yaml:"responses,omitempty,noref"`
Definitions map[string]*SchemaRef `json:"definitions,omitempty" yaml:"definitions,omitempty"`
Parameters map[string]*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"`
SecurityDefinitions map[string]*SecurityScheme `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"`
Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
Tags openapi3.Tags `json:"tags,omitempty" yaml:"tags,omitempty"`
}
func (doc *T) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(doc)
// MarshalJSON returns the JSON encoding of T.
func (doc T) MarshalJSON() ([]byte, error) {
m := make(map[string]any, 15+len(doc.Extensions))
for k, v := range doc.Extensions {
m[k] = v
}
m["swagger"] = doc.Swagger
m["info"] = doc.Info
if x := doc.ExternalDocs; x != nil {
m["externalDocs"] = x
}
if x := doc.Schemes; len(x) != 0 {
m["schemes"] = x
}
if x := doc.Consumes; len(x) != 0 {
m["consumes"] = x
}
if x := doc.Produces; len(x) != 0 {
m["produces"] = x
}
if x := doc.Host; x != "" {
m["host"] = x
}
if x := doc.BasePath; x != "" {
m["basePath"] = x
}
if x := doc.Paths; len(x) != 0 {
m["paths"] = x
}
if x := doc.Definitions; len(x) != 0 {
m["definitions"] = x
}
if x := doc.Parameters; len(x) != 0 {
m["parameters"] = x
}
if x := doc.Responses; len(x) != 0 {
m["responses"] = x
}
if x := doc.SecurityDefinitions; len(x) != 0 {
m["securityDefinitions"] = x
}
if x := doc.Security; len(x) != 0 {
m["security"] = x
}
if x := doc.Tags; len(x) != 0 {
m["tags"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets T to a copy of data.
func (doc *T) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, doc)
type TBis T
var x TBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "swagger")
delete(x.Extensions, "info")
delete(x.Extensions, "externalDocs")
delete(x.Extensions, "schemes")
delete(x.Extensions, "consumes")
delete(x.Extensions, "produces")
delete(x.Extensions, "host")
delete(x.Extensions, "basePath")
delete(x.Extensions, "paths")
delete(x.Extensions, "definitions")
delete(x.Extensions, "parameters")
delete(x.Extensions, "responses")
delete(x.Extensions, "securityDefinitions")
delete(x.Extensions, "security")
delete(x.Extensions, "tags")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*doc = T(x)
return nil
}
func (doc *T) AddOperation(path string, method string, operation *Operation) {
paths := doc.Paths
if paths == nil {
paths = make(map[string]*PathItem, 8)
doc.Paths = paths
if doc.Paths == nil {
doc.Paths = make(map[string]*PathItem)
}
pathItem := paths[path]
pathItem := doc.Paths[path]
if pathItem == nil {
pathItem = &PathItem{}
paths[path] = pathItem
doc.Paths[path] = pathItem
}
pathItem.SetOperation(method, operation)
}
type PathItem struct {
openapi3.ExtensionProps
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"`
Get *Operation `json:"get,omitempty" yaml:"get,omitempty"`
Head *Operation `json:"head,omitempty" yaml:"head,omitempty"`
Options *Operation `json:"options,omitempty" yaml:"options,omitempty"`
Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"`
Post *Operation `json:"post,omitempty" yaml:"post,omitempty"`
Put *Operation `json:"put,omitempty" yaml:"put,omitempty"`
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
}
func (pathItem *PathItem) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(pathItem)
}
func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, pathItem)
}
func (pathItem *PathItem) Operations() map[string]*Operation {
operations := make(map[string]*Operation, 8)
if v := pathItem.Delete; v != nil {
operations[http.MethodDelete] = v
}
if v := pathItem.Get; v != nil {
operations[http.MethodGet] = v
}
if v := pathItem.Head; v != nil {
operations[http.MethodHead] = v
}
if v := pathItem.Options; v != nil {
operations[http.MethodOptions] = v
}
if v := pathItem.Patch; v != nil {
operations[http.MethodPatch] = v
}
if v := pathItem.Post; v != nil {
operations[http.MethodPost] = v
}
if v := pathItem.Put; v != nil {
operations[http.MethodPut] = v
}
return operations
}
func (pathItem *PathItem) GetOperation(method string) *Operation {
switch method {
case http.MethodDelete:
return pathItem.Delete
case http.MethodGet:
return pathItem.Get
case http.MethodHead:
return pathItem.Head
case http.MethodOptions:
return pathItem.Options
case http.MethodPatch:
return pathItem.Patch
case http.MethodPost:
return pathItem.Post
case http.MethodPut:
return pathItem.Put
default:
panic(fmt.Errorf("unsupported HTTP method %q", method))
}
}
func (pathItem *PathItem) SetOperation(method string, operation *Operation) {
switch method {
case http.MethodDelete:
pathItem.Delete = operation
case http.MethodGet:
pathItem.Get = operation
case http.MethodHead:
pathItem.Head = operation
case http.MethodOptions:
pathItem.Options = operation
case http.MethodPatch:
pathItem.Patch = operation
case http.MethodPost:
pathItem.Post = operation
case http.MethodPut:
pathItem.Put = operation
default:
panic(fmt.Errorf("unsupported HTTP method %q", method))
}
}
type Operation struct {
openapi3.ExtensionProps
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Responses map[string]*Response `json:"responses" yaml:"responses"`
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
Security *SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
}
func (operation *Operation) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(operation)
}
func (operation *Operation) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, operation)
}
type Parameters []*Parameter
var _ sort.Interface = Parameters{}
func (ps Parameters) Len() int { return len(ps) }
func (ps Parameters) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] }
func (ps Parameters) Less(i, j int) bool {
if ps[i].Name != ps[j].Name {
return ps[i].Name < ps[j].Name
}
if ps[i].In != ps[j].In {
return ps[i].In < ps[j].In
}
return ps[i].Ref < ps[j].Ref
}
type Parameter struct {
openapi3.ExtensionProps
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
Schema *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Items *openapi3.SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"`
MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
Minimum *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
Maximum *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"`
MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"`
MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"`
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
}
func (parameter *Parameter) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(parameter)
}
func (parameter *Parameter) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, parameter)
}
type Response struct {
openapi3.ExtensionProps
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Schema *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"`
Examples map[string]interface{} `json:"examples,omitempty" yaml:"examples,omitempty"`
}
func (response *Response) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(response)
}
func (response *Response) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, response)
}
type Header struct {
openapi3.ExtensionProps
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
}
func (header *Header) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(header)
}
func (header *Header) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, header)
}
type SecurityRequirements []map[string][]string
type SecurityScheme struct {
openapi3.ExtensionProps
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Flow string `json:"flow,omitempty" yaml:"flow,omitempty"`
AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
Scopes map[string]string `json:"scopes,omitempty" yaml:"scopes,omitempty"`
Tags openapi3.Tags `json:"tags,omitempty" yaml:"tags,omitempty"`
}
func (securityScheme *SecurityScheme) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(securityScheme)
}
func (securityScheme *SecurityScheme) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, securityScheme)
}

View File

@ -0,0 +1,94 @@
package openapi2
import (
"encoding/json"
"github.com/getkin/kin-openapi/openapi3"
)
type Operation struct {
Extensions map[string]any `json:"-" yaml:"-"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Responses map[string]*Response `json:"responses" yaml:"responses"`
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
Security *SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
}
// MarshalJSON returns the JSON encoding of Operation.
func (operation Operation) MarshalJSON() ([]byte, error) {
m := make(map[string]any, 12+len(operation.Extensions))
for k, v := range operation.Extensions {
m[k] = v
}
if x := operation.Summary; x != "" {
m["summary"] = x
}
if x := operation.Description; x != "" {
m["description"] = x
}
if x := operation.Deprecated; x {
m["deprecated"] = x
}
if x := operation.ExternalDocs; x != nil {
m["externalDocs"] = x
}
if x := operation.Tags; len(x) != 0 {
m["tags"] = x
}
if x := operation.OperationID; x != "" {
m["operationId"] = x
}
if x := operation.Parameters; len(x) != 0 {
m["parameters"] = x
}
m["responses"] = operation.Responses
if x := operation.Consumes; len(x) != 0 {
m["consumes"] = x
}
if x := operation.Produces; len(x) != 0 {
m["produces"] = x
}
if x := operation.Schemes; len(x) != 0 {
m["schemes"] = x
}
if x := operation.Security; x != nil {
m["security"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Operation to a copy of data.
func (operation *Operation) UnmarshalJSON(data []byte) error {
type OperationBis Operation
var x OperationBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "summary")
delete(x.Extensions, "description")
delete(x.Extensions, "deprecated")
delete(x.Extensions, "externalDocs")
delete(x.Extensions, "tags")
delete(x.Extensions, "operationId")
delete(x.Extensions, "parameters")
delete(x.Extensions, "responses")
delete(x.Extensions, "consumes")
delete(x.Extensions, "produces")
delete(x.Extensions, "schemes")
delete(x.Extensions, "security")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*operation = Operation(x)
return nil
}

View File

@ -0,0 +1,180 @@
package openapi2
import (
"encoding/json"
"sort"
"github.com/getkin/kin-openapi/openapi3"
)
type Parameters []*Parameter
var _ sort.Interface = Parameters{}
func (ps Parameters) Len() int { return len(ps) }
func (ps Parameters) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] }
func (ps Parameters) Less(i, j int) bool {
if ps[i].Name != ps[j].Name {
return ps[i].Name < ps[j].Name
}
if ps[i].In != ps[j].In {
return ps[i].In < ps[j].In
}
return ps[i].Ref < ps[j].Ref
}
type Parameter struct {
Extensions map[string]any `json:"-" yaml:"-"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
Type *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"`
MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
Minimum *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
Maximum *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"`
MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"`
MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"`
Default any `json:"default,omitempty" yaml:"default,omitempty"`
}
// MarshalJSON returns the JSON encoding of Parameter.
func (parameter Parameter) MarshalJSON() ([]byte, error) {
if ref := parameter.Ref; ref != "" {
return json.Marshal(openapi3.Ref{Ref: ref})
}
m := make(map[string]any, 24+len(parameter.Extensions))
for k, v := range parameter.Extensions {
m[k] = v
}
if x := parameter.In; x != "" {
m["in"] = x
}
if x := parameter.Name; x != "" {
m["name"] = x
}
if x := parameter.Description; x != "" {
m["description"] = x
}
if x := parameter.CollectionFormat; x != "" {
m["collectionFormat"] = x
}
if x := parameter.Type; x != nil {
m["type"] = x
}
if x := parameter.Format; x != "" {
m["format"] = x
}
if x := parameter.Pattern; x != "" {
m["pattern"] = x
}
if x := parameter.AllowEmptyValue; x {
m["allowEmptyValue"] = x
}
if x := parameter.Required; x {
m["required"] = x
}
if x := parameter.UniqueItems; x {
m["uniqueItems"] = x
}
if x := parameter.ExclusiveMin; x {
m["exclusiveMinimum"] = x
}
if x := parameter.ExclusiveMax; x {
m["exclusiveMaximum"] = x
}
if x := parameter.Schema; x != nil {
m["schema"] = x
}
if x := parameter.Items; x != nil {
m["items"] = x
}
if x := parameter.Enum; x != nil {
m["enum"] = x
}
if x := parameter.MultipleOf; x != nil {
m["multipleOf"] = x
}
if x := parameter.Minimum; x != nil {
m["minimum"] = x
}
if x := parameter.Maximum; x != nil {
m["maximum"] = x
}
if x := parameter.MaxLength; x != nil {
m["maxLength"] = x
}
if x := parameter.MaxItems; x != nil {
m["maxItems"] = x
}
if x := parameter.MinLength; x != 0 {
m["minLength"] = x
}
if x := parameter.MinItems; x != 0 {
m["minItems"] = x
}
if x := parameter.Default; x != nil {
m["default"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Parameter to a copy of data.
func (parameter *Parameter) UnmarshalJSON(data []byte) error {
type ParameterBis Parameter
var x ParameterBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "$ref")
delete(x.Extensions, "in")
delete(x.Extensions, "name")
delete(x.Extensions, "description")
delete(x.Extensions, "collectionFormat")
delete(x.Extensions, "type")
delete(x.Extensions, "format")
delete(x.Extensions, "pattern")
delete(x.Extensions, "allowEmptyValue")
delete(x.Extensions, "required")
delete(x.Extensions, "uniqueItems")
delete(x.Extensions, "exclusiveMinimum")
delete(x.Extensions, "exclusiveMaximum")
delete(x.Extensions, "schema")
delete(x.Extensions, "items")
delete(x.Extensions, "enum")
delete(x.Extensions, "multipleOf")
delete(x.Extensions, "minimum")
delete(x.Extensions, "maximum")
delete(x.Extensions, "maxLength")
delete(x.Extensions, "maxItems")
delete(x.Extensions, "minLength")
delete(x.Extensions, "minItems")
delete(x.Extensions, "default")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*parameter = Parameter(x)
return nil
}

View File

@ -0,0 +1,153 @@
package openapi2
import (
"encoding/json"
"fmt"
"net/http"
"github.com/getkin/kin-openapi/openapi3"
)
type PathItem struct {
Extensions map[string]any `json:"-" yaml:"-"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"`
Get *Operation `json:"get,omitempty" yaml:"get,omitempty"`
Head *Operation `json:"head,omitempty" yaml:"head,omitempty"`
Options *Operation `json:"options,omitempty" yaml:"options,omitempty"`
Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"`
Post *Operation `json:"post,omitempty" yaml:"post,omitempty"`
Put *Operation `json:"put,omitempty" yaml:"put,omitempty"`
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
}
// MarshalJSON returns the JSON encoding of PathItem.
func (pathItem PathItem) MarshalJSON() ([]byte, error) {
if ref := pathItem.Ref; ref != "" {
return json.Marshal(openapi3.Ref{Ref: ref})
}
m := make(map[string]any, 8+len(pathItem.Extensions))
for k, v := range pathItem.Extensions {
m[k] = v
}
if x := pathItem.Delete; x != nil {
m["delete"] = x
}
if x := pathItem.Get; x != nil {
m["get"] = x
}
if x := pathItem.Head; x != nil {
m["head"] = x
}
if x := pathItem.Options; x != nil {
m["options"] = x
}
if x := pathItem.Patch; x != nil {
m["patch"] = x
}
if x := pathItem.Post; x != nil {
m["post"] = x
}
if x := pathItem.Put; x != nil {
m["put"] = x
}
if x := pathItem.Parameters; len(x) != 0 {
m["parameters"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets PathItem to a copy of data.
func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
type PathItemBis PathItem
var x PathItemBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "$ref")
delete(x.Extensions, "delete")
delete(x.Extensions, "get")
delete(x.Extensions, "head")
delete(x.Extensions, "options")
delete(x.Extensions, "patch")
delete(x.Extensions, "post")
delete(x.Extensions, "put")
delete(x.Extensions, "parameters")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*pathItem = PathItem(x)
return nil
}
func (pathItem *PathItem) Operations() map[string]*Operation {
operations := make(map[string]*Operation)
if v := pathItem.Delete; v != nil {
operations[http.MethodDelete] = v
}
if v := pathItem.Get; v != nil {
operations[http.MethodGet] = v
}
if v := pathItem.Head; v != nil {
operations[http.MethodHead] = v
}
if v := pathItem.Options; v != nil {
operations[http.MethodOptions] = v
}
if v := pathItem.Patch; v != nil {
operations[http.MethodPatch] = v
}
if v := pathItem.Post; v != nil {
operations[http.MethodPost] = v
}
if v := pathItem.Put; v != nil {
operations[http.MethodPut] = v
}
return operations
}
func (pathItem *PathItem) GetOperation(method string) *Operation {
switch method {
case http.MethodDelete:
return pathItem.Delete
case http.MethodGet:
return pathItem.Get
case http.MethodHead:
return pathItem.Head
case http.MethodOptions:
return pathItem.Options
case http.MethodPatch:
return pathItem.Patch
case http.MethodPost:
return pathItem.Post
case http.MethodPut:
return pathItem.Put
default:
panic(fmt.Errorf("unsupported HTTP method %q", method))
}
}
func (pathItem *PathItem) SetOperation(method string, operation *Operation) {
switch method {
case http.MethodDelete:
pathItem.Delete = operation
case http.MethodGet:
pathItem.Get = operation
case http.MethodHead:
pathItem.Head = operation
case http.MethodOptions:
pathItem.Options = operation
case http.MethodPatch:
pathItem.Patch = operation
case http.MethodPost:
pathItem.Post = operation
case http.MethodPut:
pathItem.Put = operation
default:
panic(fmt.Errorf("unsupported HTTP method %q", method))
}
}

9
vendor/github.com/getkin/kin-openapi/openapi2/ref.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
package openapi2
//go:generate go run refsgenerator.go
// Ref is specified by OpenAPI/Swagger 2.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#reference-object
type Ref struct {
Ref string `json:"$ref" yaml:"$ref"`
}

104
vendor/github.com/getkin/kin-openapi/openapi2/refs.go generated vendored Normal file
View File

@ -0,0 +1,104 @@
package openapi2
import (
"encoding/json"
"net/url"
"sort"
"strings"
"github.com/go-openapi/jsonpointer"
"github.com/perimeterx/marshmallow"
)
// SchemaRef represents either a Schema or a $ref to a Schema.
// When serializing and both fields are set, Ref is preferred over Value.
type SchemaRef struct {
// Extensions only captures fields starting with 'x-' as no other fields
// are allowed by the openapi spec.
Extensions map[string]any
Ref string
Value *Schema
extra []string
refPath *url.URL
}
var _ jsonpointer.JSONPointable = (*SchemaRef)(nil)
func (x *SchemaRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
// RefString returns the $ref value.
func (x *SchemaRef) RefString() string { return x.Ref }
// CollectionName returns the JSON string used for a collection of these components.
func (x *SchemaRef) CollectionName() string { return "schemas" }
// RefPath returns the path of the $ref relative to the root document.
func (x *SchemaRef) RefPath() *url.URL { return copyURI(x.refPath) }
func (x *SchemaRef) setRefPath(u *url.URL) {
// Once the refPath is set don't override. References can be loaded
// multiple times not all with access to the correct path info.
if x.refPath != nil {
return
}
x.refPath = copyURI(u)
}
// MarshalYAML returns the YAML encoding of SchemaRef.
func (x SchemaRef) MarshalYAML() (any, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
return x.Value.MarshalYAML()
}
// MarshalJSON returns the JSON encoding of SchemaRef.
func (x SchemaRef) MarshalJSON() ([]byte, error) {
y, err := x.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(y)
}
// UnmarshalJSON sets SchemaRef to a copy of data.
func (x *SchemaRef) UnmarshalJSON(data []byte) error {
var refOnly Ref
if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" {
x.Ref = refOnly.Ref
if len(extra) != 0 {
x.extra = make([]string, 0, len(extra))
for key := range extra {
x.extra = append(x.extra, key)
}
sort.Strings(x.extra)
for k := range extra {
if !strings.HasPrefix(k, "x-") {
delete(extra, k)
}
}
if len(extra) != 0 {
x.Extensions = extra
}
}
return nil
}
return json.Unmarshal(data, &x.Value)
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x *SchemaRef) JSONLookup(token string) (any, error) {
if token == "$ref" {
return x.Ref, nil
}
if v, ok := x.Extensions[token]; ok {
return v, nil
}
ptr, _, err := jsonpointer.GetForToken(x.Value, token)
return ptr, err
}

View File

@ -0,0 +1,63 @@
package openapi2
import (
"encoding/json"
"github.com/getkin/kin-openapi/openapi3"
)
type Response struct {
Extensions map[string]any `json:"-" yaml:"-"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"`
Examples map[string]any `json:"examples,omitempty" yaml:"examples,omitempty"`
}
// MarshalJSON returns the JSON encoding of Response.
func (response Response) MarshalJSON() ([]byte, error) {
if ref := response.Ref; ref != "" {
return json.Marshal(openapi3.Ref{Ref: ref})
}
m := make(map[string]any, 4+len(response.Extensions))
for k, v := range response.Extensions {
m[k] = v
}
if x := response.Description; x != "" {
m["description"] = x
}
if x := response.Schema; x != nil {
m["schema"] = x
}
if x := response.Headers; len(x) != 0 {
m["headers"] = x
}
if x := response.Examples; len(x) != 0 {
m["examples"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Response to a copy of data.
func (response *Response) UnmarshalJSON(data []byte) error {
type ResponseBis Response
var x ResponseBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "$ref")
delete(x.Extensions, "description")
delete(x.Extensions, "schema")
delete(x.Extensions, "headers")
delete(x.Extensions, "examples")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*response = Response(x)
return nil
}

269
vendor/github.com/getkin/kin-openapi/openapi2/schema.go generated vendored Normal file
View File

@ -0,0 +1,269 @@
package openapi2
import (
"encoding/json"
"strings"
"github.com/getkin/kin-openapi/openapi3"
)
type (
Schemas map[string]*SchemaRef
SchemaRefs []*SchemaRef
)
// Schema is specified by OpenAPI/Swagger 2.0 standard.
// See https://swagger.io/specification/v2/#schema-object
type Schema struct {
Extensions map[string]any `json:"-" yaml:"-"`
AllOf SchemaRefs `json:"allOf,omitempty" yaml:"allOf,omitempty"`
Not *SchemaRef `json:"not,omitempty" yaml:"not,omitempty"`
Type *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"`
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"`
Default any `json:"default,omitempty" yaml:"default,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"`
ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
// Array-related, here for struct compactness
UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
// Number-related, here for struct compactness
ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
// Properties
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"`
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
XML *openapi3.XML `json:"xml,omitempty" yaml:"xml,omitempty"`
// Number
Min *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
Max *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"`
MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
// String
MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"`
MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
// Array
MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"`
MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
// Object
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
Properties Schemas `json:"properties,omitempty" yaml:"properties,omitempty"`
MinProps uint64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"`
MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"`
AdditionalProperties openapi3.AdditionalProperties `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"`
Discriminator string `json:"discriminator,omitempty" yaml:"discriminator,omitempty"`
}
// MarshalJSON returns the JSON encoding of Schema.
func (schema Schema) MarshalJSON() ([]byte, error) {
m, err := schema.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(m)
}
// MarshalYAML returns the YAML encoding of Schema.
func (schema Schema) MarshalYAML() (any, error) {
m := make(map[string]any, 36+len(schema.Extensions))
for k, v := range schema.Extensions {
m[k] = v
}
if x := schema.AllOf; len(x) != 0 {
m["allOf"] = x
}
if x := schema.Not; x != nil {
m["not"] = x
}
if x := schema.Type; x != nil {
m["type"] = x
}
if x := schema.Title; len(x) != 0 {
m["title"] = x
}
if x := schema.Format; len(x) != 0 {
m["format"] = x
}
if x := schema.Description; len(x) != 0 {
m["description"] = x
}
if x := schema.Enum; len(x) != 0 {
m["enum"] = x
}
if x := schema.Default; x != nil {
m["default"] = x
}
if x := schema.Example; x != nil {
m["example"] = x
}
if x := schema.ExternalDocs; x != nil {
m["externalDocs"] = x
}
// Array-related
if x := schema.UniqueItems; x {
m["uniqueItems"] = x
}
// Number-related
if x := schema.ExclusiveMin; x {
m["exclusiveMinimum"] = x
}
if x := schema.ExclusiveMax; x {
m["exclusiveMaximum"] = x
}
if x := schema.ReadOnly; x {
m["readOnly"] = x
}
if x := schema.WriteOnly; x {
m["writeOnly"] = x
}
if x := schema.AllowEmptyValue; x {
m["allowEmptyValue"] = x
}
if x := schema.Deprecated; x {
m["deprecated"] = x
}
if x := schema.XML; x != nil {
m["xml"] = x
}
// Number
if x := schema.Min; x != nil {
m["minimum"] = x
}
if x := schema.Max; x != nil {
m["maximum"] = x
}
if x := schema.MultipleOf; x != nil {
m["multipleOf"] = x
}
// String
if x := schema.MinLength; x != 0 {
m["minLength"] = x
}
if x := schema.MaxLength; x != nil {
m["maxLength"] = x
}
if x := schema.Pattern; x != "" {
m["pattern"] = x
}
// Array
if x := schema.MinItems; x != 0 {
m["minItems"] = x
}
if x := schema.MaxItems; x != nil {
m["maxItems"] = x
}
if x := schema.Items; x != nil {
m["items"] = x
}
// Object
if x := schema.Required; len(x) != 0 {
m["required"] = x
}
if x := schema.Properties; len(x) != 0 {
m["properties"] = x
}
if x := schema.MinProps; x != 0 {
m["minProperties"] = x
}
if x := schema.MaxProps; x != nil {
m["maxProperties"] = x
}
if x := schema.AdditionalProperties; x.Has != nil || x.Schema != nil {
m["additionalProperties"] = &x
}
if x := schema.Discriminator; x != "" {
m["discriminator"] = x
}
return m, nil
}
// UnmarshalJSON sets Schema to a copy of data.
func (schema *Schema) UnmarshalJSON(data []byte) error {
type SchemaBis Schema
var x SchemaBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "oneOf")
delete(x.Extensions, "anyOf")
delete(x.Extensions, "allOf")
delete(x.Extensions, "not")
delete(x.Extensions, "type")
delete(x.Extensions, "title")
delete(x.Extensions, "format")
delete(x.Extensions, "description")
delete(x.Extensions, "enum")
delete(x.Extensions, "default")
delete(x.Extensions, "example")
delete(x.Extensions, "externalDocs")
// Array-related
delete(x.Extensions, "uniqueItems")
// Number-related
delete(x.Extensions, "exclusiveMinimum")
delete(x.Extensions, "exclusiveMaximum")
// Properties
delete(x.Extensions, "nullable")
delete(x.Extensions, "readOnly")
delete(x.Extensions, "writeOnly")
delete(x.Extensions, "allowEmptyValue")
delete(x.Extensions, "deprecated")
delete(x.Extensions, "xml")
// Number
delete(x.Extensions, "minimum")
delete(x.Extensions, "maximum")
delete(x.Extensions, "multipleOf")
// String
delete(x.Extensions, "minLength")
delete(x.Extensions, "maxLength")
delete(x.Extensions, "pattern")
// Array
delete(x.Extensions, "minItems")
delete(x.Extensions, "maxItems")
delete(x.Extensions, "items")
// Object
delete(x.Extensions, "required")
delete(x.Extensions, "properties")
delete(x.Extensions, "minProperties")
delete(x.Extensions, "maxProperties")
delete(x.Extensions, "additionalProperties")
delete(x.Extensions, "discriminator")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*schema = Schema(x)
if schema.Format == "date" {
// This is a fix for: https://github.com/getkin/kin-openapi/issues/697
if eg, ok := schema.Example.(string); ok {
schema.Example = strings.TrimSuffix(eg, "T00:00:00Z")
}
}
return nil
}

View File

@ -0,0 +1,90 @@
package openapi2
import (
"encoding/json"
"github.com/getkin/kin-openapi/openapi3"
)
type SecurityRequirements []map[string][]string
type SecurityScheme struct {
Extensions map[string]any `json:"-" yaml:"-"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Flow string `json:"flow,omitempty" yaml:"flow,omitempty"`
AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
Scopes map[string]string `json:"scopes,omitempty" yaml:"scopes,omitempty"`
Tags openapi3.Tags `json:"tags,omitempty" yaml:"tags,omitempty"`
}
// MarshalJSON returns the JSON encoding of SecurityScheme.
func (securityScheme SecurityScheme) MarshalJSON() ([]byte, error) {
if ref := securityScheme.Ref; ref != "" {
return json.Marshal(openapi3.Ref{Ref: ref})
}
m := make(map[string]any, 10+len(securityScheme.Extensions))
for k, v := range securityScheme.Extensions {
m[k] = v
}
if x := securityScheme.Description; x != "" {
m["description"] = x
}
if x := securityScheme.Type; x != "" {
m["type"] = x
}
if x := securityScheme.In; x != "" {
m["in"] = x
}
if x := securityScheme.Name; x != "" {
m["name"] = x
}
if x := securityScheme.Flow; x != "" {
m["flow"] = x
}
if x := securityScheme.AuthorizationURL; x != "" {
m["authorizationUrl"] = x
}
if x := securityScheme.TokenURL; x != "" {
m["tokenUrl"] = x
}
if x := securityScheme.Scopes; len(x) != 0 {
m["scopes"] = x
}
if x := securityScheme.Tags; len(x) != 0 {
m["tags"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets SecurityScheme to a copy of data.
func (securityScheme *SecurityScheme) UnmarshalJSON(data []byte) error {
type SecuritySchemeBis SecurityScheme
var x SecuritySchemeBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "$ref")
delete(x.Extensions, "description")
delete(x.Extensions, "type")
delete(x.Extensions, "in")
delete(x.Extensions, "name")
delete(x.Extensions, "flow")
delete(x.Extensions, "authorizationUrl")
delete(x.Extensions, "tokenUrl")
delete(x.Extensions, "scopes")
delete(x.Extensions, "tags")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*securityScheme = SecurityScheme(x)
return nil
}

View File

@ -1,7 +1,6 @@
package openapi2conv
import (
"encoding/json"
"errors"
"fmt"
"net/url"
@ -14,23 +13,32 @@ import (
// ToV3 converts an OpenAPIv2 spec to an OpenAPIv3 spec
func ToV3(doc2 *openapi2.T) (*openapi3.T, error) {
stripNonCustomExtensions(doc2.Extensions)
return ToV3WithLoader(doc2, openapi3.NewLoader(), nil)
}
func ToV3WithLoader(doc2 *openapi2.T, loader *openapi3.Loader, location *url.URL) (*openapi3.T, error) {
doc3 := &openapi3.T{
OpenAPI: "3.0.3",
Info: &doc2.Info,
Components: openapi3.Components{},
Components: &openapi3.Components{},
Tags: doc2.Tags,
ExtensionProps: doc2.ExtensionProps,
Extensions: stripNonExtensions(doc2.Extensions),
ExternalDocs: doc2.ExternalDocs,
}
if host := doc2.Host; host != "" {
if strings.Contains(host, "/") {
err := fmt.Errorf("invalid host %q. This MUST be the host only and does not include the scheme nor sub-paths.", host)
return nil, err
}
schemes := doc2.Schemes
if len(schemes) == 0 {
schemes = []string{"https://"}
schemes = []string{"https"}
}
basePath := doc2.BasePath
if basePath == "" {
basePath = "/"
}
for _, scheme := range schemes {
u := url.URL{
Scheme: scheme,
@ -46,7 +54,7 @@ func ToV3(doc2 *openapi2.T) (*openapi3.T, error) {
doc3.Components.Parameters = make(map[string]*openapi3.ParameterRef)
doc3.Components.RequestBodies = make(map[string]*openapi3.RequestBodyRef)
for k, parameter := range parameters {
v3Parameter, v3RequestBody, v3SchemaMap, err := ToV3Parameter(&doc3.Components, parameter, doc2.Consumes)
v3Parameter, v3RequestBody, v3SchemaMap, err := ToV3Parameter(doc3.Components, parameter, doc2.Consumes)
switch {
case err != nil:
return nil, err
@ -63,21 +71,20 @@ func ToV3(doc2 *openapi2.T) (*openapi3.T, error) {
}
if paths := doc2.Paths; len(paths) != 0 {
doc3Paths := make(map[string]*openapi3.PathItem, len(paths))
doc3.Paths = openapi3.NewPathsWithCapacity(len(paths))
for path, pathItem := range paths {
r, err := ToV3PathItem(doc2, &doc3.Components, pathItem, doc2.Consumes)
r, err := ToV3PathItem(doc2, doc3.Components, pathItem, doc2.Consumes)
if err != nil {
return nil, err
}
doc3Paths[path] = r
doc3.Paths.Set(path, r)
}
doc3.Paths = doc3Paths
}
if responses := doc2.Responses; len(responses) != 0 {
doc3.Components.Responses = make(map[string]*openapi3.ResponseRef, len(responses))
doc3.Components.Responses = make(openapi3.ResponseBodies, len(responses))
for k, response := range responses {
r, err := ToV3Response(response)
r, err := ToV3Response(response, doc2.Produces)
if err != nil {
return nil, err
}
@ -102,19 +109,17 @@ func ToV3(doc2 *openapi2.T) (*openapi3.T, error) {
}
doc3.Security = ToV3SecurityRequirements(doc2.Security)
{
sl := openapi3.NewLoader()
if err := sl.ResolveRefsIn(doc3, nil); err != nil {
if err := loader.ResolveRefsIn(doc3, location); err != nil {
return nil, err
}
}
return doc3, nil
}
func ToV3PathItem(doc2 *openapi2.T, components *openapi3.Components, pathItem *openapi2.PathItem, consumes []string) (*openapi3.PathItem, error) {
stripNonCustomExtensions(pathItem.Extensions)
doc3 := &openapi3.PathItem{
ExtensionProps: pathItem.ExtensionProps,
Extensions: stripNonExtensions(pathItem.Extensions),
}
for method, operation := range pathItem.Operations() {
doc3Operation, err := ToV3Operation(doc2, components, pathItem, operation, consumes)
@ -143,13 +148,13 @@ func ToV3Operation(doc2 *openapi2.T, components *openapi3.Components, pathItem *
if operation == nil {
return nil, nil
}
stripNonCustomExtensions(operation.Extensions)
doc3 := &openapi3.Operation{
OperationID: operation.OperationID,
Summary: operation.Summary,
Description: operation.Description,
Deprecated: operation.Deprecated,
Tags: operation.Tags,
ExtensionProps: operation.ExtensionProps,
Extensions: stripNonExtensions(operation.Extensions),
}
if v := operation.Security; v != nil {
doc3Security := ToV3SecurityRequirements(*v)
@ -183,15 +188,14 @@ func ToV3Operation(doc2 *openapi2.T, components *openapi3.Components, pathItem *
}
if responses := operation.Responses; responses != nil {
doc3Responses := make(openapi3.Responses, len(responses))
doc3.Responses = openapi3.NewResponsesWithCapacity(len(responses))
for k, response := range responses {
doc3, err := ToV3Response(response)
responseRef3, err := ToV3Response(response, operation.Produces)
if err != nil {
return nil, err
}
doc3Responses[k] = doc3
doc3.Responses.Set(k, responseRef3)
}
doc3.Responses = doc3Responses
}
return doc3, nil
}
@ -222,46 +226,43 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete
}
return &openapi3.ParameterRef{Ref: ToV3Ref(ref)}, nil, nil, nil
}
stripNonCustomExtensions(parameter.Extensions)
switch parameter.In {
case "body":
result := &openapi3.RequestBody{
Description: parameter.Description,
Required: parameter.Required,
ExtensionProps: parameter.ExtensionProps,
Extensions: stripNonExtensions(parameter.Extensions),
}
if parameter.Name != "" {
if result.Extensions == nil {
result.Extensions = make(map[string]interface{})
result.Extensions = make(map[string]any, 1)
}
result.Extensions["x-originalParamName"] = parameter.Name
}
if schemaRef := parameter.Schema; schemaRef != nil {
// Assuming JSON
result.WithSchemaRef(ToV3SchemaRef(schemaRef), consumes)
}
return nil, &openapi3.RequestBodyRef{Value: result}, nil, nil
case "formData":
format, typ := parameter.Format, parameter.Type
if typ == "file" {
format, typ = "binary", "string"
if typ.Is("file") {
format, typ = "binary", &openapi3.Types{"string"}
}
if parameter.ExtensionProps.Extensions == nil {
parameter.ExtensionProps.Extensions = make(map[string]interface{})
if parameter.Extensions == nil {
parameter.Extensions = make(map[string]any, 1)
}
parameter.ExtensionProps.Extensions["x-formData-name"] = parameter.Name
parameter.Extensions["x-formData-name"] = parameter.Name
var required []string
if parameter.Required {
required = []string{parameter.Name}
}
schemaRef := &openapi3.SchemaRef{
Value: &openapi3.Schema{
schemaRef := &openapi3.SchemaRef{Value: &openapi3.Schema{
Description: parameter.Description,
Type: typ,
ExtensionProps: parameter.ExtensionProps,
Extensions: stripNonExtensions(parameter.Extensions),
Format: format,
Enum: parameter.Enum,
Min: parameter.Minimum,
@ -271,7 +272,6 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete
MinLength: parameter.MinLength,
MaxLength: parameter.MaxLength,
Default: parameter.Default,
Items: parameter.Items,
MinItems: parameter.MinItems,
MaxItems: parameter.MaxItems,
Pattern: parameter.Pattern,
@ -279,9 +279,12 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete
UniqueItems: parameter.UniqueItems,
MultipleOf: parameter.MultipleOf,
Required: required,
},
}}
if parameter.Items != nil {
schemaRef.Value.Items = ToV3SchemaRef(parameter.Items)
}
schemaRefMap := make(map[string]*openapi3.SchemaRef)
schemaRefMap := make(map[string]*openapi3.SchemaRef, 1)
schemaRefMap[parameter.Name] = schemaRef
return nil, nil, schemaRefMap, nil
@ -291,13 +294,17 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete
required = true
}
var schemaRefRef string
if schemaRef := parameter.Schema; schemaRef != nil && schemaRef.Ref != "" {
schemaRefRef = schemaRef.Ref
}
result := &openapi3.Parameter{
In: parameter.In,
Name: parameter.Name,
Description: parameter.Description,
Required: required,
ExtensionProps: parameter.ExtensionProps,
Schema: ToV3SchemaRef(&openapi3.SchemaRef{Value: &openapi3.Schema{
Extensions: stripNonExtensions(parameter.Extensions),
Schema: ToV3SchemaRef(&openapi2.SchemaRef{Value: &openapi2.Schema{
Type: parameter.Type,
Format: parameter.Format,
Enum: parameter.Enum,
@ -315,7 +322,9 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete
AllowEmptyValue: parameter.AllowEmptyValue,
UniqueItems: parameter.UniqueItems,
MultipleOf: parameter.MultipleOf,
}}),
},
Ref: schemaRefRef,
}),
}
return &openapi3.ParameterRef{Value: result}, nil, nil, nil
}
@ -334,9 +343,16 @@ func formDataBody(bodies map[string]*openapi3.SchemaRef, reqs map[string]bool, c
requireds = append(requireds, propName)
}
}
for s, ref := range bodies {
if ref.Value != nil && len(ref.Value.Required) > 0 {
ref.Value.Required = nil
bodies[s] = ref
}
}
sort.Strings(requireds)
schema := &openapi3.Schema{
Type: "object",
Properties: ToV3Schemas(bodies),
Type: &openapi3.Types{"object"},
Properties: bodies,
Required: requireds,
}
return &openapi3.RequestBodyRef{
@ -399,22 +415,51 @@ func onlyOneReqBodyParam(bodies []*openapi3.RequestBodyRef, formDataSchemas map[
return nil, nil
}
func ToV3Response(response *openapi2.Response) (*openapi3.ResponseRef, error) {
func ToV3Response(response *openapi2.Response, produces []string) (*openapi3.ResponseRef, error) {
if ref := response.Ref; ref != "" {
return &openapi3.ResponseRef{Ref: ToV3Ref(ref)}, nil
}
stripNonCustomExtensions(response.Extensions)
result := &openapi3.Response{
Description: &response.Description,
ExtensionProps: response.ExtensionProps,
Extensions: stripNonExtensions(response.Extensions),
}
// Default to "application/json" if "produces" is not specified.
if len(produces) == 0 {
produces = []string{"application/json"}
}
if schemaRef := response.Schema; schemaRef != nil {
result.WithJSONSchemaRef(ToV3SchemaRef(schemaRef))
schema := ToV3SchemaRef(schemaRef)
result.Content = make(openapi3.Content, len(produces))
for _, mime := range produces {
result.Content[mime] = openapi3.NewMediaType().WithSchemaRef(schema)
}
}
if headers := response.Headers; len(headers) > 0 {
result.Headers = ToV3Headers(headers)
}
return &openapi3.ResponseRef{Value: result}, nil
}
func ToV3Schemas(defs map[string]*openapi3.SchemaRef) map[string]*openapi3.SchemaRef {
func ToV3Headers(defs map[string]*openapi2.Header) openapi3.Headers {
headers := make(openapi3.Headers, len(defs))
for name, header := range defs {
header.In = ""
header.Name = ""
if ref := header.Ref; ref != "" {
headers[name] = &openapi3.HeaderRef{Ref: ToV3Ref(ref)}
} else {
parameter, _, _, _ := ToV3Parameter(nil, &header.Parameter, nil)
headers[name] = &openapi3.HeaderRef{Value: &openapi3.Header{
Parameter: *parameter.Value,
}}
}
}
return headers
}
func ToV3Schemas(defs map[string]*openapi2.SchemaRef) map[string]*openapi3.SchemaRef {
schemas := make(map[string]*openapi3.SchemaRef, len(defs))
for name, schema := range defs {
schemas[name] = ToV3SchemaRef(schema)
@ -422,26 +467,105 @@ func ToV3Schemas(defs map[string]*openapi3.SchemaRef) map[string]*openapi3.Schem
return schemas
}
func ToV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef {
func ToV3SchemaRef(schema *openapi2.SchemaRef) *openapi3.SchemaRef {
if schema == nil {
return &openapi3.SchemaRef{}
}
if ref := schema.Ref; ref != "" {
return &openapi3.SchemaRef{Ref: ToV3Ref(ref)}
}
if schema.Value == nil {
return schema
return &openapi3.SchemaRef{
Extensions: schema.Extensions,
}
}
v3Schema := &openapi3.Schema{
Extensions: schema.Extensions,
Type: schema.Value.Type,
Title: schema.Value.Title,
Format: schema.Value.Format,
Description: schema.Value.Description,
Enum: schema.Value.Enum,
Default: schema.Value.Default,
Example: schema.Value.Example,
ExternalDocs: schema.Value.ExternalDocs,
UniqueItems: schema.Value.UniqueItems,
ExclusiveMin: schema.Value.ExclusiveMin,
ExclusiveMax: schema.Value.ExclusiveMax,
ReadOnly: schema.Value.ReadOnly,
WriteOnly: schema.Value.WriteOnly,
AllowEmptyValue: schema.Value.AllowEmptyValue,
Deprecated: schema.Value.Deprecated,
XML: schema.Value.XML,
Min: schema.Value.Min,
Max: schema.Value.Max,
MultipleOf: schema.Value.MultipleOf,
MinLength: schema.Value.MinLength,
MaxLength: schema.Value.MaxLength,
Pattern: schema.Value.Pattern,
MinItems: schema.Value.MinItems,
MaxItems: schema.Value.MaxItems,
Required: schema.Value.Required,
MinProps: schema.Value.MinProps,
MaxProps: schema.Value.MaxProps,
AllOf: make(openapi3.SchemaRefs, len(schema.Value.AllOf)),
Properties: make(openapi3.Schemas),
AdditionalProperties: toV3AdditionalProperties(schema.Value.AdditionalProperties),
}
if schema.Value.Discriminator != "" {
v3Schema.Discriminator = &openapi3.Discriminator{
PropertyName: schema.Value.Discriminator,
}
}
if schema.Value.Items != nil {
schema.Value.Items = ToV3SchemaRef(schema.Value.Items)
v3Schema.Items = ToV3SchemaRef(schema.Value.Items)
}
if schema.Value.Type.Is("file") {
v3Schema.Format, v3Schema.Type = "binary", &openapi3.Types{"string"}
}
for k, v := range schema.Value.Properties {
schema.Value.Properties[k] = ToV3SchemaRef(v)
}
if v := schema.Value.AdditionalProperties; v != nil {
schema.Value.AdditionalProperties = ToV3SchemaRef(v)
v3Schema.Properties[k] = ToV3SchemaRef(v)
}
for i, v := range schema.Value.AllOf {
schema.Value.AllOf[i] = ToV3SchemaRef(v)
v3Schema.AllOf[i] = ToV3SchemaRef(v)
}
return schema
if val, ok := schema.Value.Extensions["x-nullable"]; ok {
if nullable, valid := val.(bool); valid {
v3Schema.Nullable = nullable
delete(v3Schema.Extensions, "x-nullable")
}
}
return &openapi3.SchemaRef{
Extensions: schema.Extensions,
Value: v3Schema,
}
}
func toV3AdditionalProperties(from openapi3.AdditionalProperties) openapi3.AdditionalProperties {
return openapi3.AdditionalProperties{
Has: from.Has,
Schema: convertRefsInV3SchemaRef(from.Schema),
}
}
func convertRefsInV3SchemaRef(from *openapi3.SchemaRef) *openapi3.SchemaRef {
if from == nil {
return nil
}
to := *from
to.Ref = ToV3Ref(to.Ref)
if to.Value != nil {
v := *from.Value
to.Value = &v
to.Value.AdditionalProperties = toV3AdditionalProperties(to.Value.AdditionalProperties)
}
return &to
}
var ref2To3 = map[string]string{
@ -485,10 +609,9 @@ func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.Secu
if securityScheme == nil {
return nil, nil
}
stripNonCustomExtensions(securityScheme.Extensions)
result := &openapi3.SecurityScheme{
Description: securityScheme.Description,
ExtensionProps: securityScheme.ExtensionProps,
Extensions: stripNonExtensions(securityScheme.Extensions),
}
switch securityScheme.Type {
case "basic":
@ -518,6 +641,8 @@ func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.Secu
flows.AuthorizationCode = flow
case "password":
flows.Password = flow
case "application":
flows.ClientCredentials = flow
default:
return nil, fmt.Errorf("unsupported flow %q", securityScheme.Flow)
}
@ -530,12 +655,11 @@ func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.Secu
// FromV3 converts an OpenAPIv3 spec to an OpenAPIv2 spec
func FromV3(doc3 *openapi3.T) (*openapi2.T, error) {
doc2Responses, err := FromV3Responses(doc3.Components.Responses, &doc3.Components)
doc2Responses, err := FromV3Responses(doc3.Components.Responses, doc3.Components)
if err != nil {
return nil, err
}
stripNonCustomExtensions(doc3.Extensions)
schemas, parameters := FromV3Schemas(doc3.Components.Schemas, &doc3.Components)
schemas, parameters := FromV3Schemas(doc3.Components.Schemas, doc3.Components)
doc2 := &openapi2.T{
Swagger: "2.0",
Info: *doc3.Info,
@ -543,7 +667,7 @@ func FromV3(doc3 *openapi3.T) (*openapi2.T, error) {
Parameters: parameters,
Responses: doc2Responses,
Tags: doc3.Tags,
ExtensionProps: doc3.ExtensionProps,
Extensions: stripNonExtensions(doc3.Extensions),
ExternalDocs: doc3.ExternalDocs,
}
@ -566,19 +690,20 @@ func FromV3(doc3 *openapi3.T) (*openapi2.T, error) {
}
}
}
if isHTTPS {
doc2.Schemes = append(doc2.Schemes, "https")
}
if isHTTP {
doc2.Schemes = append(doc2.Schemes, "http")
}
for path, pathItem := range doc3.Paths {
for path, pathItem := range doc3.Paths.Map() {
if pathItem == nil {
continue
}
doc2.AddOperation(path, "GET", nil)
stripNonCustomExtensions(pathItem.Extensions)
addPathExtensions(doc2, path, pathItem.ExtensionProps)
addPathExtensions(doc2, path, stripNonExtensions(pathItem.Extensions))
for method, operation := range pathItem.Operations() {
if operation == nil {
continue
@ -591,7 +716,7 @@ func FromV3(doc3 *openapi3.T) (*openapi2.T, error) {
}
params := openapi2.Parameters{}
for _, param := range pathItem.Parameters {
p, err := FromV3Parameter(param, &doc3.Components)
p, err := FromV3Parameter(param, doc3.Components)
if err != nil {
return nil, err
}
@ -602,13 +727,13 @@ func FromV3(doc3 *openapi3.T) (*openapi2.T, error) {
}
for name, param := range doc3.Components.Parameters {
if doc2.Parameters[name], err = FromV3Parameter(param, &doc3.Components); err != nil {
if doc2.Parameters[name], err = FromV3Parameter(param, doc3.Components); err != nil {
return nil, err
}
}
for name, requestBodyRef := range doc3.Components.RequestBodies {
bodyOrRefParameters, formDataParameters, consumes, err := fromV3RequestBodies(name, requestBodyRef, &doc3.Components)
bodyOrRefParameters, formDataParameters, consumes, err := fromV3RequestBodies(name, requestBodyRef, doc3.Components)
if err != nil {
return nil, err
}
@ -639,6 +764,7 @@ func FromV3(doc3 *openapi3.T) (*openapi2.T, error) {
doc2.SecurityDefinitions = doc2SecuritySchemes
}
doc2.Security = FromV3SecurityRequirements(doc3.Security)
return doc2, nil
}
@ -662,34 +788,36 @@ func fromV3RequestBodies(name string, requestBodyRef *openapi3.RequestBodyRef, c
return
}
//Only select one formData or request body for an individual requesstBody as OpenAPI 2 does not support multiples
// Only select one formData or request body for an individual requestBody as OpenAPI 2 does not support multiples
if requestBodyRef.Value != nil {
for contentType, mediaType := range requestBodyRef.Value.Content {
if consumes == nil {
consumes = make(map[string]struct{})
}
consumes[contentType] = struct{}{}
if formParams := FromV3RequestBodyFormData(mediaType); len(formParams) != 0 {
formParameters = formParams
} else {
if contentType == "application/x-www-form-urlencoded" || contentType == "multipart/form-data" {
formParameters = FromV3RequestBodyFormData(mediaType)
continue
}
paramName := name
if originalName, ok := requestBodyRef.Value.Extensions["x-originalParamName"]; ok {
json.Unmarshal(originalName.(json.RawMessage), &paramName)
paramName = originalName.(string)
}
var r *openapi2.Parameter
if r, err = FromV3RequestBody(paramName, requestBodyRef, mediaType, components); err != nil {
return
}
bodyOrRefParameters = append(bodyOrRefParameters, r)
}
}
}
return
}
func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi3.SchemaRef, map[string]*openapi2.Parameter) {
v2Defs := make(map[string]*openapi3.SchemaRef)
func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi2.SchemaRef, map[string]*openapi2.Parameter) {
v2Defs := make(map[string]*openapi2.SchemaRef)
v2Params := make(map[string]*openapi2.Parameter)
for name, schema := range schemas {
schemaConv, parameterConv := FromV3SchemaRef(schema, components)
@ -705,7 +833,7 @@ func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.
return v2Defs, v2Params
}
func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi3.SchemaRef, *openapi2.Parameter) {
func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi2.SchemaRef, *openapi2.Parameter) {
if ref := schema.Ref; ref != "" {
name := getParameterNameFromNewRef(ref)
if val, ok := components.Schemas[name]; ok {
@ -715,23 +843,25 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components
}
}
return &openapi3.SchemaRef{Ref: FromV3Ref(ref)}, nil
return &openapi2.SchemaRef{Ref: FromV3Ref(ref)}, nil
}
if schema.Value == nil {
return schema, nil
return &openapi2.SchemaRef{
Extensions: schema.Extensions,
}, nil
}
if schema.Value != nil {
if schema.Value.Type == "string" && schema.Value.Format == "binary" {
paramType := "file"
if schema.Value.Type.Is("string") && schema.Value.Format == "binary" {
paramType := &openapi3.Types{"file"}
required := false
value, _ := schema.Value.Extensions["x-formData-name"]
var originalName string
json.Unmarshal(value.(json.RawMessage), &originalName)
originalName, _ := value.(string)
for _, prop := range schema.Value.Required {
if originalName == prop {
required = true
break
}
}
return nil, &openapi2.Parameter{
@ -747,35 +877,83 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components
MinLength: schema.Value.MinLength,
MaxLength: schema.Value.MaxLength,
Default: schema.Value.Default,
Items: schema.Value.Items,
// Items: schema.Value.Items,
MinItems: schema.Value.MinItems,
MaxItems: schema.Value.MaxItems,
AllowEmptyValue: schema.Value.AllowEmptyValue,
UniqueItems: schema.Value.UniqueItems,
MultipleOf: schema.Value.MultipleOf,
ExtensionProps: schema.Value.ExtensionProps,
Extensions: stripNonExtensions(schema.Value.Extensions),
Required: required,
}
}
}
if v := schema.Value.Items; v != nil {
schema.Value.Items, _ = FromV3SchemaRef(v, components)
v2Schema := &openapi2.Schema{
Extensions: schema.Value.Extensions,
Type: schema.Value.Type,
Title: schema.Value.Title,
Format: schema.Value.Format,
Description: schema.Value.Description,
Enum: schema.Value.Enum,
Default: schema.Value.Default,
Example: schema.Value.Example,
ExternalDocs: schema.Value.ExternalDocs,
UniqueItems: schema.Value.UniqueItems,
ExclusiveMin: schema.Value.ExclusiveMin,
ExclusiveMax: schema.Value.ExclusiveMax,
ReadOnly: schema.Value.ReadOnly,
WriteOnly: schema.Value.WriteOnly,
AllowEmptyValue: schema.Value.AllowEmptyValue,
Deprecated: schema.Value.Deprecated,
XML: schema.Value.XML,
Min: schema.Value.Min,
Max: schema.Value.Max,
MultipleOf: schema.Value.MultipleOf,
MinLength: schema.Value.MinLength,
MaxLength: schema.Value.MaxLength,
Pattern: schema.Value.Pattern,
MinItems: schema.Value.MinItems,
MaxItems: schema.Value.MaxItems,
Required: schema.Value.Required,
MinProps: schema.Value.MinProps,
MaxProps: schema.Value.MaxProps,
Properties: make(openapi2.Schemas),
AllOf: make(openapi2.SchemaRefs, len(schema.Value.AllOf)),
AdditionalProperties: schema.Value.AdditionalProperties,
}
if v := schema.Value.Items; v != nil {
v2Schema.Items, _ = FromV3SchemaRef(v, components)
}
keys := make([]string, 0, len(schema.Value.Properties))
for k := range schema.Value.Properties {
keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
schema.Value.Properties[key], _ = FromV3SchemaRef(schema.Value.Properties[key], components)
property, _ := FromV3SchemaRef(schema.Value.Properties[key], components)
if property != nil {
v2Schema.Properties[key] = property
}
if v := schema.Value.AdditionalProperties; v != nil {
schema.Value.AdditionalProperties, _ = FromV3SchemaRef(v, components)
}
for i, v := range schema.Value.AllOf {
schema.Value.AllOf[i], _ = FromV3SchemaRef(v, components)
v2Schema.AllOf[i], _ = FromV3SchemaRef(v, components)
}
return schema, nil
if schema.Value.PermitsNull() {
schema.Value.Nullable = false
if schema.Value.Extensions == nil {
v2Schema.Extensions = make(map[string]any)
}
v2Schema.Extensions["x-nullable"] = true
}
return &openapi2.SchemaRef{
Extensions: schema.Extensions,
Value: v2Schema,
}, nil
}
func FromV3SecurityRequirements(requirements openapi3.SecurityRequirements) openapi2.SecurityRequirements {
@ -790,9 +968,8 @@ func FromV3SecurityRequirements(requirements openapi3.SecurityRequirements) open
}
func FromV3PathItem(doc3 *openapi3.T, pathItem *openapi3.PathItem) (*openapi2.PathItem, error) {
stripNonCustomExtensions(pathItem.Extensions)
result := &openapi2.PathItem{
ExtensionProps: pathItem.ExtensionProps,
Extensions: stripNonExtensions(pathItem.Extensions),
}
for method, operation := range pathItem.Operations() {
r, err := FromV3Operation(doc3, operation)
@ -802,7 +979,7 @@ func FromV3PathItem(doc3 *openapi3.T, pathItem *openapi3.PathItem) (*openapi2.Pa
result.SetOperation(method, r)
}
for _, parameter := range pathItem.Parameters {
p, err := FromV3Parameter(parameter, &doc3.Components)
p, err := FromV3Parameter(parameter, doc3.Components)
if err != nil {
return nil, err
}
@ -836,7 +1013,7 @@ func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameter
val := schemaRef.Value
typ := val.Type
if val.Format == "binary" {
typ = "file"
typ = &openapi3.Types{"file"}
}
required := false
for _, name := range val.Required {
@ -845,19 +1022,24 @@ func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameter
break
}
}
var v2Items *openapi2.SchemaRef
if val.Items != nil {
v2Items, _ = FromV3SchemaRef(val.Items, nil)
}
parameter := &openapi2.Parameter{
Name: propName,
Description: val.Description,
Type: typ,
In: "formData",
ExtensionProps: val.ExtensionProps,
Extensions: stripNonExtensions(val.Extensions),
Enum: val.Enum,
ExclusiveMin: val.ExclusiveMin,
ExclusiveMax: val.ExclusiveMax,
MinLength: val.MinLength,
MaxLength: val.MaxLength,
Default: val.Default,
Items: val.Items,
Items: v2Items,
MinItems: val.MinItems,
MaxItems: val.MaxItems,
Maximum: val.Max,
@ -879,20 +1061,20 @@ func FromV3Operation(doc3 *openapi3.T, operation *openapi3.Operation) (*openapi2
if operation == nil {
return nil, nil
}
stripNonCustomExtensions(operation.Extensions)
result := &openapi2.Operation{
OperationID: operation.OperationID,
Summary: operation.Summary,
Description: operation.Description,
Deprecated: operation.Deprecated,
Tags: operation.Tags,
ExtensionProps: operation.ExtensionProps,
Extensions: stripNonExtensions(operation.Extensions),
}
if v := operation.Security; v != nil {
resultSecurity := FromV3SecurityRequirements(*v)
result.Security = &resultSecurity
}
for _, parameter := range operation.Parameters {
r, err := FromV3Parameter(parameter, &doc3.Components)
r, err := FromV3Parameter(parameter, doc3.Components)
if err != nil {
return nil, err
}
@ -905,7 +1087,7 @@ func FromV3Operation(doc3 *openapi3.T, operation *openapi3.Operation) (*openapi2
return nil, errors.New("could not find a name for request body")
}
bodyOrRefParameters, formDataParameters, consumes, err := fromV3RequestBodies(name, v, &doc3.Components)
bodyOrRefParameters, formDataParameters, consumes, err := fromV3RequestBodies(name, v, doc3.Components)
if err != nil {
return nil, err
}
@ -926,7 +1108,7 @@ func FromV3Operation(doc3 *openapi3.T, operation *openapi3.Operation) (*openapi2
sort.Sort(result.Parameters)
if responses := operation.Responses; responses != nil {
resultResponses, err := FromV3Responses(responses, &doc3.Components)
resultResponses, err := FromV3Responses(responses.Map(), doc3.Components)
if err != nil {
return nil, err
}
@ -938,13 +1120,12 @@ func FromV3Operation(doc3 *openapi3.T, operation *openapi3.Operation) (*openapi2
func FromV3RequestBody(name string, requestBodyRef *openapi3.RequestBodyRef, mediaType *openapi3.MediaType, components *openapi3.Components) (*openapi2.Parameter, error) {
requestBody := requestBodyRef.Value
stripNonCustomExtensions(requestBody.Extensions)
result := &openapi2.Parameter{
In: "body",
Name: name,
Description: requestBody.Description,
Required: requestBody.Required,
ExtensionProps: requestBody.ExtensionProps,
Extensions: stripNonExtensions(requestBody.Extensions),
}
if mediaType != nil {
@ -961,17 +1142,20 @@ func FromV3Parameter(ref *openapi3.ParameterRef, components *openapi3.Components
if parameter == nil {
return nil, nil
}
stripNonCustomExtensions(parameter.Extensions)
result := &openapi2.Parameter{
Description: parameter.Description,
In: parameter.In,
Name: parameter.Name,
Required: parameter.Required,
ExtensionProps: parameter.ExtensionProps,
Extensions: stripNonExtensions(parameter.Extensions),
}
if schemaRef := parameter.Schema; schemaRef != nil {
schemaRef, _ = FromV3SchemaRef(schemaRef, components)
schema := schemaRef.Value
schemaRefV2, _ := FromV3SchemaRef(schemaRef, components)
if ref := schemaRefV2.Ref; ref != "" {
result.Schema = &openapi2.SchemaRef{Ref: FromV3Ref(ref)}
return result, nil
}
schema := schemaRefV2.Value
result.Type = schema.Type
result.Format = schema.Format
result.Enum = schema.Enum
@ -1019,29 +1203,48 @@ func FromV3Response(ref *openapi3.ResponseRef, components *openapi3.Components)
if desc := response.Description; desc != nil {
description = *desc
}
stripNonCustomExtensions(response.Extensions)
result := &openapi2.Response{
Description: description,
ExtensionProps: response.ExtensionProps,
Extensions: stripNonExtensions(response.Extensions),
}
if content := response.Content; content != nil {
if ct := content["application/json"]; ct != nil {
result.Schema, _ = FromV3SchemaRef(ct.Schema, components)
}
}
if headers := response.Headers; len(headers) > 0 {
var err error
if result.Headers, err = FromV3Headers(headers, components); err != nil {
return nil, err
}
}
return result, nil
}
func FromV3Headers(defs openapi3.Headers, components *openapi3.Components) (map[string]*openapi2.Header, error) {
headers := make(map[string]*openapi2.Header, len(defs))
for name, header := range defs {
ref := openapi3.ParameterRef{Ref: header.Ref, Value: &header.Value.Parameter}
parameter, err := FromV3Parameter(&ref, components)
if err != nil {
return nil, err
}
parameter.In = ""
parameter.Name = ""
headers[name] = &openapi2.Header{Parameter: *parameter}
}
return headers, nil
}
func FromV3SecurityScheme(doc3 *openapi3.T, ref *openapi3.SecuritySchemeRef) (*openapi2.SecurityScheme, error) {
securityScheme := ref.Value
if securityScheme == nil {
return nil, nil
}
stripNonCustomExtensions(securityScheme.Extensions)
result := &openapi2.SecurityScheme{
Ref: FromV3Ref(ref.Ref),
Description: securityScheme.Description,
ExtensionProps: securityScheme.ExtensionProps,
Extensions: stripNonExtensions(securityScheme.Extensions),
}
switch securityScheme.Type {
case "http":
@ -1063,15 +1266,33 @@ func FromV3SecurityScheme(doc3 *openapi3.T, ref *openapi3.SecuritySchemeRef) (*o
if flows != nil {
var flow *openapi3.OAuthFlow
// TODO: Is this the right priority? What if multiple defined?
if flow = flows.Implicit; flow != nil {
switch {
case flows.Implicit != nil:
result.Flow = "implicit"
} else if flow = flows.AuthorizationCode; flow != nil {
flow = flows.Implicit
result.AuthorizationURL = flow.AuthorizationURL
case flows.AuthorizationCode != nil:
result.Flow = "accessCode"
} else if flow = flows.Password; flow != nil {
flow = flows.AuthorizationCode
result.AuthorizationURL = flow.AuthorizationURL
result.TokenURL = flow.TokenURL
case flows.Password != nil:
result.Flow = "password"
} else {
flow = flows.Password
result.TokenURL = flow.TokenURL
case flows.ClientCredentials != nil:
result.Flow = "application"
flow = flows.ClientCredentials
result.TokenURL = flow.TokenURL
default:
return nil, nil
}
result.Scopes = make(map[string]string, len(flow.Scopes))
for scope, desc := range flow.Scopes {
result.Scopes[scope] = desc
}
@ -1087,24 +1308,24 @@ var attemptedBodyParameterNames = []string{
"requestBody",
}
func stripNonCustomExtensions(extensions map[string]interface{}) {
// stripNonExtensions removes invalid extensions: those not prefixed by "x-" and returns them
func stripNonExtensions(extensions map[string]any) map[string]any {
for extName := range extensions {
if !strings.HasPrefix(extName, "x-") {
delete(extensions, extName)
}
}
return extensions
}
func addPathExtensions(doc2 *openapi2.T, path string, extensionProps openapi3.ExtensionProps) {
paths := doc2.Paths
if paths == nil {
paths = make(map[string]*openapi2.PathItem, 8)
doc2.Paths = paths
func addPathExtensions(doc2 *openapi2.T, path string, extensions map[string]any) {
if doc2.Paths == nil {
doc2.Paths = make(map[string]*openapi2.PathItem)
}
pathItem := paths[path]
pathItem := doc2.Paths[path]
if pathItem == nil {
pathItem = &openapi2.PathItem{}
paths[path] = pathItem
doc2.Paths[path] = pathItem
}
pathItem.ExtensionProps = extensionProps
pathItem.Extensions = extensions
}

View File

@ -2,35 +2,60 @@ package openapi3
import (
"context"
"fmt"
"github.com/go-openapi/jsonpointer"
"sort"
)
type Callbacks map[string]*CallbackRef
// Callback is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callback-object
type Callback struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
var _ jsonpointer.JSONPointable = (*Callbacks)(nil)
func (c Callbacks) JSONLookup(token string) (interface{}, error) {
ref, ok := c[token]
if ref == nil || !ok {
return nil, fmt.Errorf("object has no field %q", token)
m map[string]*PathItem
}
if ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
// NewCallback builds a Callback object with path items in insertion order.
func NewCallback(opts ...NewCallbackOption) *Callback {
Callback := NewCallbackWithCapacity(len(opts))
for _, opt := range opts {
opt(Callback)
}
return ref.Value, nil
return Callback
}
// Callback is specified by OpenAPI/Swagger standard version 3.0.
type Callback map[string]*PathItem
// NewCallbackOption describes options to NewCallback func
type NewCallbackOption func(*Callback)
func (value Callback) Validate(ctx context.Context) error {
for _, v := range value {
// WithCallback adds Callback as an option to NewCallback
func WithCallback(cb string, pathItem *PathItem) NewCallbackOption {
return func(callback *Callback) {
if p := pathItem; p != nil && cb != "" {
callback.Set(cb, p)
}
}
}
// Validate returns an error if Callback does not comply with the OpenAPI spec.
func (callback *Callback) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
keys := make([]string, 0, callback.Len())
for key := range callback.Map() {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
v := callback.Value(key)
if err := v.Validate(ctx); err != nil {
return err
}
}
return nil
return validateExtensions(ctx, callback.Extensions)
}
// UnmarshalJSON sets Callbacks to a copy of data.
func (callbacks *Callbacks) UnmarshalJSON(data []byte) (err error) {
*callbacks, _, err = unmarshalStringMapP[CallbackRef](data)
return
}

View File

@ -2,20 +2,36 @@ package openapi3
import (
"context"
"encoding/json"
"fmt"
"regexp"
"sort"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
// Components is specified by OpenAPI/Swagger standard version 3.0.
type (
Callbacks map[string]*CallbackRef
Examples map[string]*ExampleRef
Headers map[string]*HeaderRef
Links map[string]*LinkRef
ParametersMap map[string]*ParameterRef
RequestBodies map[string]*RequestBodyRef
ResponseBodies map[string]*ResponseRef
Schemas map[string]*SchemaRef
SecuritySchemes map[string]*SecuritySchemeRef
)
// Components is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#components-object
type Components struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Schemas Schemas `json:"schemas,omitempty" yaml:"schemas,omitempty"`
Parameters ParametersMap `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
RequestBodies RequestBodies `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"`
Responses Responses `json:"responses,omitempty" yaml:"responses,omitempty"`
Responses ResponseBodies `json:"responses,omitempty" yaml:"responses,omitempty"`
SecuritySchemes SecuritySchemes `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"`
Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
Links Links `json:"links,omitempty" yaml:"links,omitempty"`
@ -26,82 +42,331 @@ func NewComponents() Components {
return Components{}
}
func (components *Components) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(components)
// MarshalJSON returns the JSON encoding of Components.
func (components Components) MarshalJSON() ([]byte, error) {
x, err := components.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Components.
func (components Components) MarshalYAML() (any, error) {
m := make(map[string]any, 9+len(components.Extensions))
for k, v := range components.Extensions {
m[k] = v
}
if x := components.Schemas; len(x) != 0 {
m["schemas"] = x
}
if x := components.Parameters; len(x) != 0 {
m["parameters"] = x
}
if x := components.Headers; len(x) != 0 {
m["headers"] = x
}
if x := components.RequestBodies; len(x) != 0 {
m["requestBodies"] = x
}
if x := components.Responses; len(x) != 0 {
m["responses"] = x
}
if x := components.SecuritySchemes; len(x) != 0 {
m["securitySchemes"] = x
}
if x := components.Examples; len(x) != 0 {
m["examples"] = x
}
if x := components.Links; len(x) != 0 {
m["links"] = x
}
if x := components.Callbacks; len(x) != 0 {
m["callbacks"] = x
}
return m, nil
}
// UnmarshalJSON sets Components to a copy of data.
func (components *Components) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, components)
type ComponentsBis Components
var x ComponentsBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
func (components *Components) Validate(ctx context.Context) (err error) {
for k, v := range components.Schemas {
if err = ValidateIdentifier(k); err != nil {
return
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "schemas")
delete(x.Extensions, "parameters")
delete(x.Extensions, "headers")
delete(x.Extensions, "requestBodies")
delete(x.Extensions, "responses")
delete(x.Extensions, "securitySchemes")
delete(x.Extensions, "examples")
delete(x.Extensions, "links")
delete(x.Extensions, "callbacks")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
if err = v.Validate(ctx); err != nil {
return
}
}
for k, v := range components.Parameters {
if err = ValidateIdentifier(k); err != nil {
return
}
if err = v.Validate(ctx); err != nil {
return
}
}
for k, v := range components.RequestBodies {
if err = ValidateIdentifier(k); err != nil {
return
}
if err = v.Validate(ctx); err != nil {
return
}
}
for k, v := range components.Responses {
if err = ValidateIdentifier(k); err != nil {
return
}
if err = v.Validate(ctx); err != nil {
return
}
}
for k, v := range components.Headers {
if err = ValidateIdentifier(k); err != nil {
return
}
if err = v.Validate(ctx); err != nil {
return
}
}
for k, v := range components.SecuritySchemes {
if err = ValidateIdentifier(k); err != nil {
return
}
if err = v.Validate(ctx); err != nil {
return
}
}
return
}
const identifierPattern = `^[a-zA-Z0-9._-]+$`
// IdentifierRegExp verifies whether Component object key matches 'identifierPattern' pattern, according to OapiAPI v3.x.0.
// Hovever, to be able supporting legacy OpenAPI v2.x, there is a need to customize above pattern in orde not to fail
// converted v2-v3 validation
var IdentifierRegExp = regexp.MustCompile(identifierPattern)
func ValidateIdentifier(value string) error {
if IdentifierRegExp.MatchString(value) {
*components = Components(x)
return nil
}
return fmt.Errorf("identifier %q is not supported by OpenAPIv3 standard (regexp: %q)", value, identifierPattern)
// Validate returns an error if Components does not comply with the OpenAPI spec.
func (components *Components) Validate(ctx context.Context, opts ...ValidationOption) (err error) {
ctx = WithValidationOptions(ctx, opts...)
schemas := make([]string, 0, len(components.Schemas))
for name := range components.Schemas {
schemas = append(schemas, name)
}
sort.Strings(schemas)
for _, k := range schemas {
v := components.Schemas[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("schema %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("schema %q: %w", k, err)
}
}
parameters := make([]string, 0, len(components.Parameters))
for name := range components.Parameters {
parameters = append(parameters, name)
}
sort.Strings(parameters)
for _, k := range parameters {
v := components.Parameters[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("parameter %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("parameter %q: %w", k, err)
}
}
requestBodies := make([]string, 0, len(components.RequestBodies))
for name := range components.RequestBodies {
requestBodies = append(requestBodies, name)
}
sort.Strings(requestBodies)
for _, k := range requestBodies {
v := components.RequestBodies[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("request body %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("request body %q: %w", k, err)
}
}
responses := make([]string, 0, len(components.Responses))
for name := range components.Responses {
responses = append(responses, name)
}
sort.Strings(responses)
for _, k := range responses {
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("response %q: %w", k, err)
}
v := components.Responses[k]
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("response %q: %w", k, err)
}
}
headers := make([]string, 0, len(components.Headers))
for name := range components.Headers {
headers = append(headers, name)
}
sort.Strings(headers)
for _, k := range headers {
v := components.Headers[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("header %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("header %q: %w", k, err)
}
}
securitySchemes := make([]string, 0, len(components.SecuritySchemes))
for name := range components.SecuritySchemes {
securitySchemes = append(securitySchemes, name)
}
sort.Strings(securitySchemes)
for _, k := range securitySchemes {
v := components.SecuritySchemes[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("security scheme %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("security scheme %q: %w", k, err)
}
}
examples := make([]string, 0, len(components.Examples))
for name := range components.Examples {
examples = append(examples, name)
}
sort.Strings(examples)
for _, k := range examples {
v := components.Examples[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("example %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("example %q: %w", k, err)
}
}
links := make([]string, 0, len(components.Links))
for name := range components.Links {
links = append(links, name)
}
sort.Strings(links)
for _, k := range links {
v := components.Links[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("link %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("link %q: %w", k, err)
}
}
callbacks := make([]string, 0, len(components.Callbacks))
for name := range components.Callbacks {
callbacks = append(callbacks, name)
}
sort.Strings(callbacks)
for _, k := range callbacks {
v := components.Callbacks[k]
if err = ValidateIdentifier(k); err != nil {
return fmt.Errorf("callback %q: %w", k, err)
}
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("callback %q: %w", k, err)
}
}
return validateExtensions(ctx, components.Extensions)
}
var _ jsonpointer.JSONPointable = (*Schemas)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m Schemas) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no schema %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*ParametersMap)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m ParametersMap) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no parameter %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*Headers)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m Headers) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no header %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m RequestBodies) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no request body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*ResponseRef)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m ResponseBodies) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no response body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*SecuritySchemes)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m SecuritySchemes) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no security scheme body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*Examples)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m Examples) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no example body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*Links)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m Links) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no link body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}
var _ jsonpointer.JSONPointable = (*Callbacks)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (m Callbacks) JSONLookup(token string) (any, error) {
if v, ok := m[token]; !ok || v == nil {
return nil, fmt.Errorf("no callback body %q", token)
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
return v.Value, nil
}
}

View File

@ -0,0 +1,71 @@
package openapi3
import (
"context"
"encoding/json"
)
// Contact is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#contact-object
type Contact struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
Email string `json:"email,omitempty" yaml:"email,omitempty"`
}
// MarshalJSON returns the JSON encoding of Contact.
func (contact Contact) MarshalJSON() ([]byte, error) {
x, err := contact.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Contact.
func (contact Contact) MarshalYAML() (any, error) {
m := make(map[string]any, 3+len(contact.Extensions))
for k, v := range contact.Extensions {
m[k] = v
}
if x := contact.Name; x != "" {
m["name"] = x
}
if x := contact.URL; x != "" {
m["url"] = x
}
if x := contact.Email; x != "" {
m["email"] = x
}
return m, nil
}
// UnmarshalJSON sets Contact to a copy of data.
func (contact *Contact) UnmarshalJSON(data []byte) error {
type ContactBis Contact
var x ContactBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "name")
delete(x.Extensions, "url")
delete(x.Extensions, "email")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*contact = Contact(x)
return nil
}
// Validate returns an error if Contact does not comply with the OpenAPI spec.
func (contact *Contact) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
return validateExtensions(ctx, contact.Extensions)
}

View File

@ -2,6 +2,7 @@ package openapi3
import (
"context"
"sort"
"strings"
)
@ -9,7 +10,7 @@ import (
type Content map[string]*MediaType
func NewContent() Content {
return make(map[string]*MediaType, 4)
return make(map[string]*MediaType)
}
func NewContentWithSchema(schema *Schema, consumes []string) Content {
@ -104,12 +105,26 @@ func (content Content) Get(mime string) *MediaType {
return content["*/*"]
}
func (value Content) Validate(ctx context.Context) error {
for _, v := range value {
// Validate MediaType
// Validate returns an error if Content does not comply with the OpenAPI spec.
func (content Content) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
keys := make([]string, 0, len(content))
for key := range content {
keys = append(keys, key)
}
sort.Strings(keys)
for _, k := range keys {
v := content[k]
if err := v.Validate(ctx); err != nil {
return err
}
}
return nil
}
// UnmarshalJSON sets Content to a copy of data.
func (content *Content) UnmarshalJSON(data []byte) (err error) {
*content, _, err = unmarshalStringMapP[MediaType](data)
return
}

View File

@ -2,25 +2,63 @@ package openapi3
import (
"context"
"github.com/getkin/kin-openapi/jsoninfo"
"encoding/json"
)
// Discriminator is specified by OpenAPI/Swagger standard version 3.0.
// Discriminator is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminator-object
type Discriminator struct {
ExtensionProps
PropertyName string `json:"propertyName" yaml:"propertyName"`
Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
PropertyName string `json:"propertyName" yaml:"propertyName"` // required
Mapping StringMap `json:"mapping,omitempty" yaml:"mapping,omitempty"`
}
func (value *Discriminator) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(value)
// MarshalJSON returns the JSON encoding of Discriminator.
func (discriminator Discriminator) MarshalJSON() ([]byte, error) {
x, err := discriminator.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
func (value *Discriminator) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, value)
// MarshalYAML returns the YAML encoding of Discriminator.
func (discriminator Discriminator) MarshalYAML() (any, error) {
m := make(map[string]any, 2+len(discriminator.Extensions))
for k, v := range discriminator.Extensions {
m[k] = v
}
m["propertyName"] = discriminator.PropertyName
if x := discriminator.Mapping; len(x) != 0 {
m["mapping"] = x
}
return m, nil
}
func (value *Discriminator) Validate(ctx context.Context) error {
// UnmarshalJSON sets Discriminator to a copy of data.
func (discriminator *Discriminator) UnmarshalJSON(data []byte) error {
type DiscriminatorBis Discriminator
var x DiscriminatorBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "propertyName")
delete(x.Extensions, "mapping")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*discriminator = Discriminator(x)
return nil
}
// Validate returns an error if Discriminator does not comply with the OpenAPI spec.
func (discriminator *Discriminator) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
return validateExtensions(ctx, discriminator.Extensions)
}

View File

@ -1,4 +1,4 @@
// Package openapi3 parses and writes OpenAPI 3 specification documents.
//
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md
package openapi3

View File

@ -2,14 +2,16 @@ package openapi3
import (
"context"
"encoding/json"
"fmt"
"github.com/getkin/kin-openapi/jsoninfo"
"sort"
)
// Encoding is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#encoding-object
type Encoding struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"`
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
@ -38,12 +40,59 @@ func (encoding *Encoding) WithHeaderRef(name string, ref *HeaderRef) *Encoding {
return encoding
}
func (encoding *Encoding) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(encoding)
// MarshalJSON returns the JSON encoding of Encoding.
func (encoding Encoding) MarshalJSON() ([]byte, error) {
x, err := encoding.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Encoding.
func (encoding Encoding) MarshalYAML() (any, error) {
m := make(map[string]any, 5+len(encoding.Extensions))
for k, v := range encoding.Extensions {
m[k] = v
}
if x := encoding.ContentType; x != "" {
m["contentType"] = x
}
if x := encoding.Headers; len(x) != 0 {
m["headers"] = x
}
if x := encoding.Style; x != "" {
m["style"] = x
}
if x := encoding.Explode; x != nil {
m["explode"] = x
}
if x := encoding.AllowReserved; x {
m["allowReserved"] = x
}
return m, nil
}
// UnmarshalJSON sets Encoding to a copy of data.
func (encoding *Encoding) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, encoding)
type EncodingBis Encoding
var x EncodingBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "contentType")
delete(x.Extensions, "headers")
delete(x.Extensions, "style")
delete(x.Extensions, "explode")
delete(x.Extensions, "allowReserved")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*encoding = Encoding(x)
return nil
}
// SerializationMethod returns a serialization method of request body.
@ -61,11 +110,21 @@ func (encoding *Encoding) SerializationMethod() *SerializationMethod {
return sm
}
func (value *Encoding) Validate(ctx context.Context) error {
if value == nil {
// Validate returns an error if Encoding does not comply with the OpenAPI spec.
func (encoding *Encoding) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if encoding == nil {
return nil
}
for k, v := range value.Headers {
headers := make([]string, 0, len(encoding.Headers))
for k := range encoding.Headers {
headers = append(headers, k)
}
sort.Strings(headers)
for _, k := range headers {
v := encoding.Headers[k]
if err := ValidateIdentifier(k); err != nil {
return nil
}
@ -75,7 +134,7 @@ func (value *Encoding) Validate(ctx context.Context) error {
}
// Validate a media types's serialization method.
sm := value.SerializationMethod()
sm := encoding.SerializationMethod()
switch {
case sm.Style == SerializationForm && sm.Explode,
sm.Style == SerializationForm && !sm.Explode,
@ -84,10 +143,9 @@ func (value *Encoding) Validate(ctx context.Context) error {
sm.Style == SerializationPipeDelimited && sm.Explode,
sm.Style == SerializationPipeDelimited && !sm.Explode,
sm.Style == SerializationDeepObject && sm.Explode:
// it is a valid
default:
return fmt.Errorf("serialization method with style=%q and explode=%v is not supported by media type", sm.Style, sm.Explode)
}
return nil
return validateExtensions(ctx, encoding.Extensions)
}

View File

@ -10,10 +10,16 @@ import (
type MultiError []error
func (me MultiError) Error() string {
return spliceErr(" | ", me)
}
func spliceErr(sep string, errs []error) string {
buff := &bytes.Buffer{}
for _, e := range me {
for i, e := range errs {
buff.WriteString(e.Error())
buff.WriteString(" | ")
if i != len(errs)-1 {
buff.WriteString(sep)
}
}
return buff.String()
}
@ -33,7 +39,7 @@ func (me MultiError) Is(target error) bool {
}
// As allows you to use `errors.As()` to set target to the first error within the multi error that matches the target type
func (me MultiError) As(target interface{}) bool {
func (me MultiError) As(target any) bool {
for _, e := range me {
if errors.As(e, target) {
return true
@ -41,3 +47,13 @@ func (me MultiError) As(target interface{}) bool {
}
return false
}
type multiErrorForOneOf MultiError
func (meo multiErrorForOneOf) Error() string {
return spliceErr(" Or ", meo)
}
func (meo multiErrorForOneOf) Unwrap() error {
return MultiError(meo)
}

View File

@ -0,0 +1,93 @@
package openapi3
import (
"context"
"encoding/json"
"errors"
)
// Example is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#example-object
type Example struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Value any `json:"value,omitempty" yaml:"value,omitempty"`
ExternalValue string `json:"externalValue,omitempty" yaml:"externalValue,omitempty"`
}
func NewExample(value any) *Example {
return &Example{Value: value}
}
// MarshalJSON returns the JSON encoding of Example.
func (example Example) MarshalJSON() ([]byte, error) {
x, err := example.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Example.
func (example Example) MarshalYAML() (any, error) {
m := make(map[string]any, 4+len(example.Extensions))
for k, v := range example.Extensions {
m[k] = v
}
if x := example.Summary; x != "" {
m["summary"] = x
}
if x := example.Description; x != "" {
m["description"] = x
}
if x := example.Value; x != nil {
m["value"] = x
}
if x := example.ExternalValue; x != "" {
m["externalValue"] = x
}
return m, nil
}
// UnmarshalJSON sets Example to a copy of data.
func (example *Example) UnmarshalJSON(data []byte) error {
type ExampleBis Example
var x ExampleBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "summary")
delete(x.Extensions, "description")
delete(x.Extensions, "value")
delete(x.Extensions, "externalValue")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*example = Example(x)
return nil
}
// Validate returns an error if Example does not comply with the OpenAPI spec.
func (example *Example) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if example.Value != nil && example.ExternalValue != "" {
return errors.New("value and externalValue are mutually exclusive")
}
if example.Value == nil && example.ExternalValue == "" {
return errors.New("no value or externalValue field")
}
return validateExtensions(ctx, example.Extensions)
}
// UnmarshalJSON sets Examples to a copy of data.
func (examples *Examples) UnmarshalJSON(data []byte) (err error) {
*examples, _, err = unmarshalStringMapP[ExampleRef](data)
return
}

View File

@ -0,0 +1,16 @@
package openapi3
import "context"
func validateExampleValue(ctx context.Context, input any, schema *Schema) error {
opts := make([]SchemaValidationOption, 0, 2)
if vo := getValidationOptions(ctx); vo.examplesValidationAsReq {
opts = append(opts, VisitAsRequest())
} else if vo.examplesValidationAsRes {
opts = append(opts, VisitAsResponse())
}
opts = append(opts, MultiErrors())
return schema.VisitJSON(input, opts...)
}

View File

@ -1,53 +0,0 @@
package openapi3
import (
"context"
"fmt"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
type Examples map[string]*ExampleRef
var _ jsonpointer.JSONPointable = (*Examples)(nil)
func (e Examples) JSONLookup(token string) (interface{}, error) {
ref, ok := e[token]
if ref == nil || !ok {
return nil, fmt.Errorf("object has no field %q", token)
}
if ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
// Example is specified by OpenAPI/Swagger 3.0 standard.
type Example struct {
ExtensionProps
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Value interface{} `json:"value,omitempty" yaml:"value,omitempty"`
ExternalValue string `json:"externalValue,omitempty" yaml:"externalValue,omitempty"`
}
func NewExample(value interface{}) *Example {
return &Example{
Value: value,
}
}
func (example *Example) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(example)
}
func (example *Example) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, example)
}
func (value *Example) Validate(ctx context.Context) error {
return nil // TODO
}

View File

@ -1,38 +1,32 @@
package openapi3
import (
"github.com/getkin/kin-openapi/jsoninfo"
"context"
"fmt"
"sort"
"strings"
)
// ExtensionProps provides support for OpenAPI extensions.
// It reads/writes all properties that begin with "x-".
type ExtensionProps struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
func validateExtensions(ctx context.Context, extensions map[string]any) error { // FIXME: newtype + Validate(...)
allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
var unknowns []string
for k := range extensions {
if strings.HasPrefix(k, "x-") {
continue
}
if allowed != nil {
if _, ok := allowed[k]; ok {
continue
}
}
unknowns = append(unknowns, k)
}
// Assert that the type implements the interface
var _ jsoninfo.StrictStruct = &ExtensionProps{}
// EncodeWith will be invoked by package "jsoninfo"
func (props *ExtensionProps) EncodeWith(encoder *jsoninfo.ObjectEncoder, value interface{}) error {
for k, v := range props.Extensions {
if err := encoder.EncodeExtension(k, v); err != nil {
return err
}
}
return encoder.EncodeStructFieldsAndExtensions(value)
if len(unknowns) != 0 {
sort.Strings(unknowns)
return fmt.Errorf("extra sibling fields: %+v", unknowns)
}
// DecodeWith will be invoked by package "jsoninfo"
func (props *ExtensionProps) DecodeWith(decoder *jsoninfo.ObjectDecoder, value interface{}) error {
if err := decoder.DecodeStructFieldsAndExtensions(value); err != nil {
return err
}
source := decoder.DecodeExtensionMap()
result := make(map[string]interface{}, len(source))
for k, v := range source {
result[k] = v
}
props.Extensions = result
return nil
}

View File

@ -1,21 +1,75 @@
package openapi3
import (
"github.com/getkin/kin-openapi/jsoninfo"
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
)
// ExternalDocs is specified by OpenAPI/Swagger standard version 3.0.
// ExternalDocs is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#external-documentation-object
type ExternalDocs struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Description string `json:"description,omitempty"`
URL string `json:"url,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
}
func (e *ExternalDocs) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(e)
// MarshalJSON returns the JSON encoding of ExternalDocs.
func (e ExternalDocs) MarshalJSON() ([]byte, error) {
x, err := e.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of ExternalDocs.
func (e ExternalDocs) MarshalYAML() (any, error) {
m := make(map[string]any, 2+len(e.Extensions))
for k, v := range e.Extensions {
m[k] = v
}
if x := e.Description; x != "" {
m["description"] = x
}
if x := e.URL; x != "" {
m["url"] = x
}
return m, nil
}
// UnmarshalJSON sets ExternalDocs to a copy of data.
func (e *ExternalDocs) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, e)
type ExternalDocsBis ExternalDocs
var x ExternalDocsBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "description")
delete(x.Extensions, "url")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*e = ExternalDocs(x)
return nil
}
// Validate returns an error if ExternalDocs does not comply with the OpenAPI spec.
func (e *ExternalDocs) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if e.URL == "" {
return errors.New("url is required")
}
if _, err := url.Parse(e.URL); err != nil {
return fmt.Errorf("url is incorrect: %w", err)
}
return validateExtensions(ctx, e.Extensions)
}

View File

@ -5,61 +5,63 @@ import (
"errors"
"fmt"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
type Headers map[string]*HeaderRef
var _ jsonpointer.JSONPointable = (*Headers)(nil)
func (h Headers) JSONLookup(token string) (interface{}, error) {
ref, ok := h[token]
if ref == nil || !ok {
return nil, fmt.Errorf("object has no field %q", token)
}
if ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
// Header is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#headerObject
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#header-object
type Header struct {
Parameter
}
var _ jsonpointer.JSONPointable = (*Header)(nil)
func (value *Header) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, value)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (header Header) JSONLookup(token string) (any, error) {
return header.Parameter.JSONLookup(token)
}
// MarshalJSON returns the JSON encoding of Header.
func (header Header) MarshalJSON() ([]byte, error) {
return header.Parameter.MarshalJSON()
}
// UnmarshalJSON sets Header to a copy of data.
func (header *Header) UnmarshalJSON(data []byte) error {
return header.Parameter.UnmarshalJSON(data)
}
// MarshalYAML returns the JSON encoding of Header.
func (header Header) MarshalYAML() (any, error) {
return header.Parameter, nil
}
// SerializationMethod returns a header's serialization method.
func (value *Header) SerializationMethod() (*SerializationMethod, error) {
style := value.Style
func (header *Header) SerializationMethod() (*SerializationMethod, error) {
style := header.Style
if style == "" {
style = SerializationSimple
}
explode := false
if value.Explode != nil {
explode = *value.Explode
if header.Explode != nil {
explode = *header.Explode
}
return &SerializationMethod{Style: style, Explode: explode}, nil
}
func (value *Header) Validate(ctx context.Context) error {
if value.Name != "" {
// Validate returns an error if Header does not comply with the OpenAPI spec.
func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if header.Name != "" {
return errors.New("header 'name' MUST NOT be specified, it is given in the corresponding headers map")
}
if value.In != "" {
if header.In != "" {
return errors.New("header 'in' MUST NOT be specified, it is implicitly in header")
}
// Validate a parameter's serialization method.
sm, err := value.SerializationMethod()
sm, err := header.SerializationMethod()
if err != nil {
return err
}
@ -67,62 +69,34 @@ func (value *Header) Validate(ctx context.Context) error {
sm.Style == SerializationSimple && !sm.Explode ||
sm.Style == SerializationSimple && sm.Explode; !smSupported {
e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a header parameter", sm.Style, sm.Explode)
return fmt.Errorf("header schema is invalid: %v", e)
return fmt.Errorf("header schema is invalid: %w", e)
}
if (value.Schema == nil) == (value.Content == nil) {
e := fmt.Errorf("parameter must contain exactly one of content and schema: %v", value)
return fmt.Errorf("header schema is invalid: %v", e)
if (header.Schema == nil) == (len(header.Content) == 0) {
e := fmt.Errorf("parameter must contain exactly one of content and schema: %v", header)
return fmt.Errorf("header schema is invalid: %w", e)
}
if schema := value.Schema; schema != nil {
if schema := header.Schema; schema != nil {
if err := schema.Validate(ctx); err != nil {
return fmt.Errorf("header schema is invalid: %v", err)
return fmt.Errorf("header schema is invalid: %w", err)
}
}
if content := value.Content; content != nil {
if content := header.Content; content != nil {
e := errors.New("parameter content must only contain one entry")
if len(content) > 1 {
return fmt.Errorf("header content is invalid: %w", e)
}
if err := content.Validate(ctx); err != nil {
return fmt.Errorf("header content is invalid: %v", err)
return fmt.Errorf("header content is invalid: %w", err)
}
}
return nil
}
func (value Header) JSONLookup(token string) (interface{}, error) {
switch token {
case "schema":
if value.Schema != nil {
if value.Schema.Ref != "" {
return &Ref{Ref: value.Schema.Ref}, nil
}
return value.Schema.Value, nil
}
case "name":
return value.Name, nil
case "in":
return value.In, nil
case "description":
return value.Description, nil
case "style":
return value.Style, nil
case "explode":
return value.Explode, nil
case "allowEmptyValue":
return value.AllowEmptyValue, nil
case "allowReserved":
return value.AllowReserved, nil
case "deprecated":
return value.Deprecated, nil
case "required":
return value.Required, nil
case "example":
return value.Example, nil
case "examples":
return value.Examples, nil
case "content":
return value.Content, nil
}
v, _, err := jsonpointer.GetForToken(value.ExtensionProps, token)
return v, err
// UnmarshalJSON sets Headers to a copy of data.
func (headers *Headers) UnmarshalJSON(data []byte) (err error) {
*headers, _, err = unmarshalStringMapP[HeaderRef](data)
return
}

View File

@ -0,0 +1,261 @@
package openapi3
import (
"fmt"
"net/url"
"path"
"reflect"
"regexp"
"sort"
"strings"
"github.com/go-openapi/jsonpointer"
)
const identifierChars = `a-zA-Z0-9._-`
// IdentifierRegExp verifies whether Component object key matches contains just 'identifierChars', according to OpenAPI v3.x.
// InvalidIdentifierCharRegExp matches all characters not contained in 'identifierChars'.
// However, to be able supporting legacy OpenAPI v2.x, there is a need to customize above pattern in order not to fail
// converted v2-v3 validation
var (
IdentifierRegExp = regexp.MustCompile(`^[` + identifierChars + `]+$`)
InvalidIdentifierCharRegExp = regexp.MustCompile(`[^` + identifierChars + `]`)
)
// ValidateIdentifier returns an error if the given component name does not match [IdentifierRegExp].
func ValidateIdentifier(value string) error {
if IdentifierRegExp.MatchString(value) {
return nil
}
return fmt.Errorf("identifier %q is not supported by OpenAPIv3 standard (charset: [%q])", value, identifierChars)
}
// Float64Ptr is a helper for defining OpenAPI schemas.
func Float64Ptr(value float64) *float64 {
return &value
}
// BoolPtr is a helper for defining OpenAPI schemas.
func BoolPtr(value bool) *bool {
return &value
}
// Int64Ptr is a helper for defining OpenAPI schemas.
func Int64Ptr(value int64) *int64 {
return &value
}
// Uint64Ptr is a helper for defining OpenAPI schemas.
func Uint64Ptr(value uint64) *uint64 {
return &value
}
// componentNames returns the map keys in a sorted slice.
func componentNames[E any](s map[string]E) []string {
out := make([]string, 0, len(s))
for i := range s {
out = append(out, i)
}
sort.Strings(out)
return out
}
// copyURI makes a copy of the pointer.
func copyURI(u *url.URL) *url.URL {
if u == nil {
return nil
}
c := *u // shallow-copy
return &c
}
type ComponentRef interface {
RefString() string
RefPath() *url.URL
CollectionName() string
}
// refersToSameDocument returns if the $ref refers to the same document.
//
// Documents in different directories will have distinct $ref values that resolve to
// the same document.
// For example, consider the 3 files:
//
// /records.yaml
// /root.yaml $ref: records.yaml
// /schema/other.yaml $ref: ../records.yaml
//
// The records.yaml reference in the 2 latter refers to the same document.
func refersToSameDocument(o1 ComponentRef, o2 ComponentRef) bool {
if o1 == nil || o2 == nil {
return false
}
r1 := o1.RefPath()
r2 := o2.RefPath()
if r1 == nil || r2 == nil {
return false
}
// refURL is relative to the working directory & base spec file.
return referenceURIMatch(r1, r2)
}
// referencesRootDocument returns if the $ref points to the root document of the OpenAPI spec.
//
// If the document has no location, perhaps loaded from data in memory, it always returns false.
func referencesRootDocument(doc *T, ref ComponentRef) bool {
if doc.url == nil || ref == nil || ref.RefPath() == nil {
return false
}
refURL := *ref.RefPath()
refURL.Fragment = ""
// Check referenced element was in the root document.
return referenceURIMatch(doc.url, &refURL)
}
func referenceURIMatch(u1 *url.URL, u2 *url.URL) bool {
s1, s2 := *u1, *u2
if s1.Scheme == "" {
s1.Scheme = "file"
}
if s2.Scheme == "" {
s2.Scheme = "file"
}
return s1.String() == s2.String()
}
// ReferencesComponentInRootDocument returns if the given component reference references
// the same document or element as another component reference in the root document's
// '#/components/<type>'. If it does, it returns the name of it in the form
// '#/components/<type>/NameXXX'
//
// Of course given a component from the root document will always match itself.
//
// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#relative-references-in-urls
//
// Example. Take the spec with directory structure:
//
// openapi.yaml
// schemas/
// ├─ record.yaml
// ├─ records.yaml
//
// In openapi.yaml we have:
//
// components:
// schemas:
// Record:
// $ref: schemas/record.yaml
//
// Case 1: records.yml references a component in the root document
//
// $ref: ../openapi.yaml#/components/schemas/Record
//
// This would return...
//
// #/components/schemas/Record
//
// Case 2: records.yml indirectly refers to the same schema
// as a schema the root document's '#/components/schemas'.
//
// $ref: ./record.yaml
//
// This would also return...
//
// #/components/schemas/Record
func ReferencesComponentInRootDocument(doc *T, ref ComponentRef) (string, bool) {
if ref == nil || ref.RefString() == "" {
return "", false
}
// Case 1:
// Something like: ../another-folder/document.json#/myElement
if isRemoteReference(ref.RefString()) && isRootComponentReference(ref.RefString(), ref.CollectionName()) {
// Determine if it is *this* root doc.
if referencesRootDocument(doc, ref) {
_, name, _ := strings.Cut(ref.RefString(), path.Join("#/components/", ref.CollectionName()))
return path.Join("#/components/", ref.CollectionName(), name), true
}
}
// If there are no schemas defined in the root document return early.
if doc.Components == nil {
return "", false
}
collection, _, err := jsonpointer.GetForToken(doc.Components, ref.CollectionName())
if err != nil {
panic(err) // unreachable
}
var components map[string]ComponentRef
componentRefType := reflect.TypeOf(new(ComponentRef)).Elem()
if t := reflect.TypeOf(collection); t.Kind() == reflect.Map &&
t.Key().Kind() == reflect.String &&
t.Elem().AssignableTo(componentRefType) {
v := reflect.ValueOf(collection)
components = make(map[string]ComponentRef, v.Len())
for _, key := range v.MapKeys() {
strct := v.MapIndex(key)
// Type assertion safe, already checked via reflection above.
components[key.Interface().(string)] = strct.Interface().(ComponentRef)
}
} else {
return "", false
}
// Case 2:
// Something like: ../openapi.yaml#/components/schemas/myElement
for name, s := range components {
// Must be a reference to a YAML file.
if !isWholeDocumentReference(s.RefString()) {
continue
}
// Is the schema a ref to the same resource.
if !refersToSameDocument(s, ref) {
continue
}
// Transform the remote ref to the equivalent schema in the root document.
return path.Join("#/components/", ref.CollectionName(), name), true
}
return "", false
}
// isElementReference takes a $ref value and checks if it references a specific element.
func isElementReference(ref string) bool {
return ref != "" && !isWholeDocumentReference(ref)
}
// isSchemaReference takes a $ref value and checks if it references a schema element.
func isRootComponentReference(ref string, compType string) bool {
return isElementReference(ref) && strings.Contains(ref, path.Join("#/components/", compType))
}
// isWholeDocumentReference takes a $ref value and checks if it is whole document reference.
func isWholeDocumentReference(ref string) bool {
return ref != "" && !strings.ContainsAny(ref, "#")
}
// isRemoteReference takes a $ref value and checks if it is remote reference.
func isRemoteReference(ref string) bool {
return ref != "" && !strings.HasPrefix(ref, "#") && !isURLReference(ref)
}
// isURLReference takes a $ref value and checks if it is URL reference.
func isURLReference(ref string) bool {
return strings.HasPrefix(ref, "http://") || strings.HasPrefix(ref, "https://") || strings.HasPrefix(ref, "//")
}

View File

@ -2,14 +2,16 @@ package openapi3
import (
"context"
"encoding/json"
"errors"
"github.com/getkin/kin-openapi/jsoninfo"
)
// Info is specified by OpenAPI/Swagger standard version 3.0.
// Info is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#info-object
type Info struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Title string `json:"title" yaml:"title"` // Required
Description string `json:"description,omitempty" yaml:"description,omitempty"`
TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"`
@ -18,76 +20,86 @@ type Info struct {
Version string `json:"version" yaml:"version"` // Required
}
func (value *Info) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(value)
// MarshalJSON returns the JSON encoding of Info.
func (info Info) MarshalJSON() ([]byte, error) {
x, err := info.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
func (value *Info) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, value)
// MarshalYAML returns the YAML encoding of Info.
func (info *Info) MarshalYAML() (any, error) {
if info == nil {
return nil, nil
}
m := make(map[string]any, 6+len(info.Extensions))
for k, v := range info.Extensions {
m[k] = v
}
m["title"] = info.Title
if x := info.Description; x != "" {
m["description"] = x
}
if x := info.TermsOfService; x != "" {
m["termsOfService"] = x
}
if x := info.Contact; x != nil {
m["contact"] = x
}
if x := info.License; x != nil {
m["license"] = x
}
m["version"] = info.Version
return m, nil
}
func (value *Info) Validate(ctx context.Context) error {
if contact := value.Contact; contact != nil {
// UnmarshalJSON sets Info to a copy of data.
func (info *Info) UnmarshalJSON(data []byte) error {
type InfoBis Info
var x InfoBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "title")
delete(x.Extensions, "description")
delete(x.Extensions, "termsOfService")
delete(x.Extensions, "contact")
delete(x.Extensions, "license")
delete(x.Extensions, "version")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*info = Info(x)
return nil
}
// Validate returns an error if Info does not comply with the OpenAPI spec.
func (info *Info) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if contact := info.Contact; contact != nil {
if err := contact.Validate(ctx); err != nil {
return err
}
}
if license := value.License; license != nil {
if license := info.License; license != nil {
if err := license.Validate(ctx); err != nil {
return err
}
}
if value.Version == "" {
if info.Version == "" {
return errors.New("value of version must be a non-empty string")
}
if value.Title == "" {
if info.Title == "" {
return errors.New("value of title must be a non-empty string")
}
return nil
}
// Contact is specified by OpenAPI/Swagger standard version 3.0.
type Contact struct {
ExtensionProps
Name string `json:"name,omitempty" yaml:"name,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
Email string `json:"email,omitempty" yaml:"email,omitempty"`
}
func (value *Contact) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(value)
}
func (value *Contact) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, value)
}
func (value *Contact) Validate(ctx context.Context) error {
return nil
}
// License is specified by OpenAPI/Swagger standard version 3.0.
type License struct {
ExtensionProps
Name string `json:"name" yaml:"name"` // Required
URL string `json:"url,omitempty" yaml:"url,omitempty"`
}
func (value *License) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(value)
}
func (value *License) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, value)
}
func (value *License) Validate(ctx context.Context) error {
if value.Name == "" {
return errors.New("value of license name must be a non-empty string")
}
return nil
return validateExtensions(ctx, info.Extensions)
}

View File

@ -0,0 +1,546 @@
package openapi3
import (
"context"
"path"
"strings"
)
// RefNameResolver maps a component to an name that is used as it's internalized name.
//
// The function should avoid name collisions (i.e. be a injective mapping).
// It must only contain characters valid for fixed field names: [IdentifierRegExp].
type RefNameResolver func(*T, ComponentRef) string
// DefaultRefResolver is a default implementation of refNameResolver for the
// InternalizeRefs function.
//
// The external reference is internalized to (hopefully) a unique name. If
// the external reference matches (by path) to another reference in the root
// document then the name of that component is used.
//
// The transformation involves:
// - Cutting the "#/components/<type>" part.
// - Cutting the file extensions (.yaml/.json) from documents.
// - Trimming the common directory with the root spec.
// - Replace invalid characters with with underscores.
//
// This is an injective mapping over a "reasonable" amount of the possible openapi
// spec domain space but is not perfect. There might be edge cases.
func DefaultRefNameResolver(doc *T, ref ComponentRef) string {
if ref.RefString() == "" || ref.RefPath() == nil {
panic("unable to resolve reference to name")
}
name := ref.RefPath()
// If refering to a component in the root spec, no need to internalize just use
// the existing component.
// XXX(percivalalb): since this function call is iterating over components behind the
// scenes during an internalization call it actually starts interating over
// new & replaced internalized components. This might caused some edge cases,
// haven't found one yet but this might need to actually be used on a frozen copy
// of doc.
if nameInRoot, found := ReferencesComponentInRootDocument(doc, ref); found {
nameInRoot = strings.TrimPrefix(nameInRoot, "#")
rootCompURI := copyURI(doc.url)
rootCompURI.Fragment = nameInRoot
name = rootCompURI
}
filePath, componentPath := name.Path, name.Fragment
// Cut out the "#/components/<type>" to make the names shorter.
// XXX(percivalalb): This might cause collisions but is worth the brevity.
if b, a, ok := strings.Cut(componentPath, path.Join("components", ref.CollectionName(), "")); ok {
componentPath = path.Join(b, a)
}
if filePath != "" {
// If the path is the same as the root doc, just remove.
if doc.url != nil && filePath == doc.url.Path {
filePath = ""
}
// Remove the path extentions to make this JSON/YAML agnostic.
for ext := path.Ext(filePath); len(ext) > 0; ext = path.Ext(filePath) {
filePath = strings.TrimSuffix(filePath, ext)
}
// Trim the common prefix with the root doc path.
if doc.url != nil {
commonDir := path.Dir(doc.url.Path)
for {
if commonDir == "." { // no common prefix
break
}
if p, found := cutDirectories(filePath, commonDir); found {
filePath = p
break
}
commonDir = path.Dir(commonDir)
}
}
}
var internalizedName string
// Trim .'s & slashes from start e.g. otherwise ./doc.yaml would end up as __doc
if filePath != "" {
internalizedName = strings.TrimLeft(filePath, "./")
}
if componentPath != "" {
if internalizedName != "" {
internalizedName += "_"
}
internalizedName += strings.TrimLeft(componentPath, "./")
}
// Replace invalid characters in component fixed field names.
internalizedName = InvalidIdentifierCharRegExp.ReplaceAllString(internalizedName, "_")
return internalizedName
}
// cutDirectories removes the given directories from the start of the path if
// the path is a child.
func cutDirectories(p, dirs string) (string, bool) {
if dirs == "" || p == "" {
return p, false
}
p = strings.TrimRight(p, "/")
dirs = strings.TrimRight(dirs, "/")
var sb strings.Builder
sb.Grow(len(ParameterInHeader))
for _, segments := range strings.Split(p, "/") {
sb.WriteString(segments)
if sb.String() == p {
return strings.TrimPrefix(p, dirs), true
}
sb.WriteRune('/')
}
return p, false
}
func isExternalRef(ref string, parentIsExternal bool) bool {
return ref != "" && (!strings.HasPrefix(ref, "#/components/") || parentIsExternal)
}
func (doc *T) addSchemaToSpec(s *SchemaRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if s == nil || !isExternalRef(s.Ref, parentIsExternal) {
return false
}
name := refNameResolver(doc, s)
if doc.Components != nil {
if _, ok := doc.Components.Schemas[name]; ok {
s.Ref = "#/components/schemas/" + name
return true
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Schemas == nil {
doc.Components.Schemas = make(Schemas)
}
doc.Components.Schemas[name] = s.Value.NewRef()
s.Ref = "#/components/schemas/" + name
return true
}
func (doc *T) addParameterToSpec(p *ParameterRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if p == nil || !isExternalRef(p.Ref, parentIsExternal) {
return false
}
name := refNameResolver(doc, p)
if doc.Components != nil {
if _, ok := doc.Components.Parameters[name]; ok {
p.Ref = "#/components/parameters/" + name
return true
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Parameters == nil {
doc.Components.Parameters = make(ParametersMap)
}
doc.Components.Parameters[name] = &ParameterRef{Value: p.Value}
p.Ref = "#/components/parameters/" + name
return true
}
func (doc *T) addHeaderToSpec(h *HeaderRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if h == nil || !isExternalRef(h.Ref, parentIsExternal) {
return false
}
name := refNameResolver(doc, h)
if doc.Components != nil {
if _, ok := doc.Components.Headers[name]; ok {
h.Ref = "#/components/headers/" + name
return true
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Headers == nil {
doc.Components.Headers = make(Headers)
}
doc.Components.Headers[name] = &HeaderRef{Value: h.Value}
h.Ref = "#/components/headers/" + name
return true
}
func (doc *T) addRequestBodyToSpec(r *RequestBodyRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if r == nil || !isExternalRef(r.Ref, parentIsExternal) {
return false
}
name := refNameResolver(doc, r)
if doc.Components != nil {
if _, ok := doc.Components.RequestBodies[name]; ok {
r.Ref = "#/components/requestBodies/" + name
return true
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.RequestBodies == nil {
doc.Components.RequestBodies = make(RequestBodies)
}
doc.Components.RequestBodies[name] = &RequestBodyRef{Value: r.Value}
r.Ref = "#/components/requestBodies/" + name
return true
}
func (doc *T) addResponseToSpec(r *ResponseRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if r == nil || !isExternalRef(r.Ref, parentIsExternal) {
return false
}
name := refNameResolver(doc, r)
if doc.Components != nil {
if _, ok := doc.Components.Responses[name]; ok {
r.Ref = "#/components/responses/" + name
return true
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Responses == nil {
doc.Components.Responses = make(ResponseBodies)
}
doc.Components.Responses[name] = &ResponseRef{Value: r.Value}
r.Ref = "#/components/responses/" + name
return true
}
func (doc *T) addSecuritySchemeToSpec(ss *SecuritySchemeRef, refNameResolver RefNameResolver, parentIsExternal bool) {
if ss == nil || !isExternalRef(ss.Ref, parentIsExternal) {
return
}
name := refNameResolver(doc, ss)
if doc.Components != nil {
if _, ok := doc.Components.SecuritySchemes[name]; ok {
ss.Ref = "#/components/securitySchemes/" + name
return
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.SecuritySchemes == nil {
doc.Components.SecuritySchemes = make(SecuritySchemes)
}
doc.Components.SecuritySchemes[name] = &SecuritySchemeRef{Value: ss.Value}
ss.Ref = "#/components/securitySchemes/" + name
}
func (doc *T) addExampleToSpec(e *ExampleRef, refNameResolver RefNameResolver, parentIsExternal bool) {
if e == nil || !isExternalRef(e.Ref, parentIsExternal) {
return
}
name := refNameResolver(doc, e)
if doc.Components != nil {
if _, ok := doc.Components.Examples[name]; ok {
e.Ref = "#/components/examples/" + name
return
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Examples == nil {
doc.Components.Examples = make(Examples)
}
doc.Components.Examples[name] = &ExampleRef{Value: e.Value}
e.Ref = "#/components/examples/" + name
}
func (doc *T) addLinkToSpec(l *LinkRef, refNameResolver RefNameResolver, parentIsExternal bool) {
if l == nil || !isExternalRef(l.Ref, parentIsExternal) {
return
}
name := refNameResolver(doc, l)
if doc.Components != nil {
if _, ok := doc.Components.Links[name]; ok {
l.Ref = "#/components/links/" + name
return
}
}
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Links == nil {
doc.Components.Links = make(Links)
}
doc.Components.Links[name] = &LinkRef{Value: l.Value}
l.Ref = "#/components/links/" + name
}
func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
if c == nil || !isExternalRef(c.Ref, parentIsExternal) {
return false
}
name := refNameResolver(doc, c)
if doc.Components == nil {
doc.Components = &Components{}
}
if doc.Components.Callbacks == nil {
doc.Components.Callbacks = make(Callbacks)
}
c.Ref = "#/components/callbacks/" + name
doc.Components.Callbacks[name] = &CallbackRef{Value: c.Value}
return true
}
func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver, parentIsExternal bool) {
if s == nil || doc.isVisitedSchema(s) {
return
}
for _, list := range []SchemaRefs{s.AllOf, s.AnyOf, s.OneOf} {
for _, s2 := range list {
isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal)
if s2 != nil {
doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal)
}
}
}
for _, name := range componentNames(s.Properties) {
s2 := s.Properties[name]
isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal)
if s2 != nil {
doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal)
}
}
for _, ref := range []*SchemaRef{s.Not, s.AdditionalProperties.Schema, s.Items} {
isExternal := doc.addSchemaToSpec(ref, refNameResolver, parentIsExternal)
if ref != nil {
doc.derefSchema(ref.Value, refNameResolver, isExternal || parentIsExternal)
}
}
}
func (doc *T) derefHeaders(hs Headers, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, name := range componentNames(hs) {
h := hs[name]
isExternal := doc.addHeaderToSpec(h, refNameResolver, parentIsExternal)
if doc.isVisitedHeader(h.Value) {
continue
}
doc.derefParameter(h.Value.Parameter, refNameResolver, parentIsExternal || isExternal)
}
}
func (doc *T) derefExamples(es Examples, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, name := range componentNames(es) {
e := es[name]
doc.addExampleToSpec(e, refNameResolver, parentIsExternal)
}
}
func (doc *T) derefContent(c Content, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, name := range componentNames(c) {
mediatype := c[name]
isExternal := doc.addSchemaToSpec(mediatype.Schema, refNameResolver, parentIsExternal)
if mediatype.Schema != nil {
doc.derefSchema(mediatype.Schema.Value, refNameResolver, isExternal || parentIsExternal)
}
doc.derefExamples(mediatype.Examples, refNameResolver, parentIsExternal)
for _, name := range componentNames(mediatype.Encoding) {
e := mediatype.Encoding[name]
doc.derefHeaders(e.Headers, refNameResolver, parentIsExternal)
}
}
}
func (doc *T) derefLinks(ls Links, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, name := range componentNames(ls) {
l := ls[name]
doc.addLinkToSpec(l, refNameResolver, parentIsExternal)
}
}
func (doc *T) derefResponse(r *ResponseRef, refNameResolver RefNameResolver, parentIsExternal bool) {
isExternal := doc.addResponseToSpec(r, refNameResolver, parentIsExternal)
if v := r.Value; v != nil {
doc.derefHeaders(v.Headers, refNameResolver, isExternal || parentIsExternal)
doc.derefContent(v.Content, refNameResolver, isExternal || parentIsExternal)
doc.derefLinks(v.Links, refNameResolver, isExternal || parentIsExternal)
}
}
func (doc *T) derefResponses(rs *Responses, refNameResolver RefNameResolver, parentIsExternal bool) {
doc.derefResponseBodies(rs.Map(), refNameResolver, parentIsExternal)
}
func (doc *T) derefResponseBodies(es ResponseBodies, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, name := range componentNames(es) {
e := es[name]
doc.derefResponse(e, refNameResolver, parentIsExternal)
}
}
func (doc *T) derefParameter(p Parameter, refNameResolver RefNameResolver, parentIsExternal bool) {
isExternal := doc.addSchemaToSpec(p.Schema, refNameResolver, parentIsExternal)
doc.derefContent(p.Content, refNameResolver, parentIsExternal)
if p.Schema != nil {
doc.derefSchema(p.Schema.Value, refNameResolver, isExternal || parentIsExternal)
}
}
func (doc *T) derefRequestBody(r RequestBody, refNameResolver RefNameResolver, parentIsExternal bool) {
doc.derefContent(r.Content, refNameResolver, parentIsExternal)
}
func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameResolver, parentIsExternal bool) {
for _, name := range componentNames(paths) {
ops := paths[name]
pathIsExternal := isExternalRef(ops.Ref, parentIsExternal)
// inline full operations
ops.Ref = ""
for _, param := range ops.Parameters {
isExternal := doc.addParameterToSpec(param, refNameResolver, pathIsExternal)
if param.Value != nil {
doc.derefParameter(*param.Value, refNameResolver, pathIsExternal || isExternal)
}
}
opsWithMethod := ops.Operations()
for _, name := range componentNames(opsWithMethod) {
op := opsWithMethod[name]
isExternal := doc.addRequestBodyToSpec(op.RequestBody, refNameResolver, pathIsExternal)
if op.RequestBody != nil && op.RequestBody.Value != nil {
doc.derefRequestBody(*op.RequestBody.Value, refNameResolver, pathIsExternal || isExternal)
}
for _, name := range componentNames(op.Callbacks) {
cb := op.Callbacks[name]
isExternal := doc.addCallbackToSpec(cb, refNameResolver, pathIsExternal)
if cb.Value != nil {
cbValue := (*cb.Value).Map()
doc.derefPaths(cbValue, refNameResolver, pathIsExternal || isExternal)
}
}
doc.derefResponses(op.Responses, refNameResolver, pathIsExternal)
for _, param := range op.Parameters {
isExternal := doc.addParameterToSpec(param, refNameResolver, pathIsExternal)
if param.Value != nil {
doc.derefParameter(*param.Value, refNameResolver, pathIsExternal || isExternal)
}
}
}
}
}
// InternalizeRefs removes all references to external files from the spec and moves them
// to the components section.
//
// refNameResolver takes in references to returns a name to store the reference under locally.
// It MUST return a unique name for each reference type.
// A default implementation is provided that will suffice for most use cases. See the function
// documentation for more details.
//
// Example:
//
// doc.InternalizeRefs(context.Background(), nil)
func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(*T, ComponentRef) string) {
doc.resetVisited()
if refNameResolver == nil {
refNameResolver = DefaultRefNameResolver
}
if components := doc.Components; components != nil {
for _, name := range componentNames(components.Schemas) {
schema := components.Schemas[name]
isExternal := doc.addSchemaToSpec(schema, refNameResolver, false)
if schema != nil {
schema.Ref = "" // always dereference the top level
doc.derefSchema(schema.Value, refNameResolver, isExternal)
}
}
for _, name := range componentNames(components.Parameters) {
p := components.Parameters[name]
isExternal := doc.addParameterToSpec(p, refNameResolver, false)
if p != nil && p.Value != nil {
p.Ref = "" // always dereference the top level
doc.derefParameter(*p.Value, refNameResolver, isExternal)
}
}
doc.derefHeaders(components.Headers, refNameResolver, false)
for _, name := range componentNames(components.RequestBodies) {
req := components.RequestBodies[name]
isExternal := doc.addRequestBodyToSpec(req, refNameResolver, false)
if req != nil && req.Value != nil {
req.Ref = "" // always dereference the top level
doc.derefRequestBody(*req.Value, refNameResolver, isExternal)
}
}
doc.derefResponseBodies(components.Responses, refNameResolver, false)
for _, name := range componentNames(components.SecuritySchemes) {
ss := components.SecuritySchemes[name]
doc.addSecuritySchemeToSpec(ss, refNameResolver, false)
}
doc.derefExamples(components.Examples, refNameResolver, false)
doc.derefLinks(components.Links, refNameResolver, false)
for _, name := range componentNames(components.Callbacks) {
cb := components.Callbacks[name]
isExternal := doc.addCallbackToSpec(cb, refNameResolver, false)
if cb != nil && cb.Value != nil {
cb.Ref = "" // always dereference the top level
cbValue := (*cb.Value).Map()
doc.derefPaths(cbValue, refNameResolver, isExternal)
}
}
}
doc.derefPaths(doc.Paths.Map(), refNameResolver, false)
}

View File

@ -0,0 +1,68 @@
package openapi3
import (
"context"
"encoding/json"
"errors"
)
// License is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#license-object
type License struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Name string `json:"name" yaml:"name"` // Required
URL string `json:"url,omitempty" yaml:"url,omitempty"`
}
// MarshalJSON returns the JSON encoding of License.
func (license License) MarshalJSON() ([]byte, error) {
x, err := license.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of License.
func (license License) MarshalYAML() (any, error) {
m := make(map[string]any, 2+len(license.Extensions))
for k, v := range license.Extensions {
m[k] = v
}
m["name"] = license.Name
if x := license.URL; x != "" {
m["url"] = x
}
return m, nil
}
// UnmarshalJSON sets License to a copy of data.
func (license *License) UnmarshalJSON(data []byte) error {
type LicenseBis License
var x LicenseBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "name")
delete(x.Extensions, "url")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*license = License(x)
return nil
}
// Validate returns an error if License does not comply with the OpenAPI spec.
func (license *License) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if license.Name == "" {
return errors.New("value of license name must be a non-empty string")
}
return validateExtensions(ctx, license.Extensions)
}

View File

@ -2,54 +2,102 @@ package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
type Links map[string]*LinkRef
func (l Links) JSONLookup(token string) (interface{}, error) {
ref, ok := l[token]
if ok == false {
return nil, fmt.Errorf("object has no field %q", token)
}
if ref != nil && ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
var _ jsonpointer.JSONPointable = (*Links)(nil)
// Link is specified by OpenAPI/Swagger standard version 3.0.
// Link is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#link-object
type Link struct {
ExtensionProps
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"`
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Parameters map[string]any `json:"parameters,omitempty" yaml:"parameters,omitempty"`
Server *Server `json:"server,omitempty" yaml:"server,omitempty"`
RequestBody interface{} `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
RequestBody any `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
}
func (value *Link) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(value)
// MarshalJSON returns the JSON encoding of Link.
func (link Link) MarshalJSON() ([]byte, error) {
x, err := link.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
func (value *Link) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, value)
// MarshalYAML returns the YAML encoding of Link.
func (link Link) MarshalYAML() (any, error) {
m := make(map[string]any, 6+len(link.Extensions))
for k, v := range link.Extensions {
m[k] = v
}
func (value *Link) Validate(ctx context.Context) error {
if value.OperationID == "" && value.OperationRef == "" {
return errors.New("missing operationId or operationRef on link")
if x := link.OperationRef; x != "" {
m["operationRef"] = x
}
if value.OperationID != "" && value.OperationRef != "" {
return fmt.Errorf("operationId %q and operationRef %q are mutually exclusive", value.OperationID, value.OperationRef)
if x := link.OperationID; x != "" {
m["operationId"] = x
}
if x := link.Description; x != "" {
m["description"] = x
}
if x := link.Parameters; len(x) != 0 {
m["parameters"] = x
}
if x := link.Server; x != nil {
m["server"] = x
}
if x := link.RequestBody; x != nil {
m["requestBody"] = x
}
return m, nil
}
// UnmarshalJSON sets Link to a copy of data.
func (link *Link) UnmarshalJSON(data []byte) error {
type LinkBis Link
var x LinkBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "operationRef")
delete(x.Extensions, "operationId")
delete(x.Extensions, "description")
delete(x.Extensions, "parameters")
delete(x.Extensions, "server")
delete(x.Extensions, "requestBody")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*link = Link(x)
return nil
}
// Validate returns an error if Link does not comply with the OpenAPI spec.
func (link *Link) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if link.OperationID == "" && link.OperationRef == "" {
return errors.New("missing operationId or operationRef on link")
}
if link.OperationID != "" && link.OperationRef != "" {
return fmt.Errorf("operationId %q and operationRef %q are mutually exclusive", link.OperationID, link.OperationRef)
}
return validateExtensions(ctx, link.Extensions)
}
// UnmarshalJSON sets Links to a copy of data.
func (links *Links) UnmarshalJSON(data []byte) (err error) {
*links, _, err = unmarshalStringMapP[LinkRef](data)
return
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,116 @@
package openapi3
import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"sync"
)
// ReadFromURIFunc defines a function which reads the contents of a resource
// located at a URI.
type ReadFromURIFunc func(loader *Loader, url *url.URL) ([]byte, error)
var uriMu = &sync.RWMutex{}
// ErrURINotSupported indicates the ReadFromURIFunc does not know how to handle a
// given URI.
var ErrURINotSupported = errors.New("unsupported URI")
// ReadFromURIs returns a ReadFromURIFunc which tries to read a URI using the
// given reader functions, in the same order. If a reader function does not
// support the URI and returns ErrURINotSupported, the next function is checked
// until a match is found, or the URI is not supported by any.
func ReadFromURIs(readers ...ReadFromURIFunc) ReadFromURIFunc {
return func(loader *Loader, url *url.URL) ([]byte, error) {
for i := range readers {
buf, err := readers[i](loader, url)
if err == ErrURINotSupported {
continue
} else if err != nil {
return nil, err
}
return buf, nil
}
return nil, ErrURINotSupported
}
}
// DefaultReadFromURI returns a caching ReadFromURIFunc which can read remote
// HTTP URIs and local file URIs.
var DefaultReadFromURI = URIMapCache(ReadFromURIs(ReadFromHTTP(http.DefaultClient), ReadFromFile))
// ReadFromHTTP returns a ReadFromURIFunc which uses the given http.Client to
// read the contents from a remote HTTP URI. This client may be customized to
// implement timeouts, RFC 7234 caching, etc.
func ReadFromHTTP(cl *http.Client) ReadFromURIFunc {
return func(loader *Loader, location *url.URL) ([]byte, error) {
if location.Scheme == "" || location.Host == "" {
return nil, ErrURINotSupported
}
req, err := http.NewRequest("GET", location.String(), nil)
if err != nil {
return nil, err
}
resp, err := cl.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode > 399 {
return nil, fmt.Errorf("error loading %q: request returned status code %d", location.String(), resp.StatusCode)
}
return io.ReadAll(resp.Body)
}
}
func is_file(location *url.URL) bool {
return location.Path != "" &&
location.Host == "" &&
(location.Scheme == "" || location.Scheme == "file")
}
// ReadFromFile is a ReadFromURIFunc which reads local file URIs.
func ReadFromFile(loader *Loader, location *url.URL) ([]byte, error) {
if !is_file(location) {
return nil, ErrURINotSupported
}
return os.ReadFile(filepath.FromSlash(location.Path))
}
// URIMapCache returns a ReadFromURIFunc that caches the contents read from URI
// locations in a simple map. This cache implementation is suitable for
// short-lived processes such as command-line tools which process OpenAPI
// documents.
func URIMapCache(reader ReadFromURIFunc) ReadFromURIFunc {
cache := map[string][]byte{}
return func(loader *Loader, location *url.URL) (buf []byte, err error) {
if location.Scheme == "" || location.Scheme == "file" {
if !filepath.IsAbs(location.Path) {
// Do not cache relative file paths; this can cause trouble if
// the current working directory changes when processing
// multiple top-level documents.
return reader(loader, location)
}
}
uri := location.String()
var ok bool
uriMu.RLock()
if buf, ok = cache[uri]; ok {
uriMu.RUnlock()
return
}
uriMu.RUnlock()
if buf, err = reader(loader, location); err != nil {
return
}
uriMu.Lock()
defer uriMu.Unlock()
cache[uri] = buf
return
}
}

View File

@ -0,0 +1,435 @@
package openapi3
import (
"encoding/json"
"sort"
"strings"
"github.com/go-openapi/jsonpointer"
)
// NewResponsesWithCapacity builds a responses object of the given capacity.
func NewResponsesWithCapacity(cap int) *Responses {
if cap == 0 {
return &Responses{m: make(map[string]*ResponseRef)}
}
return &Responses{m: make(map[string]*ResponseRef, cap)}
}
// Value returns the responses for key or nil
func (responses *Responses) Value(key string) *ResponseRef {
if responses.Len() == 0 {
return nil
}
return responses.m[key]
}
// Set adds or replaces key 'key' of 'responses' with 'value'.
// Note: 'responses' MUST be non-nil
func (responses *Responses) Set(key string, value *ResponseRef) {
if responses.m == nil {
responses.m = make(map[string]*ResponseRef)
}
responses.m[key] = value
}
// Len returns the amount of keys in responses excluding responses.Extensions.
func (responses *Responses) Len() int {
if responses == nil || responses.m == nil {
return 0
}
return len(responses.m)
}
// Delete removes the entry associated with key 'key' from 'responses'.
func (responses *Responses) Delete(key string) {
if responses != nil && responses.m != nil {
delete(responses.m, key)
}
}
// Map returns responses as a 'map'.
// Note: iteration on Go maps is not ordered.
func (responses *Responses) Map() (m map[string]*ResponseRef) {
if responses == nil || len(responses.m) == 0 {
return make(map[string]*ResponseRef)
}
m = make(map[string]*ResponseRef, len(responses.m))
for k, v := range responses.m {
m[k] = v
}
return
}
var _ jsonpointer.JSONPointable = (*Responses)(nil)
// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable
func (responses Responses) JSONLookup(token string) (any, error) {
if v := responses.Value(token); v == nil {
vv, _, err := jsonpointer.GetForToken(responses.Extensions, token)
return vv, err
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
var vv *Response = v.Value
return vv, nil
}
}
// MarshalYAML returns the YAML encoding of Responses.
func (responses *Responses) MarshalYAML() (any, error) {
if responses == nil {
return nil, nil
}
m := make(map[string]any, responses.Len()+len(responses.Extensions))
for k, v := range responses.Extensions {
m[k] = v
}
for k, v := range responses.Map() {
m[k] = v
}
return m, nil
}
// MarshalJSON returns the JSON encoding of Responses.
func (responses *Responses) MarshalJSON() ([]byte, error) {
responsesYaml, err := responses.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(responsesYaml)
}
// UnmarshalJSON sets Responses to a copy of data.
func (responses *Responses) UnmarshalJSON(data []byte) (err error) {
var m map[string]any
if err = json.Unmarshal(data, &m); err != nil {
return
}
ks := make([]string, 0, len(m))
for k := range m {
ks = append(ks, k)
}
sort.Strings(ks)
x := Responses{
Extensions: make(map[string]any),
m: make(map[string]*ResponseRef, len(m)),
}
for _, k := range ks {
v := m[k]
if strings.HasPrefix(k, "x-") {
x.Extensions[k] = v
continue
}
if k == originKey {
var data []byte
if data, err = json.Marshal(v); err != nil {
return
}
if err = json.Unmarshal(data, &x.Origin); err != nil {
return
}
continue
}
var data []byte
if data, err = json.Marshal(v); err != nil {
return
}
var vv ResponseRef
if err = vv.UnmarshalJSON(data); err != nil {
return
}
x.m[k] = &vv
}
*responses = x
return
}
// NewCallbackWithCapacity builds a callback object of the given capacity.
func NewCallbackWithCapacity(cap int) *Callback {
if cap == 0 {
return &Callback{m: make(map[string]*PathItem)}
}
return &Callback{m: make(map[string]*PathItem, cap)}
}
// Value returns the callback for key or nil
func (callback *Callback) Value(key string) *PathItem {
if callback.Len() == 0 {
return nil
}
return callback.m[key]
}
// Set adds or replaces key 'key' of 'callback' with 'value'.
// Note: 'callback' MUST be non-nil
func (callback *Callback) Set(key string, value *PathItem) {
if callback.m == nil {
callback.m = make(map[string]*PathItem)
}
callback.m[key] = value
}
// Len returns the amount of keys in callback excluding callback.Extensions.
func (callback *Callback) Len() int {
if callback == nil || callback.m == nil {
return 0
}
return len(callback.m)
}
// Delete removes the entry associated with key 'key' from 'callback'.
func (callback *Callback) Delete(key string) {
if callback != nil && callback.m != nil {
delete(callback.m, key)
}
}
// Map returns callback as a 'map'.
// Note: iteration on Go maps is not ordered.
func (callback *Callback) Map() (m map[string]*PathItem) {
if callback == nil || len(callback.m) == 0 {
return make(map[string]*PathItem)
}
m = make(map[string]*PathItem, len(callback.m))
for k, v := range callback.m {
m[k] = v
}
return
}
var _ jsonpointer.JSONPointable = (*Callback)(nil)
// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable
func (callback Callback) JSONLookup(token string) (any, error) {
if v := callback.Value(token); v == nil {
vv, _, err := jsonpointer.GetForToken(callback.Extensions, token)
return vv, err
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
var vv *PathItem = v
return vv, nil
}
}
// MarshalYAML returns the YAML encoding of Callback.
func (callback *Callback) MarshalYAML() (any, error) {
if callback == nil {
return nil, nil
}
m := make(map[string]any, callback.Len()+len(callback.Extensions))
for k, v := range callback.Extensions {
m[k] = v
}
for k, v := range callback.Map() {
m[k] = v
}
return m, nil
}
// MarshalJSON returns the JSON encoding of Callback.
func (callback *Callback) MarshalJSON() ([]byte, error) {
callbackYaml, err := callback.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(callbackYaml)
}
// UnmarshalJSON sets Callback to a copy of data.
func (callback *Callback) UnmarshalJSON(data []byte) (err error) {
var m map[string]any
if err = json.Unmarshal(data, &m); err != nil {
return
}
ks := make([]string, 0, len(m))
for k := range m {
ks = append(ks, k)
}
sort.Strings(ks)
x := Callback{
Extensions: make(map[string]any),
m: make(map[string]*PathItem, len(m)),
}
for _, k := range ks {
v := m[k]
if strings.HasPrefix(k, "x-") {
x.Extensions[k] = v
continue
}
if k == originKey {
var data []byte
if data, err = json.Marshal(v); err != nil {
return
}
if err = json.Unmarshal(data, &x.Origin); err != nil {
return
}
continue
}
var data []byte
if data, err = json.Marshal(v); err != nil {
return
}
var vv PathItem
if err = vv.UnmarshalJSON(data); err != nil {
return
}
x.m[k] = &vv
}
*callback = x
return
}
// NewPathsWithCapacity builds a paths object of the given capacity.
func NewPathsWithCapacity(cap int) *Paths {
if cap == 0 {
return &Paths{m: make(map[string]*PathItem)}
}
return &Paths{m: make(map[string]*PathItem, cap)}
}
// Value returns the paths for key or nil
func (paths *Paths) Value(key string) *PathItem {
if paths.Len() == 0 {
return nil
}
return paths.m[key]
}
// Set adds or replaces key 'key' of 'paths' with 'value'.
// Note: 'paths' MUST be non-nil
func (paths *Paths) Set(key string, value *PathItem) {
if paths.m == nil {
paths.m = make(map[string]*PathItem)
}
paths.m[key] = value
}
// Len returns the amount of keys in paths excluding paths.Extensions.
func (paths *Paths) Len() int {
if paths == nil || paths.m == nil {
return 0
}
return len(paths.m)
}
// Delete removes the entry associated with key 'key' from 'paths'.
func (paths *Paths) Delete(key string) {
if paths != nil && paths.m != nil {
delete(paths.m, key)
}
}
// Map returns paths as a 'map'.
// Note: iteration on Go maps is not ordered.
func (paths *Paths) Map() (m map[string]*PathItem) {
if paths == nil || len(paths.m) == 0 {
return make(map[string]*PathItem)
}
m = make(map[string]*PathItem, len(paths.m))
for k, v := range paths.m {
m[k] = v
}
return
}
var _ jsonpointer.JSONPointable = (*Paths)(nil)
// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable
func (paths Paths) JSONLookup(token string) (any, error) {
if v := paths.Value(token); v == nil {
vv, _, err := jsonpointer.GetForToken(paths.Extensions, token)
return vv, err
} else if ref := v.Ref; ref != "" {
return &Ref{Ref: ref}, nil
} else {
var vv *PathItem = v
return vv, nil
}
}
// MarshalYAML returns the YAML encoding of Paths.
func (paths *Paths) MarshalYAML() (any, error) {
if paths == nil {
return nil, nil
}
m := make(map[string]any, paths.Len()+len(paths.Extensions))
for k, v := range paths.Extensions {
m[k] = v
}
for k, v := range paths.Map() {
m[k] = v
}
return m, nil
}
// MarshalJSON returns the JSON encoding of Paths.
func (paths *Paths) MarshalJSON() ([]byte, error) {
pathsYaml, err := paths.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(pathsYaml)
}
// UnmarshalJSON sets Paths to a copy of data.
func (paths *Paths) UnmarshalJSON(data []byte) (err error) {
var m map[string]any
if err = json.Unmarshal(data, &m); err != nil {
return
}
ks := make([]string, 0, len(m))
for k := range m {
ks = append(ks, k)
}
sort.Strings(ks)
x := Paths{
Extensions: make(map[string]any),
m: make(map[string]*PathItem, len(m)),
}
for _, k := range ks {
v := m[k]
if strings.HasPrefix(k, "x-") {
x.Extensions[k] = v
continue
}
if k == originKey {
var data []byte
if data, err = json.Marshal(v); err != nil {
return
}
if err = json.Unmarshal(data, &x.Origin); err != nil {
return
}
continue
}
var data []byte
if data, err = json.Marshal(v); err != nil {
return
}
var vv PathItem
if err = vv.UnmarshalJSON(data); err != nil {
return
}
x.m[k] = &vv
}
*paths = x
return
}

34
vendor/github.com/getkin/kin-openapi/openapi3/marsh.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
package openapi3
import (
"encoding/json"
"fmt"
"strings"
"github.com/oasdiff/yaml"
)
func unmarshalError(jsonUnmarshalErr error) error {
if before, after, found := strings.Cut(jsonUnmarshalErr.Error(), "Bis"); found && before != "" && after != "" {
before = strings.ReplaceAll(before, " Go struct ", " ")
return fmt.Errorf("%s%s", before, strings.ReplaceAll(after, "Bis", ""))
}
return jsonUnmarshalErr
}
func unmarshal(data []byte, v any, includeOrigin bool) error {
var jsonErr, yamlErr error
// See https://github.com/getkin/kin-openapi/issues/680
if jsonErr = json.Unmarshal(data, v); jsonErr == nil {
return nil
}
// UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys
if yamlErr = yaml.UnmarshalWithOrigin(data, v, includeOrigin); yamlErr == nil {
return nil
}
// If both unmarshaling attempts fail, return a new error that includes both errors
return fmt.Errorf("failed to unmarshal data: json error: %v, yaml error: %v", jsonErr, yamlErr)
}

View File

@ -2,17 +2,22 @@ package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"sort"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
// MediaType is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#media-type-object
type MediaType struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"`
Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
Encoding map[string]*Encoding `json:"encoding,omitempty" yaml:"encoding,omitempty"`
}
@ -37,7 +42,7 @@ func (mediaType *MediaType) WithSchemaRef(schema *SchemaRef) *MediaType {
return mediaType
}
func (mediaType *MediaType) WithExample(name string, value interface{}) *MediaType {
func (mediaType *MediaType) WithExample(name string, value any) *MediaType {
example := mediaType.Examples
if example == nil {
example = make(map[string]*ExampleRef)
@ -59,27 +64,103 @@ func (mediaType *MediaType) WithEncoding(name string, enc *Encoding) *MediaType
return mediaType
}
func (mediaType *MediaType) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(mediaType)
// MarshalJSON returns the JSON encoding of MediaType.
func (mediaType MediaType) MarshalJSON() ([]byte, error) {
x, err := mediaType.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of MediaType.
func (mediaType MediaType) MarshalYAML() (any, error) {
m := make(map[string]any, 4+len(mediaType.Extensions))
for k, v := range mediaType.Extensions {
m[k] = v
}
if x := mediaType.Schema; x != nil {
m["schema"] = x
}
if x := mediaType.Example; x != nil {
m["example"] = x
}
if x := mediaType.Examples; len(x) != 0 {
m["examples"] = x
}
if x := mediaType.Encoding; len(x) != 0 {
m["encoding"] = x
}
return m, nil
}
// UnmarshalJSON sets MediaType to a copy of data.
func (mediaType *MediaType) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, mediaType)
type MediaTypeBis MediaType
var x MediaTypeBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
func (value *MediaType) Validate(ctx context.Context) error {
if value == nil {
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "schema")
delete(x.Extensions, "example")
delete(x.Extensions, "examples")
delete(x.Extensions, "encoding")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*mediaType = MediaType(x)
return nil
}
if schema := value.Schema; schema != nil {
// Validate returns an error if MediaType does not comply with the OpenAPI spec.
func (mediaType *MediaType) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if mediaType == nil {
return nil
}
if schema := mediaType.Schema; schema != nil {
if err := schema.Validate(ctx); err != nil {
return err
}
}
return nil
if mediaType.Example != nil && mediaType.Examples != nil {
return errors.New("example and examples are mutually exclusive")
}
func (mediaType MediaType) JSONLookup(token string) (interface{}, error) {
if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled {
if example := mediaType.Example; example != nil {
if err := validateExampleValue(ctx, example, schema.Value); err != nil {
return fmt.Errorf("invalid example: %w", err)
}
}
if examples := mediaType.Examples; examples != nil {
names := make([]string, 0, len(examples))
for name := range examples {
names = append(names, name)
}
sort.Strings(names)
for _, k := range names {
v := examples[k]
if err := v.Validate(ctx); err != nil {
return fmt.Errorf("example %s: %w", k, err)
}
if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil {
return fmt.Errorf("example %s: %w", k, err)
}
}
}
}
}
return validateExtensions(ctx, mediaType.Extensions)
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (mediaType MediaType) JSONLookup(token string) (any, error) {
switch token {
case "schema":
if mediaType.Schema != nil {
@ -95,6 +176,6 @@ func (mediaType MediaType) JSONLookup(token string) (interface{}, error) {
case "encoding":
return mediaType.Encoding, nil
}
v, _, err := jsonpointer.GetForToken(mediaType.ExtensionProps, token)
v, _, err := jsonpointer.GetForToken(mediaType.Extensions, token)
return v, err
}

View File

@ -2,43 +2,129 @@ package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
// T is the root of an OpenAPI v3 document
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#openapi-object
type T struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
OpenAPI string `json:"openapi" yaml:"openapi"` // Required
Components Components `json:"components,omitempty" yaml:"components,omitempty"`
Components *Components `json:"components,omitempty" yaml:"components,omitempty"`
Info *Info `json:"info" yaml:"info"` // Required
Paths Paths `json:"paths" yaml:"paths"` // Required
Paths *Paths `json:"paths" yaml:"paths"` // Required
Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"`
Tags Tags `json:"tags,omitempty" yaml:"tags,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
visited visitedComponent
url *url.URL
}
var _ jsonpointer.JSONPointable = (*T)(nil)
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (doc *T) JSONLookup(token string) (any, error) {
switch token {
case "openapi":
return doc.OpenAPI, nil
case "components":
return doc.Components, nil
case "info":
return doc.Info, nil
case "paths":
return doc.Paths, nil
case "security":
return doc.Security, nil
case "servers":
return doc.Servers, nil
case "tags":
return doc.Tags, nil
case "externalDocs":
return doc.ExternalDocs, nil
}
v, _, err := jsonpointer.GetForToken(doc.Extensions, token)
return v, err
}
// MarshalJSON returns the JSON encoding of T.
func (doc *T) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(doc)
x, err := doc.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of T.
func (doc *T) MarshalYAML() (any, error) {
if doc == nil {
return nil, nil
}
m := make(map[string]any, 4+len(doc.Extensions))
for k, v := range doc.Extensions {
m[k] = v
}
m["openapi"] = doc.OpenAPI
if x := doc.Components; x != nil {
m["components"] = x
}
m["info"] = doc.Info
m["paths"] = doc.Paths
if x := doc.Security; len(x) != 0 {
m["security"] = x
}
if x := doc.Servers; len(x) != 0 {
m["servers"] = x
}
if x := doc.Tags; len(x) != 0 {
m["tags"] = x
}
if x := doc.ExternalDocs; x != nil {
m["externalDocs"] = x
}
return m, nil
}
// UnmarshalJSON sets T to a copy of data.
func (doc *T) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, doc)
type TBis T
var x TBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "openapi")
delete(x.Extensions, "components")
delete(x.Extensions, "info")
delete(x.Extensions, "paths")
delete(x.Extensions, "security")
delete(x.Extensions, "servers")
delete(x.Extensions, "tags")
delete(x.Extensions, "externalDocs")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*doc = T(x)
return nil
}
func (doc *T) AddOperation(path string, method string, operation *Operation) {
paths := doc.Paths
if paths == nil {
paths = make(Paths)
doc.Paths = paths
if doc.Paths == nil {
doc.Paths = NewPaths()
}
pathItem := paths[path]
pathItem := doc.Paths.Value(path)
if pathItem == nil {
pathItem = &PathItem{}
paths[path] = pathItem
doc.Paths.Set(path, pathItem)
}
pathItem.SetOperation(method, operation)
}
@ -47,59 +133,73 @@ func (doc *T) AddServer(server *Server) {
doc.Servers = append(doc.Servers, server)
}
func (value *T) Validate(ctx context.Context) error {
if value.OpenAPI == "" {
func (doc *T) AddServers(servers ...*Server) {
doc.Servers = append(doc.Servers, servers...)
}
// Validate returns an error if T does not comply with the OpenAPI spec.
// Validations Options can be provided to modify the validation behavior.
func (doc *T) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if doc.OpenAPI == "" {
return errors.New("value of openapi must be a non-empty string")
}
// NOTE: only mention info/components/paths/... key in this func's errors.
var wrap func(error) error
{
wrap := func(e error) error { return fmt.Errorf("invalid components: %v", e) }
if err := value.Components.Validate(ctx); err != nil {
wrap = func(e error) error { return fmt.Errorf("invalid components: %w", e) }
if v := doc.Components; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
}
{
wrap := func(e error) error { return fmt.Errorf("invalid info: %v", e) }
if v := value.Info; v != nil {
wrap = func(e error) error { return fmt.Errorf("invalid info: %w", e) }
if v := doc.Info; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
} else {
return wrap(errors.New("must be an object"))
}
}
{
wrap := func(e error) error { return fmt.Errorf("invalid paths: %v", e) }
if v := value.Paths; v != nil {
wrap = func(e error) error { return fmt.Errorf("invalid paths: %w", e) }
if v := doc.Paths; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
} else {
return wrap(errors.New("must be an object"))
}
}
{
wrap := func(e error) error { return fmt.Errorf("invalid security: %v", e) }
if v := value.Security; v != nil {
wrap = func(e error) error { return fmt.Errorf("invalid security: %w", e) }
if v := doc.Security; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
}
}
{
wrap := func(e error) error { return fmt.Errorf("invalid servers: %v", e) }
if v := value.Servers; v != nil {
wrap = func(e error) error { return fmt.Errorf("invalid servers: %w", e) }
if v := doc.Servers; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
}
wrap = func(e error) error { return fmt.Errorf("invalid tags: %w", e) }
if v := doc.Tags; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
}
return nil
wrap = func(e error) error { return fmt.Errorf("invalid external docs: %w", e) }
if v := doc.ExternalDocs; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
}
return validateExtensions(ctx, doc.Extensions)
}

View File

@ -2,16 +2,19 @@ package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"strconv"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
// Operation represents "operation" specified by" OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object
type Operation struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
// Optional tags for documentation.
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
@ -32,7 +35,7 @@ type Operation struct {
RequestBody *RequestBodyRef `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
// Responses.
Responses Responses `json:"responses" yaml:"responses"` // Required
Responses *Responses `json:"responses" yaml:"responses"` // Required
// Optional callbacks
Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
@ -54,15 +57,88 @@ func NewOperation() *Operation {
return &Operation{}
}
func (operation *Operation) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(operation)
// MarshalJSON returns the JSON encoding of Operation.
func (operation Operation) MarshalJSON() ([]byte, error) {
x, err := operation.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Operation.
func (operation Operation) MarshalYAML() (any, error) {
m := make(map[string]any, 12+len(operation.Extensions))
for k, v := range operation.Extensions {
m[k] = v
}
if x := operation.Tags; len(x) != 0 {
m["tags"] = x
}
if x := operation.Summary; x != "" {
m["summary"] = x
}
if x := operation.Description; x != "" {
m["description"] = x
}
if x := operation.OperationID; x != "" {
m["operationId"] = x
}
if x := operation.Parameters; len(x) != 0 {
m["parameters"] = x
}
if x := operation.RequestBody; x != nil {
m["requestBody"] = x
}
m["responses"] = operation.Responses
if x := operation.Callbacks; len(x) != 0 {
m["callbacks"] = x
}
if x := operation.Deprecated; x {
m["deprecated"] = x
}
if x := operation.Security; x != nil {
m["security"] = x
}
if x := operation.Servers; x != nil {
m["servers"] = x
}
if x := operation.ExternalDocs; x != nil {
m["externalDocs"] = x
}
return m, nil
}
// UnmarshalJSON sets Operation to a copy of data.
func (operation *Operation) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, operation)
type OperationBis Operation
var x OperationBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "tags")
delete(x.Extensions, "summary")
delete(x.Extensions, "description")
delete(x.Extensions, "operationId")
delete(x.Extensions, "parameters")
delete(x.Extensions, "requestBody")
delete(x.Extensions, "responses")
delete(x.Extensions, "callbacks")
delete(x.Extensions, "deprecated")
delete(x.Extensions, "security")
delete(x.Extensions, "servers")
delete(x.Extensions, "externalDocs")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*operation = Operation(x)
return nil
}
func (operation Operation) JSONLookup(token string) (interface{}, error) {
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (operation Operation) JSONLookup(token string) (any, error) {
switch token {
case "requestBody":
if operation.RequestBody != nil {
@ -95,48 +171,54 @@ func (operation Operation) JSONLookup(token string) (interface{}, error) {
return operation.ExternalDocs, nil
}
v, _, err := jsonpointer.GetForToken(operation.ExtensionProps, token)
v, _, err := jsonpointer.GetForToken(operation.Extensions, token)
return v, err
}
func (operation *Operation) AddParameter(p *Parameter) {
operation.Parameters = append(operation.Parameters, &ParameterRef{
Value: p,
})
operation.Parameters = append(operation.Parameters, &ParameterRef{Value: p})
}
func (operation *Operation) AddResponse(status int, response *Response) {
responses := operation.Responses
if responses == nil {
responses = NewResponses()
operation.Responses = responses
}
code := "default"
if status != 0 {
if 0 < status && status < 1000 {
code = strconv.FormatInt(int64(status), 10)
}
responses[code] = &ResponseRef{
Value: response,
if operation.Responses == nil {
operation.Responses = NewResponses()
}
operation.Responses.Set(code, &ResponseRef{Value: response})
}
// Validate returns an error if Operation does not comply with the OpenAPI spec.
func (operation *Operation) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if v := operation.Parameters; v != nil {
if err := v.Validate(ctx); err != nil {
return err
}
}
func (value *Operation) Validate(ctx context.Context) error {
if v := value.Parameters; v != nil {
if v := operation.RequestBody; v != nil {
if err := v.Validate(ctx); err != nil {
return err
}
}
if v := value.RequestBody; v != nil {
if err := v.Validate(ctx); err != nil {
return err
}
}
if v := value.Responses; v != nil {
if v := operation.Responses; v != nil {
if err := v.Validate(ctx); err != nil {
return err
}
} else {
return errors.New("value of responses must be an object")
}
return nil
if v := operation.ExternalDocs; v != nil {
if err := v.Validate(ctx); err != nil {
return fmt.Errorf("invalid external docs: %w", err)
}
}
return validateExtensions(ctx, operation.Extensions)
}

View File

@ -0,0 +1,17 @@
package openapi3
const originKey = "__origin__"
// Origin contains the origin of a collection.
// Key is the location of the collection itself.
// Fields is a map of the location of each field in the collection.
type Origin struct {
Key *Location `json:"key,omitempty" yaml:"key,omitempty"`
Fields map[string]Location `json:"fields,omitempty" yaml:"fields,omitempty"`
}
// Location is a struct that contains the location of a field.
type Location struct {
Line int `json:"line,omitempty" yaml:"line,omitempty"`
Column int `json:"column,omitempty" yaml:"column,omitempty"`
}

View File

@ -2,47 +2,31 @@ package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"sort"
"strconv"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
type ParametersMap map[string]*ParameterRef
var _ jsonpointer.JSONPointable = (*ParametersMap)(nil)
func (p ParametersMap) JSONLookup(token string) (interface{}, error) {
ref, ok := p[token]
if ref == nil || ok == false {
return nil, fmt.Errorf("object has no field %q", token)
}
if ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
// Parameters is specified by OpenAPI/Swagger 3.0 standard.
type Parameters []*ParameterRef
var _ jsonpointer.JSONPointable = (*Parameters)(nil)
func (p Parameters) JSONLookup(token string) (interface{}, error) {
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (p Parameters) JSONLookup(token string) (any, error) {
index, err := strconv.Atoi(token)
if err != nil {
return nil, err
}
if index < 0 || index >= len(p) {
return nil, fmt.Errorf("index %d out of bounds of array of length %d", index, len(p))
}
ref := p[index]
if ref != nil && ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
@ -64,10 +48,13 @@ func (parameters Parameters) GetByInAndName(in string, name string) *Parameter {
return nil
}
func (value Parameters) Validate(ctx context.Context) error {
// Validate returns an error if Parameters does not comply with the OpenAPI spec.
func (parameters Parameters) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
dupes := make(map[string]struct{})
for _, item := range value {
if v := item.Value; v != nil {
for _, parameterRef := range parameters {
if v := parameterRef.Value; v != nil {
key := v.In + ":" + v.Name
if _, ok := dupes[key]; ok {
return fmt.Errorf("more than one %q parameter has name %q", v.In, v.Name)
@ -75,7 +62,7 @@ func (value Parameters) Validate(ctx context.Context) error {
dupes[key] = struct{}{}
}
if err := item.Validate(ctx); err != nil {
if err := parameterRef.Validate(ctx); err != nil {
return err
}
}
@ -83,9 +70,11 @@ func (value Parameters) Validate(ctx context.Context) error {
}
// Parameter is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#parameterObject
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object
type Parameter struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
In string `json:"in,omitempty" yaml:"in,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
@ -96,7 +85,7 @@ type Parameter struct {
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
Example any `json:"example,omitempty" yaml:"example,omitempty"`
Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
Content Content `json:"content,omitempty" yaml:"content,omitempty"`
}
@ -160,50 +149,133 @@ func (parameter *Parameter) WithSchema(value *Schema) *Parameter {
return parameter
}
func (parameter *Parameter) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(parameter)
// MarshalJSON returns the JSON encoding of Parameter.
func (parameter Parameter) MarshalJSON() ([]byte, error) {
x, err := parameter.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Parameter.
func (parameter Parameter) MarshalYAML() (any, error) {
m := make(map[string]any, 13+len(parameter.Extensions))
for k, v := range parameter.Extensions {
m[k] = v
}
if x := parameter.Name; x != "" {
m["name"] = x
}
if x := parameter.In; x != "" {
m["in"] = x
}
if x := parameter.Description; x != "" {
m["description"] = x
}
if x := parameter.Style; x != "" {
m["style"] = x
}
if x := parameter.Explode; x != nil {
m["explode"] = x
}
if x := parameter.AllowEmptyValue; x {
m["allowEmptyValue"] = x
}
if x := parameter.AllowReserved; x {
m["allowReserved"] = x
}
if x := parameter.Deprecated; x {
m["deprecated"] = x
}
if x := parameter.Required; x {
m["required"] = x
}
if x := parameter.Schema; x != nil {
m["schema"] = x
}
if x := parameter.Example; x != nil {
m["example"] = x
}
if x := parameter.Examples; len(x) != 0 {
m["examples"] = x
}
if x := parameter.Content; len(x) != 0 {
m["content"] = x
}
return m, nil
}
// UnmarshalJSON sets Parameter to a copy of data.
func (parameter *Parameter) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, parameter)
type ParameterBis Parameter
var x ParameterBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "name")
delete(x.Extensions, "in")
delete(x.Extensions, "description")
delete(x.Extensions, "style")
delete(x.Extensions, "explode")
delete(x.Extensions, "allowEmptyValue")
delete(x.Extensions, "allowReserved")
delete(x.Extensions, "deprecated")
delete(x.Extensions, "required")
delete(x.Extensions, "schema")
delete(x.Extensions, "example")
delete(x.Extensions, "examples")
delete(x.Extensions, "content")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
func (value Parameter) JSONLookup(token string) (interface{}, error) {
*parameter = Parameter(x)
return nil
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (parameter Parameter) JSONLookup(token string) (any, error) {
switch token {
case "schema":
if value.Schema != nil {
if value.Schema.Ref != "" {
return &Ref{Ref: value.Schema.Ref}, nil
if parameter.Schema != nil {
if parameter.Schema.Ref != "" {
return &Ref{Ref: parameter.Schema.Ref}, nil
}
return value.Schema.Value, nil
return parameter.Schema.Value, nil
}
case "name":
return value.Name, nil
return parameter.Name, nil
case "in":
return value.In, nil
return parameter.In, nil
case "description":
return value.Description, nil
return parameter.Description, nil
case "style":
return value.Style, nil
return parameter.Style, nil
case "explode":
return value.Explode, nil
return parameter.Explode, nil
case "allowEmptyValue":
return value.AllowEmptyValue, nil
return parameter.AllowEmptyValue, nil
case "allowReserved":
return value.AllowReserved, nil
return parameter.AllowReserved, nil
case "deprecated":
return value.Deprecated, nil
return parameter.Deprecated, nil
case "required":
return value.Required, nil
return parameter.Required, nil
case "example":
return value.Example, nil
return parameter.Example, nil
case "examples":
return value.Examples, nil
return parameter.Examples, nil
case "content":
return value.Content, nil
return parameter.Content, nil
}
v, _, err := jsonpointer.GetForToken(value.ExtensionProps, token)
v, _, err := jsonpointer.GetForToken(parameter.Extensions, token)
return v, err
}
@ -237,11 +309,14 @@ func (parameter *Parameter) SerializationMethod() (*SerializationMethod, error)
}
}
func (value *Parameter) Validate(ctx context.Context) error {
if value.Name == "" {
// Validate returns an error if Parameter does not comply with the OpenAPI spec.
func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if parameter.Name == "" {
return errors.New("parameter name can't be blank")
}
in := value.In
in := parameter.In
switch in {
case
ParameterInPath,
@ -249,57 +324,101 @@ func (value *Parameter) Validate(ctx context.Context) error {
ParameterInHeader,
ParameterInCookie:
default:
return fmt.Errorf("parameter can't have 'in' value %q", value.In)
return fmt.Errorf("parameter can't have 'in' value %q", parameter.In)
}
if in == ParameterInPath && !parameter.Required {
return fmt.Errorf("path parameter %q must be required", parameter.Name)
}
// Validate a parameter's serialization method.
sm, err := value.SerializationMethod()
sm, err := parameter.SerializationMethod()
if err != nil {
return err
}
var smSupported bool
switch {
case value.In == ParameterInPath && sm.Style == SerializationSimple && !sm.Explode,
value.In == ParameterInPath && sm.Style == SerializationSimple && sm.Explode,
value.In == ParameterInPath && sm.Style == SerializationLabel && !sm.Explode,
value.In == ParameterInPath && sm.Style == SerializationLabel && sm.Explode,
value.In == ParameterInPath && sm.Style == SerializationMatrix && !sm.Explode,
value.In == ParameterInPath && sm.Style == SerializationMatrix && sm.Explode,
case parameter.In == ParameterInPath && sm.Style == SerializationSimple && !sm.Explode,
parameter.In == ParameterInPath && sm.Style == SerializationSimple && sm.Explode,
parameter.In == ParameterInPath && sm.Style == SerializationLabel && !sm.Explode,
parameter.In == ParameterInPath && sm.Style == SerializationLabel && sm.Explode,
parameter.In == ParameterInPath && sm.Style == SerializationMatrix && !sm.Explode,
parameter.In == ParameterInPath && sm.Style == SerializationMatrix && sm.Explode,
value.In == ParameterInQuery && sm.Style == SerializationForm && sm.Explode,
value.In == ParameterInQuery && sm.Style == SerializationForm && !sm.Explode,
value.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && sm.Explode,
value.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && !sm.Explode,
value.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && sm.Explode,
value.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && !sm.Explode,
value.In == ParameterInQuery && sm.Style == SerializationDeepObject && sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationForm && sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationForm && !sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && !sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && !sm.Explode,
parameter.In == ParameterInQuery && sm.Style == SerializationDeepObject && sm.Explode,
value.In == ParameterInHeader && sm.Style == SerializationSimple && !sm.Explode,
value.In == ParameterInHeader && sm.Style == SerializationSimple && sm.Explode,
parameter.In == ParameterInHeader && sm.Style == SerializationSimple && !sm.Explode,
parameter.In == ParameterInHeader && sm.Style == SerializationSimple && sm.Explode,
value.In == ParameterInCookie && sm.Style == SerializationForm && !sm.Explode,
value.In == ParameterInCookie && sm.Style == SerializationForm && sm.Explode:
parameter.In == ParameterInCookie && sm.Style == SerializationForm && !sm.Explode,
parameter.In == ParameterInCookie && sm.Style == SerializationForm && sm.Explode:
smSupported = true
}
if !smSupported {
e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a %s parameter", sm.Style, sm.Explode, in)
return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, e)
return fmt.Errorf("parameter %q schema is invalid: %w", parameter.Name, e)
}
if (value.Schema == nil) == (value.Content == nil) {
if (parameter.Schema == nil) == (len(parameter.Content) == 0) {
e := errors.New("parameter must contain exactly one of content and schema")
return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, e)
return fmt.Errorf("parameter %q schema is invalid: %w", parameter.Name, e)
}
if schema := value.Schema; schema != nil {
if err := schema.Validate(ctx); err != nil {
return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, err)
if content := parameter.Content; content != nil {
e := errors.New("parameter content must only contain one entry")
if len(content) > 1 {
return fmt.Errorf("parameter %q content is invalid: %w", parameter.Name, e)
}
if err := content.Validate(ctx); err != nil {
return fmt.Errorf("parameter %q content is invalid: %w", parameter.Name, err)
}
}
if content := value.Content; content != nil {
if err := content.Validate(ctx); err != nil {
return fmt.Errorf("parameter %q content is invalid: %v", value.Name, err)
if schema := parameter.Schema; schema != nil {
if err := schema.Validate(ctx); err != nil {
return fmt.Errorf("parameter %q schema is invalid: %w", parameter.Name, err)
}
if parameter.Example != nil && parameter.Examples != nil {
return fmt.Errorf("parameter %q example and examples are mutually exclusive", parameter.Name)
}
if vo := getValidationOptions(ctx); vo.examplesValidationDisabled {
return nil
}
if example := parameter.Example; example != nil {
if err := validateExampleValue(ctx, example, schema.Value); err != nil {
return fmt.Errorf("invalid example: %w", err)
}
} else if examples := parameter.Examples; examples != nil {
names := make([]string, 0, len(examples))
for name := range examples {
names = append(names, name)
}
sort.Strings(names)
for _, k := range names {
v := examples[k]
if err := v.Validate(ctx); err != nil {
return fmt.Errorf("%s: %w", k, err)
}
if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil {
return fmt.Errorf("%s: %w", k, err)
}
}
}
}
return validateExtensions(ctx, parameter.Extensions)
}
// UnmarshalJSON sets ParametersMap to a copy of data.
func (parametersMap *ParametersMap) UnmarshalJSON(data []byte) (err error) {
*parametersMap, _, err = unmarshalStringMapP[ParameterRef](data)
return
}

View File

@ -2,14 +2,18 @@ package openapi3
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/getkin/kin-openapi/jsoninfo"
"sort"
)
// PathItem is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#path-item-object
type PathItem struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
@ -26,16 +30,99 @@ type PathItem struct {
Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
}
func (pathItem *PathItem) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(pathItem)
// MarshalJSON returns the JSON encoding of PathItem.
func (pathItem PathItem) MarshalJSON() ([]byte, error) {
x, err := pathItem.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of PathItem.
func (pathItem PathItem) MarshalYAML() (any, error) {
if ref := pathItem.Ref; ref != "" {
return Ref{Ref: ref}, nil
}
m := make(map[string]any, 13+len(pathItem.Extensions))
for k, v := range pathItem.Extensions {
m[k] = v
}
if x := pathItem.Summary; x != "" {
m["summary"] = x
}
if x := pathItem.Description; x != "" {
m["description"] = x
}
if x := pathItem.Connect; x != nil {
m["connect"] = x
}
if x := pathItem.Delete; x != nil {
m["delete"] = x
}
if x := pathItem.Get; x != nil {
m["get"] = x
}
if x := pathItem.Head; x != nil {
m["head"] = x
}
if x := pathItem.Options; x != nil {
m["options"] = x
}
if x := pathItem.Patch; x != nil {
m["patch"] = x
}
if x := pathItem.Post; x != nil {
m["post"] = x
}
if x := pathItem.Put; x != nil {
m["put"] = x
}
if x := pathItem.Trace; x != nil {
m["trace"] = x
}
if x := pathItem.Servers; len(x) != 0 {
m["servers"] = x
}
if x := pathItem.Parameters; len(x) != 0 {
m["parameters"] = x
}
return m, nil
}
// UnmarshalJSON sets PathItem to a copy of data.
func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, pathItem)
type PathItemBis PathItem
var x PathItemBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "$ref")
delete(x.Extensions, "summary")
delete(x.Extensions, "description")
delete(x.Extensions, "connect")
delete(x.Extensions, "delete")
delete(x.Extensions, "get")
delete(x.Extensions, "head")
delete(x.Extensions, "options")
delete(x.Extensions, "patch")
delete(x.Extensions, "post")
delete(x.Extensions, "put")
delete(x.Extensions, "trace")
delete(x.Extensions, "servers")
delete(x.Extensions, "parameters")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*pathItem = PathItem(x)
return nil
}
func (pathItem *PathItem) Operations() map[string]*Operation {
operations := make(map[string]*Operation, 4)
operations := make(map[string]*Operation)
if v := pathItem.Connect; v != nil {
operations[http.MethodConnect] = v
}
@ -116,11 +203,48 @@ func (pathItem *PathItem) SetOperation(method string, operation *Operation) {
}
}
func (value *PathItem) Validate(ctx context.Context) error {
for _, operation := range value.Operations() {
// Validate returns an error if PathItem does not comply with the OpenAPI spec.
func (pathItem *PathItem) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
operations := pathItem.Operations()
methods := make([]string, 0, len(operations))
for method := range operations {
methods = append(methods, method)
}
sort.Strings(methods)
for _, method := range methods {
operation := operations[method]
if err := operation.Validate(ctx); err != nil {
return fmt.Errorf("invalid operation %s: %v", method, err)
}
}
if v := pathItem.Parameters; v != nil {
if err := v.Validate(ctx); err != nil {
return err
}
}
return nil
return validateExtensions(ctx, pathItem.Extensions)
}
// isEmpty's introduced in 546590b1
func (pathItem *PathItem) isEmpty() bool {
// NOTE: ignores pathItem.Extensions
// NOTE: ignores pathItem.Ref
return pathItem.Summary == "" &&
pathItem.Description == "" &&
pathItem.Connect == nil &&
pathItem.Delete == nil &&
pathItem.Get == nil &&
pathItem.Head == nil &&
pathItem.Options == nil &&
pathItem.Patch == nil &&
pathItem.Post == nil &&
pathItem.Put == nil &&
pathItem.Trace == nil &&
len(pathItem.Servers) == 0 &&
len(pathItem.Parameters) == 0
}

View File

@ -3,22 +3,60 @@ package openapi3
import (
"context"
"fmt"
"sort"
"strings"
)
// Paths is specified by OpenAPI/Swagger standard version 3.0.
type Paths map[string]*PathItem
// Paths is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
type Paths struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
func (value Paths) Validate(ctx context.Context) error {
normalizedPaths := make(map[string]string)
for path, pathItem := range value {
m map[string]*PathItem
}
// NewPaths builds a paths object with path items in insertion order.
func NewPaths(opts ...NewPathsOption) *Paths {
paths := NewPathsWithCapacity(len(opts))
for _, opt := range opts {
opt(paths)
}
return paths
}
// NewPathsOption describes options to NewPaths func
type NewPathsOption func(*Paths)
// WithPath adds a named path item
func WithPath(path string, pathItem *PathItem) NewPathsOption {
return func(paths *Paths) {
if p := pathItem; p != nil && path != "" {
paths.Set(path, p)
}
}
}
// Validate returns an error if Paths does not comply with the OpenAPI spec.
func (paths *Paths) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
normalizedPaths := make(map[string]string, paths.Len())
keys := make([]string, 0, paths.Len())
for key := range paths.Map() {
keys = append(keys, key)
}
sort.Strings(keys)
for _, path := range keys {
pathItem := paths.Value(path)
if path == "" || path[0] != '/' {
return fmt.Errorf("path %q does not start with a forward slash (/)", path)
}
if pathItem == nil {
value[path] = &PathItem{}
pathItem = value[path]
pathItem = &PathItem{}
paths.Set(path, pathItem)
}
normalizedPath, _, varsInPath := normalizeTemplatedPath(path)
@ -35,7 +73,14 @@ func (value Paths) Validate(ctx context.Context) error {
}
}
}
for method, operation := range pathItem.Operations() {
operations := pathItem.Operations()
methods := make([]string, 0, len(operations))
for method := range operations {
methods = append(methods, method)
}
sort.Strings(methods)
for _, method := range methods {
operation := operations[method]
var setParams []string
for _, parameterRef := range operation.Parameters {
if parameterRef != nil {
@ -79,12 +124,48 @@ func (value Paths) Validate(ctx context.Context) error {
}
if err := pathItem.Validate(ctx); err != nil {
return fmt.Errorf("invalid path %s: %v", path, err)
}
}
if err := paths.validateUniqueOperationIDs(); err != nil {
return err
}
return validateExtensions(ctx, paths.Extensions)
}
// InMatchingOrder returns paths in the order they are matched against URLs.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
// When matching URLs, concrete (non-templated) paths would be matched
// before their templated counterparts.
func (paths *Paths) InMatchingOrder() []string {
// NOTE: sorting by number of variables ASC then by descending lexicographical
// order seems to be a good heuristic.
if paths.Len() == 0 {
return nil
}
vars := make(map[int][]string)
max := 0
for path := range paths.Map() {
count := strings.Count(path, "}")
vars[count] = append(vars[count], path)
if count > max {
max = count
}
}
ordered := make([]string, 0, paths.Len())
for c := 0; c <= max; c++ {
if ps, ok := vars[c]; ok {
sort.Sort(sort.Reverse(sort.StringSlice(ps)))
ordered = append(ordered, ps...)
}
}
return ordered
}
// Find returns a path that matches the key.
//
// The method ignores differences in template variable names (except possible "*" suffix).
@ -97,15 +178,15 @@ func (value Paths) Validate(ctx context.Context) error {
// pathItem := path.Find("/person/{name}")
//
// would return the correct path item.
func (paths Paths) Find(key string) *PathItem {
func (paths *Paths) Find(key string) *PathItem {
// Try directly access the map
pathItem := paths[key]
pathItem := paths.Value(key)
if pathItem != nil {
return pathItem
}
normalizedPath, expected, _ := normalizeTemplatedPath(key)
for path, pathItem := range paths {
for path, pathItem := range paths.Map() {
pathNormalized, got, _ := normalizeTemplatedPath(path)
if got == expected && pathNormalized == normalizedPath {
return pathItem
@ -114,6 +195,30 @@ func (paths Paths) Find(key string) *PathItem {
return nil
}
func (paths *Paths) validateUniqueOperationIDs() error {
operationIDs := make(map[string]string)
for urlPath, pathItem := range paths.Map() {
if pathItem == nil {
continue
}
for httpMethod, operation := range pathItem.Operations() {
if operation == nil || operation.OperationID == "" {
continue
}
endpoint := httpMethod + " " + urlPath
if endpointDup, ok := operationIDs[operation.OperationID]; ok {
if endpoint > endpointDup { // For make error message a bit more deterministic. May be useful for tests.
endpoint, endpointDup = endpointDup, endpoint
}
return fmt.Errorf("operations %q and %q have the same operation id %q",
endpoint, endpointDup, operation.OperationID)
}
operationIDs[operation.OperationID] = endpoint
}
}
return nil
}
func normalizeTemplatedPath(path string) (string, uint, map[string]struct{}) {
if strings.IndexByte(path, '{') < 0 {
return path, 0, nil

10
vendor/github.com/getkin/kin-openapi/openapi3/ref.go generated vendored Normal file
View File

@ -0,0 +1,10 @@
package openapi3
//go:generate go run refsgenerator.go
// Ref is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
type Ref struct {
Ref string `json:"$ref" yaml:"$ref"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
}

File diff suppressed because it is too large Load Diff

153
vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl generated vendored Normal file
View File

@ -0,0 +1,153 @@
// Code generated by go generate; DO NOT EDIT.
package {{ .Package }}
import (
"context"
"encoding/json"
"fmt"
"net/url"
"sort"
"strings"
"github.com/go-openapi/jsonpointer"
"github.com/perimeterx/marshmallow"
)
{{ range $type := .Types }}
// {{ $type.Name }}Ref represents either a {{ $type.Name }} or a $ref to a {{ $type.Name }}.
// When serializing and both fields are set, Ref is preferred over Value.
type {{ $type.Name }}Ref struct {
// Extensions only captures fields starting with 'x-' as no other fields
// are allowed by the openapi spec.
Extensions map[string]any
Origin *Origin
Ref string
Value *{{ $type.Name }}
extra []string
refPath *url.URL
}
var _ jsonpointer.JSONPointable = (*{{ $type.Name }}Ref)(nil)
func (x *{{ $type.Name }}Ref) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
// RefString returns the $ref value.
func (x *{{ $type.Name }}Ref) RefString() string { return x.Ref }
// CollectionName returns the JSON string used for a collection of these components.
func (x *{{ $type.Name }}Ref) CollectionName() string { return "{{ $type.CollectionName }}" }
// RefPath returns the path of the $ref relative to the root document.
func (x *{{ $type.Name }}Ref) RefPath() *url.URL { return copyURI(x.refPath) }
func (x *{{ $type.Name }}Ref) setRefPath(u *url.URL) {
// Once the refPath is set don't override. References can be loaded
// multiple times not all with access to the correct path info.
if x.refPath != nil {
return
}
x.refPath = copyURI(u)
}
// MarshalYAML returns the YAML encoding of {{ $type.Name }}Ref.
func (x {{ $type.Name }}Ref) MarshalYAML() (any, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
return x.Value.MarshalYAML()
}
// MarshalJSON returns the JSON encoding of {{ $type.Name }}Ref.
func (x {{ $type.Name }}Ref) MarshalJSON() ([]byte, error) {
y, err := x.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(y)
}
// UnmarshalJSON sets {{ $type.Name }}Ref to a copy of data.
func (x *{{ $type.Name }}Ref) UnmarshalJSON(data []byte) error {
var refOnly Ref
if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" {
x.Ref = refOnly.Ref
x.Origin = refOnly.Origin
if len(extra) != 0 {
x.extra = make([]string, 0, len(extra))
for key := range extra {
x.extra = append(x.extra, key)
}
sort.Strings(x.extra)
for k := range extra {
if !strings.HasPrefix(k, "x-") {
delete(extra, k)
}
}
if len(extra) != 0 {
x.Extensions = extra
}
}
return nil
}
return json.Unmarshal(data, &x.Value)
}
// Validate returns an error if {{ $type.Name }}Ref does not comply with the OpenAPI spec.
func (x *{{ $type.Name }}Ref) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited
var extras []string
if extra := x.extra; len(extra) != 0 {
allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
for _, ex := range extra {
if allowed != nil {
if _, ok := allowed[ex]; ok {
continue
}
}
// extras in the Extensions checked below
if _, ok := x.Extensions[ex]; !ok {
extras = append(extras, ex)
}
}
}
if extra := x.Extensions; exProhibited && len(extra) != 0 {
allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
for ex := range extra {
if allowed != nil {
if _, ok := allowed[ex]; ok {
continue
}
}
extras = append(extras, ex)
}
}
if len(extras) != 0 {
return fmt.Errorf("extra sibling fields: %+v", extras)
}
if v := x.Value; v != nil {
return v.Validate(ctx)
}
return foundUnresolvedRef(x.Ref)
}
// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
func (x *{{ $type.Name }}Ref) JSONLookup(token string) (any, error) {
if token == "$ref" {
return x.Ref, nil
}
if v, ok := x.Extensions[token]; ok {
return v, nil
}
ptr, _, err := jsonpointer.GetForToken(x.Value, token)
return ptr, err
}
{{ end -}}

View File

@ -0,0 +1,54 @@
// Code generated by go generate; DO NOT EDIT.
package {{ .Package }}
import (
"context"
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
{{ range $type := .Types }}
func Test{{ $type.Name }}Ref_Extensions(t *testing.T) {
data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`)
ref := {{ $type.Name }}Ref{}
err := json.Unmarshal(data, &ref)
assert.NoError(t, err)
// captures extension
assert.Equal(t, "#/components/schemas/Pet", ref.Ref)
assert.Equal(t, float64(1), ref.Extensions["x-order"])
// does not capture non-extensions
assert.Nil(t, ref.Extensions["something"])
// validation
err = ref.Validate(context.Background())
require.EqualError(t, err, "extra sibling fields: [something]")
err = ref.Validate(context.Background(), ProhibitExtensionsWithRef())
require.EqualError(t, err, "extra sibling fields: [something x-order]")
err = ref.Validate(context.Background(), AllowExtraSiblingFields("something"))
assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined
// non-extension not json lookable
_, err = ref.JSONLookup("something")
assert.Error(t, err)
{{ if ne $type.Name "Header" }}
t.Run("extentions in value", func(t *testing.T) {
ref.Value = &{{ $type.Name }}{Extensions: map[string]any{}}
ref.Value.Extensions["x-order"] = 2.0
// prefers the value next to the \$ref over the one in the \$ref.
v, err := ref.JSONLookup("x-order")
assert.NoError(t, err)
assert.Equal(t, float64(1), v)
})
{{ else }}
// Header does not have its own extensions.
{{ end -}}
}
{{ end -}}

View File

@ -2,34 +2,19 @@ package openapi3
import (
"context"
"fmt"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
"encoding/json"
"errors"
)
type RequestBodies map[string]*RequestBodyRef
var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil)
func (r RequestBodies) JSONLookup(token string) (interface{}, error) {
ref, ok := r[token]
if ok == false {
return nil, fmt.Errorf("object has no field %q", token)
}
if ref != nil && ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
// RequestBody is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#request-body-object
type RequestBody struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
Content Content `json:"content,omitempty" yaml:"content,omitempty"`
Content Content `json:"content" yaml:"content"`
}
func NewRequestBody() *RequestBody {
@ -89,19 +74,73 @@ func (requestBody *RequestBody) GetMediaType(mediaType string) *MediaType {
return m[mediaType]
}
func (requestBody *RequestBody) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(requestBody)
// MarshalJSON returns the JSON encoding of RequestBody.
func (requestBody RequestBody) MarshalJSON() ([]byte, error) {
x, err := requestBody.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of RequestBody.
func (requestBody RequestBody) MarshalYAML() (any, error) {
m := make(map[string]any, 3+len(requestBody.Extensions))
for k, v := range requestBody.Extensions {
m[k] = v
}
if x := requestBody.Description; x != "" {
m["description"] = requestBody.Description
}
if x := requestBody.Required; x {
m["required"] = x
}
if x := requestBody.Content; true {
m["content"] = x
}
return m, nil
}
// UnmarshalJSON sets RequestBody to a copy of data.
func (requestBody *RequestBody) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, requestBody)
}
func (value *RequestBody) Validate(ctx context.Context) error {
if v := value.Content; v != nil {
if err := v.Validate(ctx); err != nil {
return err
type RequestBodyBis RequestBody
var x RequestBodyBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "description")
delete(x.Extensions, "required")
delete(x.Extensions, "content")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*requestBody = RequestBody(x)
return nil
}
// Validate returns an error if RequestBody does not comply with the OpenAPI spec.
func (requestBody *RequestBody) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if requestBody.Content == nil {
return errors.New("content of the request body is required")
}
if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled {
vo.examplesValidationAsReq, vo.examplesValidationAsRes = true, false
}
if err := requestBody.Content.Validate(ctx); err != nil {
return err
}
return validateExtensions(ctx, requestBody.Extensions)
}
// UnmarshalJSON sets RequestBodies to a copy of data.
func (requestBodies *RequestBodies) UnmarshalJSON(data []byte) (err error) {
*requestBodies, _, err = unmarshalStringMapP[RequestBodyRef](data)
return
}

View File

@ -2,60 +2,109 @@ package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"sort"
"strconv"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
// Responses is specified by OpenAPI/Swagger 3.0 standard.
type Responses map[string]*ResponseRef
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responses-object
type Responses struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"-" yaml:"-"`
var _ jsonpointer.JSONPointable = (*Responses)(nil)
func NewResponses() Responses {
r := make(Responses)
r["default"] = &ResponseRef{Value: NewResponse().WithDescription("")}
return r
m map[string]*ResponseRef
}
func (responses Responses) Default() *ResponseRef {
return responses["default"]
// NewResponses builds a responses object with response objects in insertion order.
// Given no arguments, NewResponses returns a valid responses object containing a default match-all reponse.
func NewResponses(opts ...NewResponsesOption) *Responses {
if len(opts) == 0 {
return NewResponses(WithName("default", NewResponse().WithDescription("")))
}
responses := NewResponsesWithCapacity(len(opts))
for _, opt := range opts {
opt(responses)
}
return responses
}
func (responses Responses) Get(status int) *ResponseRef {
return responses[strconv.FormatInt(int64(status), 10)]
// NewResponsesOption describes options to NewResponses func
type NewResponsesOption func(*Responses)
// WithStatus adds a status code keyed ResponseRef
func WithStatus(status int, responseRef *ResponseRef) NewResponsesOption {
return func(responses *Responses) {
if r := responseRef; r != nil {
code := strconv.FormatInt(int64(status), 10)
responses.Set(code, r)
}
}
}
func (value Responses) Validate(ctx context.Context) error {
if len(value) == 0 {
return errors.New("the responses object MUST contain at least one response code")
// WithName adds a name-keyed Response
func WithName(name string, response *Response) NewResponsesOption {
return func(responses *Responses) {
if r := response; r != nil && name != "" {
responses.Set(name, &ResponseRef{Value: r})
}
for _, v := range value {
if err := v.Validate(ctx); err != nil {
return err
}
}
// Default returns the default response
func (responses *Responses) Default() *ResponseRef {
return responses.Value("default")
}
// Status returns a ResponseRef for the given status
// If an exact match isn't initially found a patterned field is checked using
// the first digit to determine the range (eg: 201 to 2XX)
// See https://spec.openapis.org/oas/v3.0.3#patterned-fields-0
func (responses *Responses) Status(status int) *ResponseRef {
st := strconv.FormatInt(int64(status), 10)
if rref := responses.Value(st); rref != nil {
return rref
}
if 99 < status && status < 600 {
st = string(st[0]) + "XX"
switch st {
case "1XX", "2XX", "3XX", "4XX", "5XX":
return responses.Value(st)
}
}
return nil
}
func (responses Responses) JSONLookup(token string) (interface{}, error) {
ref, ok := responses[token]
if ok == false {
return nil, fmt.Errorf("invalid token reference: %q", token)
// Validate returns an error if Responses does not comply with the OpenAPI spec.
func (responses *Responses) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if responses.Len() == 0 {
return errors.New("the responses object MUST contain at least one response code")
}
if ref != nil && ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
keys := make([]string, 0, responses.Len())
for key := range responses.Map() {
keys = append(keys, key)
}
return ref.Value, nil
sort.Strings(keys)
for _, key := range keys {
v := responses.Value(key)
if err := v.Validate(ctx); err != nil {
return err
}
}
return validateExtensions(ctx, responses.Extensions)
}
// Response is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#response-object
type Response struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Description *string `json:"description,omitempty" yaml:"description,omitempty"`
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
Content Content `json:"content,omitempty" yaml:"content,omitempty"`
@ -86,23 +135,102 @@ func (response *Response) WithJSONSchemaRef(schema *SchemaRef) *Response {
return response
}
func (response *Response) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(response)
// MarshalJSON returns the JSON encoding of Response.
func (response Response) MarshalJSON() ([]byte, error) {
x, err := response.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Response.
func (response Response) MarshalYAML() (any, error) {
m := make(map[string]any, 4+len(response.Extensions))
for k, v := range response.Extensions {
m[k] = v
}
if x := response.Description; x != nil {
m["description"] = x
}
if x := response.Headers; len(x) != 0 {
m["headers"] = x
}
if x := response.Content; len(x) != 0 {
m["content"] = x
}
if x := response.Links; len(x) != 0 {
m["links"] = x
}
return m, nil
}
// UnmarshalJSON sets Response to a copy of data.
func (response *Response) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, response)
type ResponseBis Response
var x ResponseBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "description")
delete(x.Extensions, "headers")
delete(x.Extensions, "content")
delete(x.Extensions, "links")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*response = Response(x)
return nil
}
func (value *Response) Validate(ctx context.Context) error {
if value.Description == nil {
// Validate returns an error if Response does not comply with the OpenAPI spec.
func (response *Response) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if response.Description == nil {
return errors.New("a short description of the response is required")
}
if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled {
vo.examplesValidationAsReq, vo.examplesValidationAsRes = false, true
}
if content := value.Content; content != nil {
if content := response.Content; content != nil {
if err := content.Validate(ctx); err != nil {
return err
}
}
return nil
headers := make([]string, 0, len(response.Headers))
for name := range response.Headers {
headers = append(headers, name)
}
sort.Strings(headers)
for _, name := range headers {
header := response.Headers[name]
if err := header.Validate(ctx); err != nil {
return err
}
}
links := make([]string, 0, len(response.Links))
for name := range response.Links {
links = append(links, name)
}
sort.Strings(links)
for _, name := range links {
link := response.Links[name]
if err := link.Validate(ctx); err != nil {
return err
}
}
return validateExtensions(ctx, response.Extensions)
}
// UnmarshalJSON sets ResponseBodies to a copy of data.
func (responseBodies *ResponseBodies) UnmarshalJSON(data []byte) (err error) {
*responseBodies, _, err = unmarshalStringMapP[ResponseRef](data)
return
}

File diff suppressed because it is too large Load Diff

View File

@ -2,104 +2,170 @@ package openapi3
import (
"fmt"
"net"
"math"
"net/netip"
"regexp"
)
type (
// FormatValidator is an interface for custom format validators.
FormatValidator[T any] interface {
Validate(value T) error
}
// StringFormatValidator is a type alias for FormatValidator[string]
StringFormatValidator = FormatValidator[string]
// NumberFormatValidator is a type alias for FormatValidator[float64]
NumberFormatValidator = FormatValidator[float64]
// IntegerFormatValidator is a type alias for FormatValidator[int64]
IntegerFormatValidator = FormatValidator[int64]
)
var (
// SchemaStringFormats is a map of custom string format validators.
SchemaStringFormats = make(map[string]StringFormatValidator)
// SchemaNumberFormats is a map of custom number format validators.
SchemaNumberFormats = make(map[string]NumberFormatValidator)
// SchemaIntegerFormats is a map of custom integer format validators.
SchemaIntegerFormats = make(map[string]IntegerFormatValidator)
)
const (
// FormatOfStringForUUIDOfRFC4122 is an optional predefined format for UUID v1-v5 as specified by RFC4122
FormatOfStringForUUIDOfRFC4122 = `^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`
FormatOfStringForUUIDOfRFC4122 = `^(?:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000)$`
// FormatOfStringForEmail pattern catches only some suspiciously wrong-looking email addresses.
// Use DefineStringFormat(...) if you need something stricter.
FormatOfStringForEmail = `^[^@]+@[^@<>",\s]+$`
// FormatOfStringByte is a regexp for base64-encoded characters, for example, "U3dhZ2dlciByb2Nrcw=="
FormatOfStringByte = `(^$|^[a-zA-Z0-9+/\-_]*=*$)`
// FormatOfStringDate is a RFC3339 date format regexp, for example "2017-07-21".
FormatOfStringDate = `^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])$`
// FormatOfStringDateTime is a RFC3339 date-time format regexp, for example "2017-07-21T17:32:28Z".
FormatOfStringDateTime = `^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$`
)
//FormatCallback custom check on exotic formats
type FormatCallback func(Val string) error
type Format struct {
regexp *regexp.Regexp
callback FormatCallback
func init() {
DefineStringFormatValidator("byte", NewRegexpFormatValidator(FormatOfStringByte))
DefineStringFormatValidator("date", NewRegexpFormatValidator(FormatOfStringDate))
DefineStringFormatValidator("date-time", NewRegexpFormatValidator(FormatOfStringDateTime))
DefineIntegerFormatValidator("int32", NewRangeFormatValidator(int64(math.MinInt32), int64(math.MaxInt32)))
DefineIntegerFormatValidator("int64", NewRangeFormatValidator(int64(math.MinInt64), int64(math.MaxInt64)))
}
//SchemaStringFormats allows for validating strings format
var SchemaStringFormats = make(map[string]Format, 8)
// DefineIPv4Format opts in ipv4 format validation on top of OAS 3 spec
func DefineIPv4Format() {
DefineStringFormatValidator("ipv4", NewIPValidator(true))
}
//DefineStringFormat Defines a new regexp pattern for a given format
func DefineStringFormat(name string, pattern string) {
// DefineIPv6Format opts in ipv6 format validation on top of OAS 3 spec
func DefineIPv6Format() {
DefineStringFormatValidator("ipv6", NewIPValidator(false))
}
type stringRegexpFormatValidator struct {
re *regexp.Regexp
}
func (s stringRegexpFormatValidator) Validate(value string) error {
if !s.re.MatchString(value) {
return fmt.Errorf(`string doesn't match pattern "%s"`, s.re.String())
}
return nil
}
type callbackValidator[T any] struct {
fn func(T) error
}
func (c callbackValidator[T]) Validate(value T) error {
return c.fn(value)
}
type rangeFormat[T int64 | float64] struct {
min, max T
}
func (r rangeFormat[T]) Validate(value T) error {
if value < r.min || value > r.max {
return fmt.Errorf("value should be between %v and %v", r.min, r.max)
}
return nil
}
// NewRangeFormatValidator creates a new FormatValidator that validates the value is within a given range.
func NewRangeFormatValidator[T int64 | float64](min, max T) FormatValidator[T] {
return rangeFormat[T]{min: min, max: max}
}
// NewRegexpFormatValidator creates a new FormatValidator that uses a regular expression to validate the value.
func NewRegexpFormatValidator(pattern string) StringFormatValidator {
re, err := regexp.Compile(pattern)
if err != nil {
err := fmt.Errorf("format %q has invalid pattern %q: %v", name, pattern, err)
err := fmt.Errorf("string regexp format has invalid pattern %q: %w", pattern, err)
panic(err)
}
SchemaStringFormats[name] = Format{regexp: re}
return stringRegexpFormatValidator{re: re}
}
// DefineStringFormatCallback adds a validation function for a specific schema format entry
func DefineStringFormatCallback(name string, callback FormatCallback) {
SchemaStringFormats[name] = Format{callback: callback}
// NewCallbackValidator creates a new FormatValidator that uses a callback function to validate the value.
func NewCallbackValidator[T any](fn func(T) error) FormatValidator[T] {
return callbackValidator[T]{fn: fn}
}
func validateIP(ip string) (*net.IP, error) {
parsed := net.ParseIP(ip)
if parsed == nil {
return nil, &SchemaError{
// DefineStringFormatValidator defines a custom format validator for a given string format.
func DefineStringFormatValidator(name string, validator StringFormatValidator) {
SchemaStringFormats[name] = validator
}
// DefineNumberFormatValidator defines a custom format validator for a given number format.
func DefineNumberFormatValidator(name string, validator NumberFormatValidator) {
SchemaNumberFormats[name] = validator
}
// DefineIntegerFormatValidator defines a custom format validator for a given integer format.
func DefineIntegerFormatValidator(name string, validator IntegerFormatValidator) {
SchemaIntegerFormats[name] = validator
}
// DefineStringFormat defines a regexp pattern for a given string format
//
// Deprecated: Use openapi3.DefineStringFormatValidator(name, NewRegexpFormatValidator(pattern)) instead.
func DefineStringFormat(name string, pattern string) {
DefineStringFormatValidator(name, NewRegexpFormatValidator(pattern))
}
// DefineStringFormatCallback defines a callback function for a given string format
//
// Deprecated: Use openapi3.DefineStringFormatValidator(name, NewCallbackValidator(fn)) instead.
func DefineStringFormatCallback(name string, callback func(string) error) {
DefineStringFormatValidator(name, NewCallbackValidator(callback))
}
// NewIPValidator creates a new FormatValidator that validates the value is an IP address.
func NewIPValidator(isIPv4 bool) FormatValidator[string] {
return callbackValidator[string]{fn: func(ip string) error {
addr, err := netip.ParseAddr(ip)
if err != nil {
return &SchemaError{
Value: ip,
Reason: "Not an IP address",
}
}
return &parsed, nil
}
func validateIPv4(ip string) error {
parsed, err := validateIP(ip)
if err != nil {
return err
}
if parsed.To4() == nil {
if isIPv4 && !addr.Is4() {
return &SchemaError{
Value: ip,
Reason: "Not an IPv4 address (it's IPv6)",
}
}
return nil
}
func validateIPv6(ip string) error {
parsed, err := validateIP(ip)
if err != nil {
return err
}
if parsed.To4() != nil {
if !isIPv4 && !addr.Is6() {
return &SchemaError{
Value: ip,
Reason: "Not an IPv6 address (it's IPv4)",
}
}
return nil
}
func init() {
// This pattern catches only some suspiciously wrong-looking email addresses.
// Use DefineStringFormat(...) if you need something stricter.
DefineStringFormat("email", `^[^@]+@[^@<>",\s]+$`)
// Base64
// The pattern supports base64 and b./ase64url. Padding ('=') is supported.
DefineStringFormat("byte", `(^$|^[a-zA-Z0-9+/\-_]*=*$)`)
// date
DefineStringFormat("date", `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)$`)
// date-time
DefineStringFormat("date-time", `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)T[0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$`)
}
// DefineIPv4Format opts in ipv4 format validation on top of OAS 3 spec
func DefineIPv4Format() {
DefineStringFormatCallback("ipv4", validateIPv4)
}
// DefineIPv6Format opts in ipv6 format validation on top of OAS 3 spec
func DefineIPv6Format() {
DefineStringFormatCallback("ipv6", validateIPv6)
}}
}

View File

@ -0,0 +1,35 @@
package openapi3
import (
"fmt"
"regexp"
)
var patRewriteCodepoints = regexp.MustCompile(`(?P<replaced_with_slash_x>\\u)(?P<code>[0-9A-F]{4})`)
// See https://pkg.go.dev/regexp/syntax
func intoGoRegexp(re string) string {
return patRewriteCodepoints.ReplaceAllString(re, `\x{${code}}`)
}
// NOTE: racey WRT [writes to schema.Pattern] vs [reads schema.Pattern then writes to compiledPatterns]
func (schema *Schema) compilePattern(c RegexCompilerFunc) (cp RegexMatcher, err error) {
pattern := schema.Pattern
if c != nil {
cp, err = c(pattern)
} else {
cp, err = regexp.Compile(intoGoRegexp(pattern))
}
if err != nil {
err = &SchemaError{
Schema: schema,
SchemaField: "pattern",
Origin: err,
Reason: fmt.Sprintf("cannot compile pattern %q: %v", pattern, err),
}
return
}
var _ bool = compiledPatterns.CompareAndSwap(pattern, nil, cp)
return
}

View File

@ -1,12 +1,33 @@
package openapi3
import (
"sync"
)
// SchemaValidationOption describes options a user has when validating request / response bodies.
type SchemaValidationOption func(*schemaValidationSettings)
type RegexCompilerFunc func(expr string) (RegexMatcher, error)
type RegexMatcher interface {
MatchString(s string) bool
}
type schemaValidationSettings struct {
failfast bool
multiError bool
asreq, asrep bool // exclusive (XOR) fields
formatValidationEnabled bool
patternValidationDisabled bool
readOnlyValidationDisabled bool
writeOnlyValidationDisabled bool
regexCompiler RegexCompilerFunc
onceSettingDefaults sync.Once
defaultsSet func()
customizeMessageError func(err *SchemaError) string
}
// FailFast returns schema validation errors quicker.
@ -21,10 +42,47 @@ func MultiErrors() SchemaValidationOption {
func VisitAsRequest() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.asreq, s.asrep = true, false }
}
func VisitAsResponse() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.asreq, s.asrep = false, true }
}
// EnableFormatValidation setting makes Validate not return an error when validating documents that mention schema formats that are not defined by the OpenAPIv3 specification.
func EnableFormatValidation() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.formatValidationEnabled = true }
}
// DisablePatternValidation setting makes Validate not return an error when validating patterns that are not supported by the Go regexp engine.
func DisablePatternValidation() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.patternValidationDisabled = true }
}
// DisableReadOnlyValidation setting makes Validate not return an error when validating properties marked as read-only
func DisableReadOnlyValidation() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.readOnlyValidationDisabled = true }
}
// DisableWriteOnlyValidation setting makes Validate not return an error when validating properties marked as write-only
func DisableWriteOnlyValidation() SchemaValidationOption {
return func(s *schemaValidationSettings) { s.writeOnlyValidationDisabled = true }
}
// DefaultsSet executes the given callback (once) IFF schema validation set default values.
func DefaultsSet(f func()) SchemaValidationOption {
return func(s *schemaValidationSettings) { s.defaultsSet = f }
}
// SetSchemaErrorMessageCustomizer allows to override the schema error message.
// If the passed function returns an empty string, it returns to the previous Error() implementation.
func SetSchemaErrorMessageCustomizer(f func(err *SchemaError) string) SchemaValidationOption {
return func(s *schemaValidationSettings) { s.customizeMessageError = f }
}
// SetSchemaRegexCompiler allows to override the regex implementation used to validate field "pattern".
func SetSchemaRegexCompiler(c RegexCompilerFunc) SchemaValidationOption {
return func(s *schemaValidationSettings) { s.regexCompiler = c }
}
func newSchemaValidationSettings(opts ...SchemaValidationOption) *schemaValidationSettings {
settings := &schemaValidationSettings{}
for _, opt := range opts {

View File

@ -15,15 +15,20 @@ func (srs *SecurityRequirements) With(securityRequirement SecurityRequirement) *
return srs
}
func (value SecurityRequirements) Validate(ctx context.Context) error {
for _, item := range value {
if err := item.Validate(ctx); err != nil {
// Validate returns an error if SecurityRequirements does not comply with the OpenAPI spec.
func (srs SecurityRequirements) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
for _, security := range srs {
if err := security.Validate(ctx); err != nil {
return err
}
}
return nil
}
// SecurityRequirement is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-requirement-object
type SecurityRequirement map[string][]string
func NewSecurityRequirement() SecurityRequirement {
@ -38,6 +43,15 @@ func (security SecurityRequirement) Authenticate(provider string, scopes ...stri
return security
}
func (value SecurityRequirement) Validate(ctx context.Context) error {
// Validate returns an error if SecurityRequirement does not comply with the OpenAPI spec.
func (security *SecurityRequirement) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
return nil
}
// UnmarshalJSON sets SecurityRequirement to a copy of data.
func (security *SecurityRequirement) UnmarshalJSON(data []byte) (err error) {
*security, _, err = unmarshalStringMap[[]string](data)
return
}

View File

@ -2,31 +2,17 @@ package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
"net/url"
)
type SecuritySchemes map[string]*SecuritySchemeRef
func (s SecuritySchemes) JSONLookup(token string) (interface{}, error) {
ref, ok := s[token]
if ref == nil || ok == false {
return nil, fmt.Errorf("object has no field %q", token)
}
if ref.Ref != "" {
return &Ref{Ref: ref.Ref}, nil
}
return ref.Value, nil
}
var _ jsonpointer.JSONPointable = (*SecuritySchemes)(nil)
// SecurityScheme is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object
type SecurityScheme struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
@ -65,12 +51,70 @@ func NewJWTSecurityScheme() *SecurityScheme {
}
}
func (ss *SecurityScheme) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(ss)
// MarshalJSON returns the JSON encoding of SecurityScheme.
func (ss SecurityScheme) MarshalJSON() ([]byte, error) {
x, err := ss.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of SecurityScheme.
func (ss SecurityScheme) MarshalYAML() (any, error) {
m := make(map[string]any, 8+len(ss.Extensions))
for k, v := range ss.Extensions {
m[k] = v
}
if x := ss.Type; x != "" {
m["type"] = x
}
if x := ss.Description; x != "" {
m["description"] = x
}
if x := ss.Name; x != "" {
m["name"] = x
}
if x := ss.In; x != "" {
m["in"] = x
}
if x := ss.Scheme; x != "" {
m["scheme"] = x
}
if x := ss.BearerFormat; x != "" {
m["bearerFormat"] = x
}
if x := ss.Flows; x != nil {
m["flows"] = x
}
if x := ss.OpenIdConnectUrl; x != "" {
m["openIdConnectUrl"] = x
}
return m, nil
}
// UnmarshalJSON sets SecurityScheme to a copy of data.
func (ss *SecurityScheme) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, ss)
type SecuritySchemeBis SecurityScheme
var x SecuritySchemeBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "type")
delete(x.Extensions, "description")
delete(x.Extensions, "name")
delete(x.Extensions, "in")
delete(x.Extensions, "scheme")
delete(x.Extensions, "bearerFormat")
delete(x.Extensions, "flows")
delete(x.Extensions, "openIdConnectUrl")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*ss = SecurityScheme(x)
return nil
}
func (ss *SecurityScheme) WithType(value string) *SecurityScheme {
@ -103,15 +147,18 @@ func (ss *SecurityScheme) WithBearerFormat(value string) *SecurityScheme {
return ss
}
func (value *SecurityScheme) Validate(ctx context.Context) error {
// Validate returns an error if SecurityScheme does not comply with the OpenAPI spec.
func (ss *SecurityScheme) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
hasIn := false
hasBearerFormat := false
hasFlow := false
switch value.Type {
switch ss.Type {
case "apiKey":
hasIn = true
case "http":
scheme := value.Scheme
scheme := ss.Scheme
switch scheme {
case "bearer":
hasBearerFormat = true
@ -122,52 +169,57 @@ func (value *SecurityScheme) Validate(ctx context.Context) error {
case "oauth2":
hasFlow = true
case "openIdConnect":
if value.OpenIdConnectUrl == "" {
return fmt.Errorf("no OIDC URL found for openIdConnect security scheme %q", value.Name)
if ss.OpenIdConnectUrl == "" {
return fmt.Errorf("no OIDC URL found for openIdConnect security scheme %q", ss.Name)
}
default:
return fmt.Errorf("security scheme 'type' can't be %q", value.Type)
return fmt.Errorf("security scheme 'type' can't be %q", ss.Type)
}
// Validate "in" and "name"
if hasIn {
switch value.In {
switch ss.In {
case "query", "header", "cookie":
default:
return fmt.Errorf("security scheme of type 'apiKey' should have 'in'. It can be 'query', 'header' or 'cookie', not %q", value.In)
return fmt.Errorf("security scheme of type 'apiKey' should have 'in'. It can be 'query', 'header' or 'cookie', not %q", ss.In)
}
if value.Name == "" {
if ss.Name == "" {
return errors.New("security scheme of type 'apiKey' should have 'name'")
}
} else if len(value.In) > 0 {
return fmt.Errorf("security scheme of type %q can't have 'in'", value.Type)
} else if len(value.Name) > 0 {
return errors.New("security scheme of type 'apiKey' can't have 'name'")
} else if len(ss.In) > 0 {
return fmt.Errorf("security scheme of type %q can't have 'in'", ss.Type)
} else if len(ss.Name) > 0 {
return fmt.Errorf("security scheme of type %q can't have 'name'", ss.Type)
}
// Validate "format"
// "bearerFormat" is an arbitrary string so we only check if the scheme supports it
if !hasBearerFormat && len(value.BearerFormat) > 0 {
return fmt.Errorf("security scheme of type %q can't have 'bearerFormat'", value.Type)
if !hasBearerFormat && len(ss.BearerFormat) > 0 {
return fmt.Errorf("security scheme of type %q can't have 'bearerFormat'", ss.Type)
}
// Validate "flow"
if hasFlow {
flow := value.Flows
flow := ss.Flows
if flow == nil {
return fmt.Errorf("security scheme of type %q should have 'flows'", value.Type)
return fmt.Errorf("security scheme of type %q should have 'flows'", ss.Type)
}
if err := flow.Validate(ctx); err != nil {
return fmt.Errorf("security scheme 'flow' is invalid: %v", err)
return fmt.Errorf("security scheme 'flow' is invalid: %w", err)
}
} else if value.Flows != nil {
return fmt.Errorf("security scheme of type %q can't have 'flows'", value.Type)
}
return nil
} else if ss.Flows != nil {
return fmt.Errorf("security scheme of type %q can't have 'flows'", ss.Type)
}
return validateExtensions(ctx, ss.Extensions)
}
// OAuthFlows is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object
type OAuthFlows struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Implicit *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"`
Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"`
ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty" yaml:"clientCredentials,omitempty"`
@ -183,59 +235,208 @@ const (
oAuthFlowAuthorizationCode
)
func (flows *OAuthFlows) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(flows)
// MarshalJSON returns the JSON encoding of OAuthFlows.
func (flows OAuthFlows) MarshalJSON() ([]byte, error) {
x, err := flows.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of OAuthFlows.
func (flows OAuthFlows) MarshalYAML() (any, error) {
m := make(map[string]any, 4+len(flows.Extensions))
for k, v := range flows.Extensions {
m[k] = v
}
if x := flows.Implicit; x != nil {
m["implicit"] = x
}
if x := flows.Password; x != nil {
m["password"] = x
}
if x := flows.ClientCredentials; x != nil {
m["clientCredentials"] = x
}
if x := flows.AuthorizationCode; x != nil {
m["authorizationCode"] = x
}
return m, nil
}
// UnmarshalJSON sets OAuthFlows to a copy of data.
func (flows *OAuthFlows) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, flows)
type OAuthFlowsBis OAuthFlows
var x OAuthFlowsBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "implicit")
delete(x.Extensions, "password")
delete(x.Extensions, "clientCredentials")
delete(x.Extensions, "authorizationCode")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*flows = OAuthFlows(x)
return nil
}
func (flows *OAuthFlows) Validate(ctx context.Context) error {
// Validate returns an error if OAuthFlows does not comply with the OpenAPI spec.
func (flows *OAuthFlows) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if v := flows.Implicit; v != nil {
return v.Validate(ctx, oAuthFlowTypeImplicit)
if err := v.validate(ctx, oAuthFlowTypeImplicit, opts...); err != nil {
return fmt.Errorf("the OAuth flow 'implicit' is invalid: %w", err)
}
if v := flows.Password; v != nil {
return v.Validate(ctx, oAuthFlowTypePassword)
}
if v := flows.ClientCredentials; v != nil {
return v.Validate(ctx, oAuthFlowTypeClientCredentials)
}
if v := flows.AuthorizationCode; v != nil {
return v.Validate(ctx, oAuthFlowAuthorizationCode)
}
return errors.New("no OAuth flow is defined")
}
if v := flows.Password; v != nil {
if err := v.validate(ctx, oAuthFlowTypePassword, opts...); err != nil {
return fmt.Errorf("the OAuth flow 'password' is invalid: %w", err)
}
}
if v := flows.ClientCredentials; v != nil {
if err := v.validate(ctx, oAuthFlowTypeClientCredentials, opts...); err != nil {
return fmt.Errorf("the OAuth flow 'clientCredentials' is invalid: %w", err)
}
}
if v := flows.AuthorizationCode; v != nil {
if err := v.validate(ctx, oAuthFlowAuthorizationCode, opts...); err != nil {
return fmt.Errorf("the OAuth flow 'authorizationCode' is invalid: %w", err)
}
}
return validateExtensions(ctx, flows.Extensions)
}
// OAuthFlow is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flow-object
type OAuthFlow struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
RefreshURL string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"`
Scopes map[string]string `json:"scopes" yaml:"scopes"`
Scopes StringMap `json:"scopes" yaml:"scopes"` // required
}
func (flow *OAuthFlow) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(flow)
// MarshalJSON returns the JSON encoding of OAuthFlow.
func (flow OAuthFlow) MarshalJSON() ([]byte, error) {
x, err := flow.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of OAuthFlow.
func (flow OAuthFlow) MarshalYAML() (any, error) {
m := make(map[string]any, 4+len(flow.Extensions))
for k, v := range flow.Extensions {
m[k] = v
}
if x := flow.AuthorizationURL; x != "" {
m["authorizationUrl"] = x
}
if x := flow.TokenURL; x != "" {
m["tokenUrl"] = x
}
if x := flow.RefreshURL; x != "" {
m["refreshUrl"] = x
}
m["scopes"] = flow.Scopes
return m, nil
}
// UnmarshalJSON sets OAuthFlow to a copy of data.
func (flow *OAuthFlow) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, flow)
type OAuthFlowBis OAuthFlow
var x OAuthFlowBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
func (flow *OAuthFlow) Validate(ctx context.Context, typ oAuthFlowType) error {
if typ == oAuthFlowAuthorizationCode || typ == oAuthFlowTypeImplicit {
if v := flow.AuthorizationURL; v == "" {
return errors.New("an OAuth flow is missing 'authorizationUrl in authorizationCode or implicit '")
}
}
if typ != oAuthFlowTypeImplicit {
if v := flow.TokenURL; v == "" {
return errors.New("an OAuth flow is missing 'tokenUrl in not implicit'")
}
}
if v := flow.Scopes; v == nil {
return errors.New("an OAuth flow is missing 'scopes'")
delete(x.Extensions, originKey)
delete(x.Extensions, "authorizationUrl")
delete(x.Extensions, "tokenUrl")
delete(x.Extensions, "refreshUrl")
delete(x.Extensions, "scopes")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*flow = OAuthFlow(x)
return nil
}
// Validate returns an error if OAuthFlows does not comply with the OpenAPI spec.
func (flow *OAuthFlow) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if v := flow.RefreshURL; v != "" {
if _, err := url.Parse(v); err != nil {
return fmt.Errorf("field 'refreshUrl' is invalid: %w", err)
}
}
if flow.Scopes == nil {
return errors.New("field 'scopes' is missing")
}
return validateExtensions(ctx, flow.Extensions)
}
func (flow *OAuthFlow) validate(ctx context.Context, typ oAuthFlowType, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
typeIn := func(types ...oAuthFlowType) bool {
for _, ty := range types {
if ty == typ {
return true
}
}
return false
}
if in := typeIn(oAuthFlowTypeImplicit, oAuthFlowAuthorizationCode); true {
switch {
case flow.AuthorizationURL == "" && in:
return errors.New("field 'authorizationUrl' is empty or missing")
case flow.AuthorizationURL != "" && !in:
return errors.New("field 'authorizationUrl' should not be set")
case flow.AuthorizationURL != "":
if _, err := url.Parse(flow.AuthorizationURL); err != nil {
return fmt.Errorf("field 'authorizationUrl' is invalid: %w", err)
}
}
}
if in := typeIn(oAuthFlowTypePassword, oAuthFlowTypeClientCredentials, oAuthFlowAuthorizationCode); true {
switch {
case flow.TokenURL == "" && in:
return errors.New("field 'tokenUrl' is empty or missing")
case flow.TokenURL != "" && !in:
return errors.New("field 'tokenUrl' should not be set")
case flow.TokenURL != "":
if _, err := url.Parse(flow.TokenURL); err != nil {
return fmt.Errorf("field 'tokenUrl' is invalid: %w", err)
}
}
}
return flow.Validate(ctx, opts...)
}
// UnmarshalJSON sets SecuritySchemes to a copy of data.
func (securitySchemes *SecuritySchemes) UnmarshalJSON(data []byte) (err error) {
*securitySchemes, _, err = unmarshalStringMapP[SecuritySchemeRef](data)
return
}

View File

@ -2,21 +2,22 @@ package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"math"
"net/url"
"sort"
"strings"
"github.com/getkin/kin-openapi/jsoninfo"
)
// Servers is specified by OpenAPI/Swagger standard version 3.0.
// Servers is specified by OpenAPI/Swagger standard version 3.
type Servers []*Server
// Validate ensures servers are per the OpenAPIv3 specification.
func (value Servers) Validate(ctx context.Context) error {
for _, v := range value {
// Validate returns an error if Servers does not comply with the OpenAPI spec.
func (servers Servers) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
for _, v := range servers {
if err := v.Validate(ctx); err != nil {
return err
}
@ -24,6 +25,14 @@ func (value Servers) Validate(ctx context.Context) error {
return nil
}
// BasePath returns the base path of the first server in the list, or /.
func (servers Servers) BasePath() (string, error) {
for _, server := range servers {
return server.BasePath()
}
return "/", nil
}
func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string) {
rawURL := parsedURL.String()
if i := strings.IndexByte(rawURL, '?'); i >= 0 {
@ -38,20 +47,83 @@ func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string)
return nil, nil, ""
}
// Server is specified by OpenAPI/Swagger standard version 3.0.
// Server is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-object
type Server struct {
ExtensionProps
URL string `json:"url" yaml:"url"`
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
URL string `json:"url" yaml:"url"` // Required
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Variables map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"`
}
func (server *Server) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(server)
// BasePath returns the base path extracted from the default values of variables, if any.
// Assumes a valid struct (per Validate()).
func (server *Server) BasePath() (string, error) {
if server == nil {
return "/", nil
}
uri := server.URL
for name, svar := range server.Variables {
uri = strings.ReplaceAll(uri, "{"+name+"}", svar.Default)
}
u, err := url.ParseRequestURI(uri)
if err != nil {
return "", err
}
if bp := u.Path; bp != "" {
return bp, nil
}
return "/", nil
}
// MarshalJSON returns the JSON encoding of Server.
func (server Server) MarshalJSON() ([]byte, error) {
x, err := server.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of Server.
func (server Server) MarshalYAML() (any, error) {
m := make(map[string]any, 3+len(server.Extensions))
for k, v := range server.Extensions {
m[k] = v
}
m["url"] = server.URL
if x := server.Description; x != "" {
m["description"] = x
}
if x := server.Variables; len(x) != 0 {
m["variables"] = x
}
return m, nil
}
// UnmarshalJSON sets Server to a copy of data.
func (server *Server) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, server)
type ServerBis Server
var x ServerBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "url")
delete(x.Extensions, "description")
delete(x.Extensions, "variables")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*server = Server(x)
return nil
}
func (server Server) ParameterNames() ([]string, error) {
@ -101,7 +173,7 @@ func (server Server) MatchRawURL(input string) ([]string, string, bool) {
} else if ns < 0 {
i = np
} else {
i = int(math.Min(float64(np), float64(ns)))
i = min(np, ns)
}
if i < 0 {
i = len(input)
@ -125,51 +197,109 @@ func (server Server) MatchRawURL(input string) ([]string, string, bool) {
return params, input, true
}
func (value *Server) Validate(ctx context.Context) (err error) {
if value.URL == "" {
// Validate returns an error if Server does not comply with the OpenAPI spec.
func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) (err error) {
ctx = WithValidationOptions(ctx, opts...)
if server.URL == "" {
return errors.New("value of url must be a non-empty string")
}
opening, closing := strings.Count(value.URL, "{"), strings.Count(value.URL, "}")
opening, closing := strings.Count(server.URL, "{"), strings.Count(server.URL, "}")
if opening != closing {
return errors.New("server URL has mismatched { and }")
}
if opening != len(value.Variables) {
if opening != len(server.Variables) {
return errors.New("server has undeclared variables")
}
for name, v := range value.Variables {
if !strings.Contains(value.URL, fmt.Sprintf("{%s}", name)) {
variables := make([]string, 0, len(server.Variables))
for name := range server.Variables {
variables = append(variables, name)
}
sort.Strings(variables)
for _, name := range variables {
v := server.Variables[name]
if !strings.Contains(server.URL, "{"+name+"}") {
return errors.New("server has undeclared variables")
}
if err = v.Validate(ctx); err != nil {
return
}
}
return
return validateExtensions(ctx, server.Extensions)
}
// ServerVariable is specified by OpenAPI/Swagger standard version 3.0.
// ServerVariable is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object
type ServerVariable struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"`
Default string `json:"default,omitempty" yaml:"default,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
}
func (serverVariable *ServerVariable) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(serverVariable)
// MarshalJSON returns the JSON encoding of ServerVariable.
func (serverVariable ServerVariable) MarshalJSON() ([]byte, error) {
x, err := serverVariable.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of ServerVariable.
func (serverVariable ServerVariable) MarshalYAML() (any, error) {
m := make(map[string]any, 4+len(serverVariable.Extensions))
for k, v := range serverVariable.Extensions {
m[k] = v
}
if x := serverVariable.Enum; len(x) != 0 {
m["enum"] = x
}
if x := serverVariable.Default; x != "" {
m["default"] = x
}
if x := serverVariable.Description; x != "" {
m["description"] = x
}
return m, nil
}
// UnmarshalJSON sets ServerVariable to a copy of data.
func (serverVariable *ServerVariable) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, serverVariable)
type ServerVariableBis ServerVariable
var x ServerVariableBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "enum")
delete(x.Extensions, "default")
delete(x.Extensions, "description")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*serverVariable = ServerVariable(x)
return nil
}
func (value *ServerVariable) Validate(ctx context.Context) error {
if value.Default == "" {
data, err := value.MarshalJSON()
// Validate returns an error if ServerVariable does not comply with the OpenAPI spec.
func (serverVariable *ServerVariable) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if serverVariable.Default == "" {
data, err := serverVariable.MarshalJSON()
if err != nil {
return err
}
return fmt.Errorf("field default is required in %s", data)
}
return nil
return validateExtensions(ctx, serverVariable.Extensions)
}

View File

@ -0,0 +1,88 @@
package openapi3
import "encoding/json"
// StringMap is a map[string]string that ignores the origin in the underlying json representation.
type StringMap map[string]string
// UnmarshalJSON sets StringMap to a copy of data.
func (stringMap *StringMap) UnmarshalJSON(data []byte) (err error) {
*stringMap, _, err = unmarshalStringMap[string](data)
return
}
// unmarshalStringMapP unmarshals given json into a map[string]*V
func unmarshalStringMapP[V any](data []byte) (map[string]*V, *Origin, error) {
var m map[string]any
if err := json.Unmarshal(data, &m); err != nil {
return nil, nil, err
}
origin, err := popOrigin(m, originKey)
if err != nil {
return nil, nil, err
}
result := make(map[string]*V, len(m))
for k, v := range m {
value, err := deepCast[V](v)
if err != nil {
return nil, nil, err
}
result[k] = value
}
return result, origin, nil
}
// unmarshalStringMap unmarshals given json into a map[string]V
func unmarshalStringMap[V any](data []byte) (map[string]V, *Origin, error) {
var m map[string]any
if err := json.Unmarshal(data, &m); err != nil {
return nil, nil, err
}
origin, err := popOrigin(m, originKey)
if err != nil {
return nil, nil, err
}
result := make(map[string]V, len(m))
for k, v := range m {
value, err := deepCast[V](v)
if err != nil {
return nil, nil, err
}
result[k] = *value
}
return result, origin, nil
}
// deepCast casts any value to a value of type V.
func deepCast[V any](value any) (*V, error) {
data, err := json.Marshal(value)
if err != nil {
return nil, err
}
var result V
if err = json.Unmarshal(data, &result); err != nil {
return nil, err
}
return &result, nil
}
// popOrigin removes the origin from the map and returns it.
func popOrigin(m map[string]any, key string) (*Origin, error) {
if !IncludeOrigin {
return nil, nil
}
origin, err := deepCast[Origin](m[key])
if err != nil {
return nil, err
}
delete(m, key)
return origin, nil
}

View File

@ -1,6 +1,10 @@
package openapi3
import "github.com/getkin/kin-openapi/jsoninfo"
import (
"context"
"encoding/json"
"fmt"
)
// Tags is specified by OpenAPI/Swagger 3.0 standard.
type Tags []*Tag
@ -14,18 +18,84 @@ func (tags Tags) Get(name string) *Tag {
return nil
}
// Validate returns an error if Tags does not comply with the OpenAPI spec.
func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
for _, v := range tags {
if err := v.Validate(ctx); err != nil {
return err
}
}
return nil
}
// Tag is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#tag-object
type Tag struct {
ExtensionProps
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
}
func (t *Tag) MarshalJSON() ([]byte, error) {
return jsoninfo.MarshalStrictStruct(t)
// MarshalJSON returns the JSON encoding of Tag.
func (t Tag) MarshalJSON() ([]byte, error) {
x, err := t.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
func (t *Tag) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, t)
// MarshalYAML returns the YAML encoding of Tag.
func (t Tag) MarshalYAML() (any, error) {
m := make(map[string]any, 3+len(t.Extensions))
for k, v := range t.Extensions {
m[k] = v
}
if x := t.Name; x != "" {
m["name"] = x
}
if x := t.Description; x != "" {
m["description"] = x
}
if x := t.ExternalDocs; x != nil {
m["externalDocs"] = x
}
return m, nil
}
// UnmarshalJSON sets Tag to a copy of data.
func (t *Tag) UnmarshalJSON(data []byte) error {
type TagBis Tag
var x TagBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "name")
delete(x.Extensions, "description")
delete(x.Extensions, "externalDocs")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*t = Tag(x)
return nil
}
// Validate returns an error if Tag does not comply with the OpenAPI spec.
func (t *Tag) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if v := t.ExternalDocs; v != nil {
if err := v.Validate(ctx); err != nil {
return fmt.Errorf("invalid external docs: %w", err)
}
}
return validateExtensions(ctx, t.Extensions)
}

View File

@ -0,0 +1,142 @@
package openapi3
import "context"
// ValidationOption allows the modification of how the OpenAPI document is validated.
type ValidationOption func(options *ValidationOptions)
// ValidationOptions provides configuration for validating OpenAPI documents.
type ValidationOptions struct {
examplesValidationAsReq, examplesValidationAsRes bool
examplesValidationDisabled bool
schemaDefaultsValidationDisabled bool
schemaFormatValidationEnabled bool
schemaPatternValidationDisabled bool
schemaExtensionsInRefProhibited bool
regexCompilerFunc RegexCompilerFunc
extraSiblingFieldsAllowed map[string]struct{}
}
type validationOptionsKey struct{}
// AllowExtraSiblingFields called as AllowExtraSiblingFields("description") makes Validate not return an error when said field appears next to a $ref.
func AllowExtraSiblingFields(fields ...string) ValidationOption {
return func(options *ValidationOptions) {
if options.extraSiblingFieldsAllowed == nil && len(fields) != 0 {
options.extraSiblingFieldsAllowed = make(map[string]struct{}, len(fields))
}
for _, field := range fields {
options.extraSiblingFieldsAllowed[field] = struct{}{}
}
}
}
// EnableSchemaFormatValidation makes Validate not return an error when validating documents that mention schema formats that are not defined by the OpenAPIv3 specification.
// By default, schema format validation is disabled.
func EnableSchemaFormatValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaFormatValidationEnabled = true
}
}
// DisableSchemaFormatValidation does the opposite of EnableSchemaFormatValidation.
// By default, schema format validation is disabled.
func DisableSchemaFormatValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaFormatValidationEnabled = false
}
}
// EnableSchemaPatternValidation does the opposite of DisableSchemaPatternValidation.
// By default, schema pattern validation is enabled.
func EnableSchemaPatternValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaPatternValidationDisabled = false
}
}
// DisableSchemaPatternValidation makes Validate not return an error when validating patterns that are not supported by the Go regexp engine.
func DisableSchemaPatternValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaPatternValidationDisabled = true
}
}
// EnableSchemaDefaultsValidation does the opposite of DisableSchemaDefaultsValidation.
// By default, schema default values are validated against their schema.
func EnableSchemaDefaultsValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaDefaultsValidationDisabled = false
}
}
// DisableSchemaDefaultsValidation disables schemas' default field validation.
// By default, schema default values are validated against their schema.
func DisableSchemaDefaultsValidation() ValidationOption {
return func(options *ValidationOptions) {
options.schemaDefaultsValidationDisabled = true
}
}
// EnableExamplesValidation does the opposite of DisableExamplesValidation.
// By default, all schema examples are validated.
func EnableExamplesValidation() ValidationOption {
return func(options *ValidationOptions) {
options.examplesValidationDisabled = false
}
}
// DisableExamplesValidation disables all example schema validation.
// By default, all schema examples are validated.
func DisableExamplesValidation() ValidationOption {
return func(options *ValidationOptions) {
options.examplesValidationDisabled = true
}
}
// AllowExtensionsWithRef allows extensions (fields starting with 'x-')
// as siblings for $ref fields. This is the default.
// Non-extension fields are prohibited unless allowed explicitly with the
// AllowExtraSiblingFields option.
func AllowExtensionsWithRef() ValidationOption {
return func(options *ValidationOptions) {
options.schemaExtensionsInRefProhibited = false
}
}
// ProhibitExtensionsWithRef causes the validation to return an
// error if extensions (fields starting with 'x-') are found as
// siblings for $ref fields. Non-extension fields are prohibited
// unless allowed explicitly with the AllowExtraSiblingFields option.
func ProhibitExtensionsWithRef() ValidationOption {
return func(options *ValidationOptions) {
options.schemaExtensionsInRefProhibited = true
}
}
// SetRegexCompiler allows to override the regex implementation used to validate
// field "pattern".
func SetRegexCompiler(c RegexCompilerFunc) ValidationOption {
return func(options *ValidationOptions) {
options.regexCompilerFunc = c
}
}
// WithValidationOptions allows adding validation options to a context object that can be used when validating any OpenAPI type.
func WithValidationOptions(ctx context.Context, opts ...ValidationOption) context.Context {
if len(opts) == 0 {
return ctx
}
options := &ValidationOptions{}
for _, opt := range opts {
opt(options)
}
return context.WithValue(ctx, validationOptionsKey{}, options)
}
func getValidationOptions(ctx context.Context) *ValidationOptions {
if options, ok := ctx.Value(validationOptionsKey{}).(*ValidationOptions); ok {
return options
}
return &ValidationOptions{}
}

View File

@ -0,0 +1,41 @@
package openapi3
func newVisited() visitedComponent {
return visitedComponent{
header: make(map[*Header]struct{}),
schema: make(map[*Schema]struct{}),
}
}
type visitedComponent struct {
header map[*Header]struct{}
schema map[*Schema]struct{}
}
// resetVisited clears visitedComponent map
// should be called before recursion over doc *T
func (doc *T) resetVisited() {
doc.visited = newVisited()
}
// isVisitedHeader returns `true` if the *Header pointer was already visited
// otherwise it returns `false`
func (doc *T) isVisitedHeader(h *Header) bool {
if _, ok := doc.visited.header[h]; ok {
return true
}
doc.visited.header[h] = struct{}{}
return false
}
// isVisitedHeader returns `true` if the *Schema pointer was already visited
// otherwise it returns `false`
func (doc *T) isVisitedSchema(s *Schema) bool {
if _, ok := doc.visited.schema[s]; ok {
return true
}
doc.visited.schema[s] = struct{}{}
return false
}

80
vendor/github.com/getkin/kin-openapi/openapi3/xml.go generated vendored Normal file
View File

@ -0,0 +1,80 @@
package openapi3
import (
"context"
"encoding/json"
)
// XML is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#xml-object
type XML struct {
Extensions map[string]any `json:"-" yaml:"-"`
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
Attribute bool `json:"attribute,omitempty" yaml:"attribute,omitempty"`
Wrapped bool `json:"wrapped,omitempty" yaml:"wrapped,omitempty"`
}
// MarshalJSON returns the JSON encoding of XML.
func (xml XML) MarshalJSON() ([]byte, error) {
x, err := xml.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(x)
}
// MarshalYAML returns the YAML encoding of XML.
func (xml XML) MarshalYAML() (any, error) {
m := make(map[string]any, 5+len(xml.Extensions))
for k, v := range xml.Extensions {
m[k] = v
}
if x := xml.Name; x != "" {
m["name"] = x
}
if x := xml.Namespace; x != "" {
m["namespace"] = x
}
if x := xml.Prefix; x != "" {
m["prefix"] = x
}
if x := xml.Attribute; x {
m["attribute"] = x
}
if x := xml.Wrapped; x {
m["wrapped"] = x
}
return m, nil
}
// UnmarshalJSON sets XML to a copy of data.
func (xml *XML) UnmarshalJSON(data []byte) error {
type XMLBis XML
var x XMLBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, originKey)
delete(x.Extensions, "name")
delete(x.Extensions, "namespace")
delete(x.Extensions, "prefix")
delete(x.Extensions, "attribute")
delete(x.Extensions, "wrapped")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*xml = XML(x)
return nil
}
// Validate returns an error if XML does not comply with the OpenAPI spec.
func (xml *XML) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
return validateExtensions(ctx, xml.Extensions)
}

61
vendor/github.com/go-openapi/jsonpointer/.golangci.yml generated vendored Normal file
View File

@ -0,0 +1,61 @@
linters-settings:
govet:
check-shadowing: true
golint:
min-confidence: 0
gocyclo:
min-complexity: 45
maligned:
suggest-new: true
dupl:
threshold: 200
goconst:
min-len: 2
min-occurrences: 3
linters:
enable-all: true
disable:
- maligned
- unparam
- lll
- gochecknoinits
- gochecknoglobals
- funlen
- godox
- gocognit
- whitespace
- wsl
- wrapcheck
- testpackage
- nlreturn
- gomnd
- exhaustivestruct
- goerr113
- errorlint
- nestif
- godot
- gofumpt
- paralleltest
- tparallel
- thelper
- ifshort
- exhaustruct
- varnamelen
- gci
- depguard
- errchkjson
- inamedparam
- nonamedreturns
- musttag
- ireturn
- forcetypeassert
- cyclop
# deprecated linters
- deadcode
- interfacer
- scopelint
- varcheck
- structcheck
- golint
- nosnakecase

View File

@ -1,6 +1,10 @@
# gojsonpointer [![Build Status](https://travis-ci.org/go-openapi/jsonpointer.svg?branch=master)](https://travis-ci.org/go-openapi/jsonpointer) [![codecov](https://codecov.io/gh/go-openapi/jsonpointer/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/jsonpointer) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
# gojsonpointer [![Build Status](https://github.com/go-openapi/jsonpointer/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/jsonpointer/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/jsonpointer/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/jsonpointer)
[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/jsonpointer/master/LICENSE)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/jsonpointer.svg)](https://pkg.go.dev/github.com/go-openapi/jsonpointer)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/jsonpointer)](https://goreportcard.com/report/github.com/go-openapi/jsonpointer)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/jsonpointer/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/jsonpointer?status.svg)](http://godoc.org/github.com/go-openapi/jsonpointer)
An implementation of JSON Pointer - Go language
## Status

View File

@ -26,6 +26,7 @@
package jsonpointer
import (
"encoding/json"
"errors"
"fmt"
"reflect"
@ -40,6 +41,7 @@ const (
pointerSeparator = `/`
invalidStart = `JSON pointer must be empty or start with a "` + pointerSeparator
notFound = `Can't find the pointer in the document`
)
var jsonPointableType = reflect.TypeOf(new(JSONPointable)).Elem()
@ -48,13 +50,13 @@ var jsonSetableType = reflect.TypeOf(new(JSONSetable)).Elem()
// JSONPointable is an interface for structs to implement when they need to customize the
// json pointer process
type JSONPointable interface {
JSONLookup(string) (interface{}, error)
JSONLookup(string) (any, error)
}
// JSONSetable is an interface for structs to implement when they need to customize the
// json pointer process
type JSONSetable interface {
JSONSet(string, interface{}) error
JSONSet(string, any) error
}
// New creates a new json pointer for the given string
@ -81,9 +83,7 @@ func (p *Pointer) parse(jsonPointerString string) error {
err = errors.New(invalidStart)
} else {
referenceTokens := strings.Split(jsonPointerString, pointerSeparator)
for _, referenceToken := range referenceTokens[1:] {
p.referenceTokens = append(p.referenceTokens, referenceToken)
}
p.referenceTokens = append(p.referenceTokens, referenceTokens[1:]...)
}
}
@ -91,38 +91,58 @@ func (p *Pointer) parse(jsonPointerString string) error {
}
// Get uses the pointer to retrieve a value from a JSON document
func (p *Pointer) Get(document interface{}) (interface{}, reflect.Kind, error) {
func (p *Pointer) Get(document any) (any, reflect.Kind, error) {
return p.get(document, swag.DefaultJSONNameProvider)
}
// Set uses the pointer to set a value from a JSON document
func (p *Pointer) Set(document interface{}, value interface{}) (interface{}, error) {
func (p *Pointer) Set(document any, value any) (any, error) {
return document, p.set(document, value, swag.DefaultJSONNameProvider)
}
// GetForToken gets a value for a json pointer token 1 level deep
func GetForToken(document interface{}, decodedToken string) (interface{}, reflect.Kind, error) {
func GetForToken(document any, decodedToken string) (any, reflect.Kind, error) {
return getSingleImpl(document, decodedToken, swag.DefaultJSONNameProvider)
}
// SetForToken gets a value for a json pointer token 1 level deep
func SetForToken(document interface{}, decodedToken string, value interface{}) (interface{}, error) {
func SetForToken(document any, decodedToken string, value any) (any, error) {
return document, setSingleImpl(document, value, decodedToken, swag.DefaultJSONNameProvider)
}
func getSingleImpl(node interface{}, decodedToken string, nameProvider *swag.NameProvider) (interface{}, reflect.Kind, error) {
func isNil(input any) bool {
if input == nil {
return true
}
kind := reflect.TypeOf(input).Kind()
switch kind { //nolint:exhaustive
case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan:
return reflect.ValueOf(input).IsNil()
default:
return false
}
}
func getSingleImpl(node any, decodedToken string, nameProvider *swag.NameProvider) (any, reflect.Kind, error) {
rValue := reflect.Indirect(reflect.ValueOf(node))
kind := rValue.Kind()
if isNil(node) {
return nil, kind, fmt.Errorf("nil value has not field %q", decodedToken)
}
if rValue.Type().Implements(jsonPointableType) {
r, err := node.(JSONPointable).JSONLookup(decodedToken)
switch typed := node.(type) {
case JSONPointable:
r, err := typed.JSONLookup(decodedToken)
if err != nil {
return nil, kind, err
}
return r, kind, nil
case *any: // case of a pointer to interface, that is not resolved by reflect.Indirect
return getSingleImpl(*typed, decodedToken, nameProvider)
}
switch kind {
switch kind { //nolint:exhaustive
case reflect.Struct:
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
if !ok {
@ -159,7 +179,7 @@ func getSingleImpl(node interface{}, decodedToken string, nameProvider *swag.Nam
}
func setSingleImpl(node, data interface{}, decodedToken string, nameProvider *swag.NameProvider) error {
func setSingleImpl(node, data any, decodedToken string, nameProvider *swag.NameProvider) error {
rValue := reflect.Indirect(reflect.ValueOf(node))
if ns, ok := node.(JSONSetable); ok { // pointer impl
@ -170,7 +190,7 @@ func setSingleImpl(node, data interface{}, decodedToken string, nameProvider *sw
return node.(JSONSetable).JSONSet(decodedToken, data)
}
switch rValue.Kind() {
switch rValue.Kind() { //nolint:exhaustive
case reflect.Struct:
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
if !ok {
@ -210,7 +230,7 @@ func setSingleImpl(node, data interface{}, decodedToken string, nameProvider *sw
}
func (p *Pointer) get(node interface{}, nameProvider *swag.NameProvider) (interface{}, reflect.Kind, error) {
func (p *Pointer) get(node any, nameProvider *swag.NameProvider) (any, reflect.Kind, error) {
if nameProvider == nil {
nameProvider = swag.DefaultJSONNameProvider
@ -231,8 +251,7 @@ func (p *Pointer) get(node interface{}, nameProvider *swag.NameProvider) (interf
if err != nil {
return nil, knd, err
}
node, kind = r, knd
node = r
}
rValue := reflect.ValueOf(node)
@ -241,11 +260,11 @@ func (p *Pointer) get(node interface{}, nameProvider *swag.NameProvider) (interf
return node, kind, nil
}
func (p *Pointer) set(node, data interface{}, nameProvider *swag.NameProvider) error {
func (p *Pointer) set(node, data any, nameProvider *swag.NameProvider) error {
knd := reflect.ValueOf(node).Kind()
if knd != reflect.Ptr && knd != reflect.Struct && knd != reflect.Map && knd != reflect.Slice && knd != reflect.Array {
return fmt.Errorf("only structs, pointers, maps and slices are supported for setting values")
return errors.New("only structs, pointers, maps and slices are supported for setting values")
}
if nameProvider == nil {
@ -284,7 +303,7 @@ func (p *Pointer) set(node, data interface{}, nameProvider *swag.NameProvider) e
continue
}
switch kind {
switch kind { //nolint:exhaustive
case reflect.Struct:
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
if !ok {
@ -363,6 +382,128 @@ func (p *Pointer) String() string {
return pointerString
}
func (p *Pointer) Offset(document string) (int64, error) {
dec := json.NewDecoder(strings.NewReader(document))
var offset int64
for _, ttk := range p.DecodedTokens() {
tk, err := dec.Token()
if err != nil {
return 0, err
}
switch tk := tk.(type) {
case json.Delim:
switch tk {
case '{':
offset, err = offsetSingleObject(dec, ttk)
if err != nil {
return 0, err
}
case '[':
offset, err = offsetSingleArray(dec, ttk)
if err != nil {
return 0, err
}
default:
return 0, fmt.Errorf("invalid token %#v", tk)
}
default:
return 0, fmt.Errorf("invalid token %#v", tk)
}
}
return offset, nil
}
func offsetSingleObject(dec *json.Decoder, decodedToken string) (int64, error) {
for dec.More() {
offset := dec.InputOffset()
tk, err := dec.Token()
if err != nil {
return 0, err
}
switch tk := tk.(type) {
case json.Delim:
switch tk {
case '{':
if err = drainSingle(dec); err != nil {
return 0, err
}
case '[':
if err = drainSingle(dec); err != nil {
return 0, err
}
}
case string:
if tk == decodedToken {
return offset, nil
}
default:
return 0, fmt.Errorf("invalid token %#v", tk)
}
}
return 0, fmt.Errorf("token reference %q not found", decodedToken)
}
func offsetSingleArray(dec *json.Decoder, decodedToken string) (int64, error) {
idx, err := strconv.Atoi(decodedToken)
if err != nil {
return 0, fmt.Errorf("token reference %q is not a number: %v", decodedToken, err)
}
var i int
for i = 0; i < idx && dec.More(); i++ {
tk, err := dec.Token()
if err != nil {
return 0, err
}
if delim, isDelim := tk.(json.Delim); isDelim {
switch delim {
case '{':
if err = drainSingle(dec); err != nil {
return 0, err
}
case '[':
if err = drainSingle(dec); err != nil {
return 0, err
}
}
}
}
if !dec.More() {
return 0, fmt.Errorf("token reference %q not found", decodedToken)
}
return dec.InputOffset(), nil
}
// drainSingle drains a single level of object or array.
// The decoder has to guarantee the beginning delim (i.e. '{' or '[') has been consumed.
func drainSingle(dec *json.Decoder) error {
for dec.More() {
tk, err := dec.Token()
if err != nil {
return err
}
if delim, isDelim := tk.(json.Delim); isDelim {
switch delim {
case '{':
if err = drainSingle(dec); err != nil {
return err
}
case '[':
if err = drainSingle(dec); err != nil {
return err
}
}
}
}
// Consumes the ending delim
if _, err := dec.Token(); err != nil {
return err
}
return nil
}
// Specific JSON pointer encoding here
// ~0 => ~
// ~1 => /
@ -377,14 +518,14 @@ const (
// Unescape unescapes a json pointer reference token string to the original representation
func Unescape(token string) string {
step1 := strings.Replace(token, encRefTok1, decRefTok1, -1)
step2 := strings.Replace(step1, encRefTok0, decRefTok0, -1)
step1 := strings.ReplaceAll(token, encRefTok1, decRefTok1)
step2 := strings.ReplaceAll(step1, encRefTok0, decRefTok0)
return step2
}
// Escape escapes a pointer reference token string
func Escape(token string) string {
step1 := strings.Replace(token, decRefTok0, encRefTok0, -1)
step2 := strings.Replace(step1, decRefTok1, encRefTok1, -1)
step1 := strings.ReplaceAll(token, decRefTok0, encRefTok0)
step2 := strings.ReplaceAll(step1, decRefTok1, encRefTok1)
return step2
}

View File

@ -2,3 +2,4 @@ secrets.yml
vendor
Godeps
.idea
*.out

View File

@ -4,14 +4,14 @@ linters-settings:
golint:
min-confidence: 0
gocyclo:
min-complexity: 25
min-complexity: 45
maligned:
suggest-new: true
dupl:
threshold: 100
threshold: 200
goconst:
min-len: 3
min-occurrences: 2
min-occurrences: 3
linters:
enable-all: true
@ -20,35 +20,41 @@ linters:
- lll
- gochecknoinits
- gochecknoglobals
- nlreturn
- testpackage
- funlen
- godox
- gocognit
- whitespace
- wsl
- wrapcheck
- testpackage
- nlreturn
- gomnd
- exhaustive
- exhaustivestruct
- goerr113
- wsl
- whitespace
- gofumpt
- godot
- errorlint
- nestif
- godox
- funlen
- gci
- gocognit
- godot
- gofumpt
- paralleltest
- tparallel
- thelper
- ifshort
- gomoddirectives
- cyclop
- forcetypeassert
- ireturn
- tagliatelle
- varnamelen
- goimports
- tenv
- golint
- exhaustruct
- nilnil
- varnamelen
- gci
- depguard
- errchkjson
- inamedparam
- nonamedreturns
- musttag
- ireturn
- forcetypeassert
- cyclop
# deprecated linters
- deadcode
- interfacer
- scopelint
- varcheck
- structcheck
- golint
- nosnakecase

52
vendor/github.com/go-openapi/swag/BENCHMARK.md generated vendored Normal file
View File

@ -0,0 +1,52 @@
# Benchmarks
## Name mangling utilities
```bash
go test -bench XXX -run XXX -benchtime 30s
```
### Benchmarks at b3e7a5386f996177e4808f11acb2aa93a0f660df
```
goos: linux
goarch: amd64
pkg: github.com/go-openapi/swag
cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
BenchmarkToXXXName/ToGoName-4 862623 44101 ns/op 10450 B/op 732 allocs/op
BenchmarkToXXXName/ToVarName-4 853656 40728 ns/op 10468 B/op 734 allocs/op
BenchmarkToXXXName/ToFileName-4 1268312 27813 ns/op 9785 B/op 617 allocs/op
BenchmarkToXXXName/ToCommandName-4 1276322 27903 ns/op 9785 B/op 617 allocs/op
BenchmarkToXXXName/ToHumanNameLower-4 895334 40354 ns/op 10472 B/op 731 allocs/op
BenchmarkToXXXName/ToHumanNameTitle-4 882441 40678 ns/op 10566 B/op 749 allocs/op
```
### Benchmarks after PR #79
~ x10 performance improvement and ~ /100 memory allocations.
```
goos: linux
goarch: amd64
pkg: github.com/go-openapi/swag
cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
BenchmarkToXXXName/ToGoName-4 9595830 3991 ns/op 42 B/op 5 allocs/op
BenchmarkToXXXName/ToVarName-4 9194276 3984 ns/op 62 B/op 7 allocs/op
BenchmarkToXXXName/ToFileName-4 17002711 2123 ns/op 147 B/op 7 allocs/op
BenchmarkToXXXName/ToCommandName-4 16772926 2111 ns/op 147 B/op 7 allocs/op
BenchmarkToXXXName/ToHumanNameLower-4 9788331 3749 ns/op 92 B/op 6 allocs/op
BenchmarkToXXXName/ToHumanNameTitle-4 9188260 3941 ns/op 104 B/op 6 allocs/op
```
```
goos: linux
goarch: amd64
pkg: github.com/go-openapi/swag
cpu: AMD Ryzen 7 5800X 8-Core Processor
BenchmarkToXXXName/ToGoName-16 18527378 1972 ns/op 42 B/op 5 allocs/op
BenchmarkToXXXName/ToVarName-16 15552692 2093 ns/op 62 B/op 7 allocs/op
BenchmarkToXXXName/ToFileName-16 32161176 1117 ns/op 147 B/op 7 allocs/op
BenchmarkToXXXName/ToCommandName-16 32256634 1137 ns/op 147 B/op 7 allocs/op
BenchmarkToXXXName/ToHumanNameLower-16 18599661 1946 ns/op 92 B/op 6 allocs/op
BenchmarkToXXXName/ToHumanNameTitle-16 17581353 2054 ns/op 105 B/op 6 allocs/op
```

View File

@ -1,7 +1,8 @@
# Swag [![Build Status](https://travis-ci.org/go-openapi/swag.svg?branch=master)](https://travis-ci.org/go-openapi/swag) [![codecov](https://codecov.io/gh/go-openapi/swag/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/swag) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
# Swag [![Build Status](https://github.com/go-openapi/swag/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/swag/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/swag/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/swag)
[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/swag/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/go-openapi/swag?status.svg)](http://godoc.org/github.com/go-openapi/swag)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/swag.svg)](https://pkg.go.dev/github.com/go-openapi/swag)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/swag)](https://goreportcard.com/report/github.com/go-openapi/swag)
Contains a bunch of helper functions for go-openapi and go-swagger projects.
@ -18,4 +19,5 @@ You may also use it standalone for your projects.
This repo has only few dependencies outside of the standard library:
* YAML utilities depend on gopkg.in/yaml.v2
* YAML utilities depend on `gopkg.in/yaml.v3`
* `github.com/mailru/easyjson v0.7.7`

202
vendor/github.com/go-openapi/swag/initialism_index.go generated vendored Normal file
View File

@ -0,0 +1,202 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package swag
import (
"sort"
"strings"
"sync"
)
var (
// commonInitialisms are common acronyms that are kept as whole uppercased words.
commonInitialisms *indexOfInitialisms
// initialisms is a slice of sorted initialisms
initialisms []string
// a copy of initialisms pre-baked as []rune
initialismsRunes [][]rune
initialismsUpperCased [][]rune
isInitialism func(string) bool
maxAllocMatches int
)
func init() {
// Taken from https://github.com/golang/lint/blob/3390df4df2787994aea98de825b964ac7944b817/lint.go#L732-L769
configuredInitialisms := map[string]bool{
"ACL": true,
"API": true,
"ASCII": true,
"CPU": true,
"CSS": true,
"DNS": true,
"EOF": true,
"GUID": true,
"HTML": true,
"HTTPS": true,
"HTTP": true,
"ID": true,
"IP": true,
"IPv4": true,
"IPv6": true,
"JSON": true,
"LHS": true,
"OAI": true,
"QPS": true,
"RAM": true,
"RHS": true,
"RPC": true,
"SLA": true,
"SMTP": true,
"SQL": true,
"SSH": true,
"TCP": true,
"TLS": true,
"TTL": true,
"UDP": true,
"UI": true,
"UID": true,
"UUID": true,
"URI": true,
"URL": true,
"UTF8": true,
"VM": true,
"XML": true,
"XMPP": true,
"XSRF": true,
"XSS": true,
}
// a thread-safe index of initialisms
commonInitialisms = newIndexOfInitialisms().load(configuredInitialisms)
initialisms = commonInitialisms.sorted()
initialismsRunes = asRunes(initialisms)
initialismsUpperCased = asUpperCased(initialisms)
maxAllocMatches = maxAllocHeuristic(initialismsRunes)
// a test function
isInitialism = commonInitialisms.isInitialism
}
func asRunes(in []string) [][]rune {
out := make([][]rune, len(in))
for i, initialism := range in {
out[i] = []rune(initialism)
}
return out
}
func asUpperCased(in []string) [][]rune {
out := make([][]rune, len(in))
for i, initialism := range in {
out[i] = []rune(upper(trim(initialism)))
}
return out
}
func maxAllocHeuristic(in [][]rune) int {
heuristic := make(map[rune]int)
for _, initialism := range in {
heuristic[initialism[0]]++
}
var maxAlloc int
for _, val := range heuristic {
if val > maxAlloc {
maxAlloc = val
}
}
return maxAlloc
}
// AddInitialisms add additional initialisms
func AddInitialisms(words ...string) {
for _, word := range words {
// commonInitialisms[upper(word)] = true
commonInitialisms.add(upper(word))
}
// sort again
initialisms = commonInitialisms.sorted()
initialismsRunes = asRunes(initialisms)
initialismsUpperCased = asUpperCased(initialisms)
}
// indexOfInitialisms is a thread-safe implementation of the sorted index of initialisms.
// Since go1.9, this may be implemented with sync.Map.
type indexOfInitialisms struct {
sortMutex *sync.Mutex
index *sync.Map
}
func newIndexOfInitialisms() *indexOfInitialisms {
return &indexOfInitialisms{
sortMutex: new(sync.Mutex),
index: new(sync.Map),
}
}
func (m *indexOfInitialisms) load(initial map[string]bool) *indexOfInitialisms {
m.sortMutex.Lock()
defer m.sortMutex.Unlock()
for k, v := range initial {
m.index.Store(k, v)
}
return m
}
func (m *indexOfInitialisms) isInitialism(key string) bool {
_, ok := m.index.Load(key)
return ok
}
func (m *indexOfInitialisms) add(key string) *indexOfInitialisms {
m.index.Store(key, true)
return m
}
func (m *indexOfInitialisms) sorted() (result []string) {
m.sortMutex.Lock()
defer m.sortMutex.Unlock()
m.index.Range(func(key, _ interface{}) bool {
k := key.(string)
result = append(result, k)
return true
})
sort.Sort(sort.Reverse(byInitialism(result)))
return
}
type byInitialism []string
func (s byInitialism) Len() int {
return len(s)
}
func (s byInitialism) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s byInitialism) Less(i, j int) bool {
if len(s[i]) != len(s[j]) {
return len(s[i]) < len(s[j])
}
return strings.Compare(s[i], s[j]) > 0
}

View File

@ -21,6 +21,7 @@ import (
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"runtime"
"strings"
@ -40,43 +41,97 @@ var LoadHTTPBasicAuthPassword = ""
var LoadHTTPCustomHeaders = map[string]string{}
// LoadFromFileOrHTTP loads the bytes from a file or a remote http server based on the path passed in
func LoadFromFileOrHTTP(path string) ([]byte, error) {
return LoadStrategy(path, os.ReadFile, loadHTTPBytes(LoadHTTPTimeout))(path)
func LoadFromFileOrHTTP(pth string) ([]byte, error) {
return LoadStrategy(pth, os.ReadFile, loadHTTPBytes(LoadHTTPTimeout))(pth)
}
// LoadFromFileOrHTTPWithTimeout loads the bytes from a file or a remote http server based on the path passed in
// timeout arg allows for per request overriding of the request timeout
func LoadFromFileOrHTTPWithTimeout(path string, timeout time.Duration) ([]byte, error) {
return LoadStrategy(path, os.ReadFile, loadHTTPBytes(timeout))(path)
func LoadFromFileOrHTTPWithTimeout(pth string, timeout time.Duration) ([]byte, error) {
return LoadStrategy(pth, os.ReadFile, loadHTTPBytes(timeout))(pth)
}
// LoadStrategy returns a loader function for a given path or uri
func LoadStrategy(path string, local, remote func(string) ([]byte, error)) func(string) ([]byte, error) {
if strings.HasPrefix(path, "http") {
// LoadStrategy returns a loader function for a given path or URI.
//
// The load strategy returns the remote load for any path starting with `http`.
// So this works for any URI with a scheme `http` or `https`.
//
// The fallback strategy is to call the local loader.
//
// The local loader takes a local file system path (absolute or relative) as argument,
// or alternatively a `file://...` URI, **without host** (see also below for windows).
//
// There are a few liberalities, initially intended to be tolerant regarding the URI syntax,
// especially on windows.
//
// Before the local loader is called, the given path is transformed:
// - percent-encoded characters are unescaped
// - simple paths (e.g. `./folder/file`) are passed as-is
// - on windows, occurrences of `/` are replaced by `\`, so providing a relative path such a `folder/file` works too.
//
// For paths provided as URIs with the "file" scheme, please note that:
// - `file://` is simply stripped.
// This means that the host part of the URI is not parsed at all.
// For example, `file:///folder/file" becomes "/folder/file`,
// but `file://localhost/folder/file` becomes `localhost/folder/file` on unix systems.
// Similarly, `file://./folder/file` yields `./folder/file`.
// - on windows, `file://...` can take a host so as to specify an UNC share location.
//
// Reminder about windows-specifics:
// - `file://host/folder/file` becomes an UNC path like `\\host\folder\file` (no port specification is supported)
// - `file:///c:/folder/file` becomes `C:\folder\file`
// - `file://c:/folder/file` is tolerated (without leading `/`) and becomes `c:\folder\file`
func LoadStrategy(pth string, local, remote func(string) ([]byte, error)) func(string) ([]byte, error) {
if strings.HasPrefix(pth, "http") {
return remote
}
return func(pth string) ([]byte, error) {
upth, err := pathUnescape(pth)
return func(p string) ([]byte, error) {
upth, err := url.PathUnescape(p)
if err != nil {
return nil, err
}
if strings.HasPrefix(pth, `file://`) {
if runtime.GOOS == "windows" {
if !strings.HasPrefix(p, `file://`) {
// regular file path provided: just normalize slashes
return local(filepath.FromSlash(upth))
}
if runtime.GOOS != "windows" {
// crude processing: this leaves full URIs with a host with a (mostly) unexpected result
upth = strings.TrimPrefix(upth, `file://`)
return local(filepath.FromSlash(upth))
}
// windows-only pre-processing of file://... URIs
// support for canonical file URIs on windows.
// Zero tolerance here for dodgy URIs.
u, _ := url.Parse(upth)
u, err := url.Parse(filepath.ToSlash(upth))
if err != nil {
return nil, err
}
if u.Host != "" {
// assume UNC name (volume share)
// file://host/share/folder\... ==> \\host\share\path\folder
// NOTE: UNC port not yet supported
upth = strings.Join([]string{`\`, u.Host, u.Path}, `\`)
} else {
// file:///c:/folder/... ==> just remove the leading slash
upth = strings.TrimPrefix(upth, `file:///`)
// when the "host" segment is a drive letter:
// file://C:/folder/... => C:\folder
upth = path.Clean(strings.Join([]string{u.Host, u.Path}, `/`))
if !strings.HasSuffix(u.Host, ":") && u.Host[0] != '.' {
// tolerance: if we have a leading dot, this can't be a host
// file://host/share/folder\... ==> \\host\share\path\folder
upth = "//" + upth
}
} else {
// no host, let's figure out if this is a drive letter
upth = strings.TrimPrefix(upth, `file://`)
first, _, _ := strings.Cut(strings.TrimPrefix(u.Path, "/"), "/")
if strings.HasSuffix(first, ":") {
// drive letter in the first segment:
// file:///c:/folder/... ==> strip the leading slash
upth = strings.TrimPrefix(upth, `/`)
}
}

View File

@ -14,74 +14,80 @@
package swag
import "unicode"
import (
"unicode"
"unicode/utf8"
)
type (
nameLexem interface {
GetUnsafeGoName() string
GetOriginal() string
IsInitialism() bool
}
lexemKind uint8
initialismNameLexem struct {
nameLexem struct {
original string
matchedInitialism string
}
casualNameLexem struct {
original string
kind lexemKind
}
)
func newInitialismNameLexem(original, matchedInitialism string) *initialismNameLexem {
return &initialismNameLexem{
const (
lexemKindCasualName lexemKind = iota
lexemKindInitialismName
)
func newInitialismNameLexem(original, matchedInitialism string) nameLexem {
return nameLexem{
kind: lexemKindInitialismName,
original: original,
matchedInitialism: matchedInitialism,
}
}
func newCasualNameLexem(original string) *casualNameLexem {
return &casualNameLexem{
func newCasualNameLexem(original string) nameLexem {
return nameLexem{
kind: lexemKindCasualName,
original: original,
}
}
func (l *initialismNameLexem) GetUnsafeGoName() string {
func (l nameLexem) GetUnsafeGoName() string {
if l.kind == lexemKindInitialismName {
return l.matchedInitialism
}
func (l *casualNameLexem) GetUnsafeGoName() string {
var first rune
var rest string
var (
first rune
rest string
)
for i, orig := range l.original {
if i == 0 {
first = orig
continue
}
if i > 0 {
rest = l.original[i:]
break
}
}
if len(l.original) > 1 {
return string(unicode.ToUpper(first)) + lower(rest)
b := poolOfBuffers.BorrowBuffer(utf8.UTFMax + len(rest))
defer func() {
poolOfBuffers.RedeemBuffer(b)
}()
b.WriteRune(unicode.ToUpper(first))
b.WriteString(lower(rest))
return b.String()
}
return l.original
}
func (l *initialismNameLexem) GetOriginal() string {
func (l nameLexem) GetOriginal() string {
return l.original
}
func (l *casualNameLexem) GetOriginal() string {
return l.original
}
func (l *initialismNameLexem) IsInitialism() bool {
return true
}
func (l *casualNameLexem) IsInitialism() bool {
return false
func (l nameLexem) IsInitialism() bool {
return l.kind == lexemKindInitialismName
}

View File

@ -1,24 +0,0 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build go1.8
// +build go1.8
package swag
import "net/url"
func pathUnescape(path string) (string, error) {
return url.PathUnescape(path)
}

View File

@ -1,68 +0,0 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build go1.9
// +build go1.9
package swag
import (
"sort"
"sync"
)
// indexOfInitialisms is a thread-safe implementation of the sorted index of initialisms.
// Since go1.9, this may be implemented with sync.Map.
type indexOfInitialisms struct {
sortMutex *sync.Mutex
index *sync.Map
}
func newIndexOfInitialisms() *indexOfInitialisms {
return &indexOfInitialisms{
sortMutex: new(sync.Mutex),
index: new(sync.Map),
}
}
func (m *indexOfInitialisms) load(initial map[string]bool) *indexOfInitialisms {
m.sortMutex.Lock()
defer m.sortMutex.Unlock()
for k, v := range initial {
m.index.Store(k, v)
}
return m
}
func (m *indexOfInitialisms) isInitialism(key string) bool {
_, ok := m.index.Load(key)
return ok
}
func (m *indexOfInitialisms) add(key string) *indexOfInitialisms {
m.index.Store(key, true)
return m
}
func (m *indexOfInitialisms) sorted() (result []string) {
m.sortMutex.Lock()
defer m.sortMutex.Unlock()
m.index.Range(func(key, value interface{}) bool {
k := key.(string)
result = append(result, k)
return true
})
sort.Sort(sort.Reverse(byInitialism(result)))
return
}

View File

@ -1,24 +0,0 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !go1.8
// +build !go1.8
package swag
import "net/url"
func pathUnescape(path string) (string, error) {
return url.QueryUnescape(path)
}

View File

@ -1,70 +0,0 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !go1.9
// +build !go1.9
package swag
import (
"sort"
"sync"
)
// indexOfInitialisms is a thread-safe implementation of the sorted index of initialisms.
// Before go1.9, this may be implemented with a mutex on the map.
type indexOfInitialisms struct {
getMutex *sync.Mutex
index map[string]bool
}
func newIndexOfInitialisms() *indexOfInitialisms {
return &indexOfInitialisms{
getMutex: new(sync.Mutex),
index: make(map[string]bool, 50),
}
}
func (m *indexOfInitialisms) load(initial map[string]bool) *indexOfInitialisms {
m.getMutex.Lock()
defer m.getMutex.Unlock()
for k, v := range initial {
m.index[k] = v
}
return m
}
func (m *indexOfInitialisms) isInitialism(key string) bool {
m.getMutex.Lock()
defer m.getMutex.Unlock()
_, ok := m.index[key]
return ok
}
func (m *indexOfInitialisms) add(key string) *indexOfInitialisms {
m.getMutex.Lock()
defer m.getMutex.Unlock()
m.index[key] = true
return m
}
func (m *indexOfInitialisms) sorted() (result []string) {
m.getMutex.Lock()
defer m.getMutex.Unlock()
for k := range m.index {
result = append(result, k)
}
sort.Sort(sort.Reverse(byInitialism(result)))
return
}

View File

@ -15,95 +15,239 @@
package swag
import (
"bytes"
"sync"
"unicode"
"unicode/utf8"
)
var nameReplaceTable = map[rune]string{
'@': "At ",
'&': "And ",
'|': "Pipe ",
'$': "Dollar ",
'!': "Bang ",
'-': "",
'_': "",
}
type (
splitter struct {
postSplitInitialismCheck bool
initialisms []string
initialismsRunes [][]rune
initialismsUpperCased [][]rune // initialisms cached in their trimmed, upper-cased version
postSplitInitialismCheck bool
}
splitterOption func(*splitter) *splitter
splitterOption func(*splitter)
initialismMatch struct {
body []rune
start, end int
complete bool
}
initialismMatches []initialismMatch
)
// split calls the splitter; splitter provides more control and post options
func split(str string) []string {
lexems := newSplitter().split(str)
result := make([]string, 0, len(lexems))
type (
// memory pools of temporary objects.
//
// These are used to recycle temporarily allocated objects
// and relieve the GC from undue pressure.
for _, lexem := range lexems {
matchesPool struct {
*sync.Pool
}
buffersPool struct {
*sync.Pool
}
lexemsPool struct {
*sync.Pool
}
splittersPool struct {
*sync.Pool
}
)
var (
// poolOfMatches holds temporary slices for recycling during the initialism match process
poolOfMatches = matchesPool{
Pool: &sync.Pool{
New: func() any {
s := make(initialismMatches, 0, maxAllocMatches)
return &s
},
},
}
poolOfBuffers = buffersPool{
Pool: &sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
},
}
poolOfLexems = lexemsPool{
Pool: &sync.Pool{
New: func() any {
s := make([]nameLexem, 0, maxAllocMatches)
return &s
},
},
}
poolOfSplitters = splittersPool{
Pool: &sync.Pool{
New: func() any {
s := newSplitter()
return &s
},
},
}
)
// nameReplaceTable finds a word representation for special characters.
func nameReplaceTable(r rune) (string, bool) {
switch r {
case '@':
return "At ", true
case '&':
return "And ", true
case '|':
return "Pipe ", true
case '$':
return "Dollar ", true
case '!':
return "Bang ", true
case '-':
return "", true
case '_':
return "", true
default:
return "", false
}
}
// split calls the splitter.
//
// Use newSplitter for more control and options
func split(str string) []string {
s := poolOfSplitters.BorrowSplitter()
lexems := s.split(str)
result := make([]string, 0, len(*lexems))
for _, lexem := range *lexems {
result = append(result, lexem.GetOriginal())
}
poolOfLexems.RedeemLexems(lexems)
poolOfSplitters.RedeemSplitter(s)
return result
}
func (s *splitter) split(str string) []nameLexem {
return s.toNameLexems(str)
}
func newSplitter(options ...splitterOption) *splitter {
splitter := &splitter{
func newSplitter(options ...splitterOption) splitter {
s := splitter{
postSplitInitialismCheck: false,
initialisms: initialisms,
initialismsRunes: initialismsRunes,
initialismsUpperCased: initialismsUpperCased,
}
for _, option := range options {
splitter = option(splitter)
option(&s)
}
return splitter
}
// withPostSplitInitialismCheck allows to catch initialisms after main split process
func withPostSplitInitialismCheck(s *splitter) *splitter {
s.postSplitInitialismCheck = true
return s
}
type (
initialismMatch struct {
start, end int
body []rune
complete bool
// withPostSplitInitialismCheck allows to catch initialisms after main split process
func withPostSplitInitialismCheck(s *splitter) {
s.postSplitInitialismCheck = true
}
initialismMatches []*initialismMatch
)
func (s *splitter) toNameLexems(name string) []nameLexem {
func (p matchesPool) BorrowMatches() *initialismMatches {
s := p.Get().(*initialismMatches)
*s = (*s)[:0] // reset slice, keep allocated capacity
return s
}
func (p buffersPool) BorrowBuffer(size int) *bytes.Buffer {
s := p.Get().(*bytes.Buffer)
s.Reset()
if s.Cap() < size {
s.Grow(size)
}
return s
}
func (p lexemsPool) BorrowLexems() *[]nameLexem {
s := p.Get().(*[]nameLexem)
*s = (*s)[:0] // reset slice, keep allocated capacity
return s
}
func (p splittersPool) BorrowSplitter(options ...splitterOption) *splitter {
s := p.Get().(*splitter)
s.postSplitInitialismCheck = false // reset options
for _, apply := range options {
apply(s)
}
return s
}
func (p matchesPool) RedeemMatches(s *initialismMatches) {
p.Put(s)
}
func (p buffersPool) RedeemBuffer(s *bytes.Buffer) {
p.Put(s)
}
func (p lexemsPool) RedeemLexems(s *[]nameLexem) {
p.Put(s)
}
func (p splittersPool) RedeemSplitter(s *splitter) {
p.Put(s)
}
func (m initialismMatch) isZero() bool {
return m.start == 0 && m.end == 0
}
func (s splitter) split(name string) *[]nameLexem {
nameRunes := []rune(name)
matches := s.gatherInitialismMatches(nameRunes)
if matches == nil {
return poolOfLexems.BorrowLexems()
}
return s.mapMatchesToNameLexems(nameRunes, matches)
}
func (s *splitter) gatherInitialismMatches(nameRunes []rune) initialismMatches {
matches := make(initialismMatches, 0)
func (s splitter) gatherInitialismMatches(nameRunes []rune) *initialismMatches {
var matches *initialismMatches
for currentRunePosition, currentRune := range nameRunes {
newMatches := make(initialismMatches, 0, len(matches))
// recycle these allocations as we loop over runes
// with such recycling, only 2 slices should be allocated per call
// instead of o(n).
newMatches := poolOfMatches.BorrowMatches()
// check current initialism matches
for _, match := range matches {
if matches != nil { // skip first iteration
for _, match := range *matches {
if keepCompleteMatch := match.complete; keepCompleteMatch {
newMatches = append(newMatches, match)
*newMatches = append(*newMatches, match)
continue
}
// drop failed match
currentMatchRune := match.body[currentRunePosition-match.start]
if !s.initialismRuneEqual(currentMatchRune, currentRune) {
if currentMatchRune != currentRune {
continue
}
@ -125,14 +269,15 @@ func (s *splitter) gatherInitialismMatches(nameRunes []rune) initialismMatches {
match.end = currentRunePosition
}
newMatches = append(newMatches, match)
*newMatches = append(*newMatches, match)
}
}
// check for new initialism matches
for _, initialism := range s.initialisms {
initialismRunes := []rune(initialism)
if s.initialismRuneEqual(initialismRunes[0], currentRune) {
newMatches = append(newMatches, &initialismMatch{
for i := range s.initialisms {
initialismRunes := s.initialismsRunes[i]
if initialismRunes[0] == currentRune {
*newMatches = append(*newMatches, initialismMatch{
start: currentRunePosition,
body: initialismRunes,
complete: false,
@ -140,24 +285,28 @@ func (s *splitter) gatherInitialismMatches(nameRunes []rune) initialismMatches {
}
}
if matches != nil {
poolOfMatches.RedeemMatches(matches)
}
matches = newMatches
}
// up to the caller to redeem this last slice
return matches
}
func (s *splitter) mapMatchesToNameLexems(nameRunes []rune, matches initialismMatches) []nameLexem {
nameLexems := make([]nameLexem, 0)
func (s splitter) mapMatchesToNameLexems(nameRunes []rune, matches *initialismMatches) *[]nameLexem {
nameLexems := poolOfLexems.BorrowLexems()
var lastAcceptedMatch *initialismMatch
for _, match := range matches {
var lastAcceptedMatch initialismMatch
for _, match := range *matches {
if !match.complete {
continue
}
if firstMatch := lastAcceptedMatch == nil; firstMatch {
nameLexems = append(nameLexems, s.breakCasualString(nameRunes[:match.start])...)
nameLexems = append(nameLexems, s.breakInitialism(string(match.body)))
if firstMatch := lastAcceptedMatch.isZero(); firstMatch {
s.appendBrokenDownCasualString(nameLexems, nameRunes[:match.start])
*nameLexems = append(*nameLexems, s.breakInitialism(string(match.body)))
lastAcceptedMatch = match
@ -169,63 +318,66 @@ func (s *splitter) mapMatchesToNameLexems(nameRunes []rune, matches initialismMa
}
middle := nameRunes[lastAcceptedMatch.end+1 : match.start]
nameLexems = append(nameLexems, s.breakCasualString(middle)...)
nameLexems = append(nameLexems, s.breakInitialism(string(match.body)))
s.appendBrokenDownCasualString(nameLexems, middle)
*nameLexems = append(*nameLexems, s.breakInitialism(string(match.body)))
lastAcceptedMatch = match
}
// we have not found any accepted matches
if lastAcceptedMatch == nil {
return s.breakCasualString(nameRunes)
if lastAcceptedMatch.isZero() {
*nameLexems = (*nameLexems)[:0]
s.appendBrokenDownCasualString(nameLexems, nameRunes)
} else if lastAcceptedMatch.end+1 != len(nameRunes) {
rest := nameRunes[lastAcceptedMatch.end+1:]
s.appendBrokenDownCasualString(nameLexems, rest)
}
if lastAcceptedMatch.end+1 != len(nameRunes) {
rest := nameRunes[lastAcceptedMatch.end+1:]
nameLexems = append(nameLexems, s.breakCasualString(rest)...)
}
poolOfMatches.RedeemMatches(matches)
return nameLexems
}
func (s *splitter) initialismRuneEqual(a, b rune) bool {
return a == b
}
func (s *splitter) breakInitialism(original string) nameLexem {
func (s splitter) breakInitialism(original string) nameLexem {
return newInitialismNameLexem(original, original)
}
func (s *splitter) breakCasualString(str []rune) []nameLexem {
segments := make([]nameLexem, 0)
currentSegment := ""
func (s splitter) appendBrokenDownCasualString(segments *[]nameLexem, str []rune) {
currentSegment := poolOfBuffers.BorrowBuffer(len(str)) // unlike strings.Builder, bytes.Buffer initial storage can reused
defer func() {
poolOfBuffers.RedeemBuffer(currentSegment)
}()
addCasualNameLexem := func(original string) {
segments = append(segments, newCasualNameLexem(original))
*segments = append(*segments, newCasualNameLexem(original))
}
addInitialismNameLexem := func(original, match string) {
segments = append(segments, newInitialismNameLexem(original, match))
*segments = append(*segments, newInitialismNameLexem(original, match))
}
addNameLexem := func(original string) {
var addNameLexem func(string)
if s.postSplitInitialismCheck {
for _, initialism := range s.initialisms {
if upper(initialism) == upper(original) {
addInitialismNameLexem(original, initialism)
addNameLexem = func(original string) {
for i := range s.initialisms {
if isEqualFoldIgnoreSpace(s.initialismsUpperCased[i], original) {
addInitialismNameLexem(original, s.initialisms[i])
return
}
}
}
addCasualNameLexem(original)
}
} else {
addNameLexem = addCasualNameLexem
}
for _, rn := range string(str) {
if replace, found := nameReplaceTable[rn]; found {
if currentSegment != "" {
addNameLexem(currentSegment)
currentSegment = ""
for _, rn := range str {
if replace, found := nameReplaceTable(rn); found {
if currentSegment.Len() > 0 {
addNameLexem(currentSegment.String())
currentSegment.Reset()
}
if replace != "" {
@ -236,27 +388,121 @@ func (s *splitter) breakCasualString(str []rune) []nameLexem {
}
if !unicode.In(rn, unicode.L, unicode.M, unicode.N, unicode.Pc) {
if currentSegment != "" {
addNameLexem(currentSegment)
currentSegment = ""
if currentSegment.Len() > 0 {
addNameLexem(currentSegment.String())
currentSegment.Reset()
}
continue
}
if unicode.IsUpper(rn) {
if currentSegment != "" {
addNameLexem(currentSegment)
if currentSegment.Len() > 0 {
addNameLexem(currentSegment.String())
}
currentSegment = ""
currentSegment.Reset()
}
currentSegment += string(rn)
currentSegment.WriteRune(rn)
}
if currentSegment != "" {
addNameLexem(currentSegment)
if currentSegment.Len() > 0 {
addNameLexem(currentSegment.String())
}
}
return segments
// isEqualFoldIgnoreSpace is the same as strings.EqualFold, but
// it ignores leading and trailing blank spaces in the compared
// string.
//
// base is assumed to be composed of upper-cased runes, and be already
// trimmed.
//
// This code is heavily inspired from strings.EqualFold.
func isEqualFoldIgnoreSpace(base []rune, str string) bool {
var i, baseIndex int
// equivalent to b := []byte(str), but without data copy
b := hackStringBytes(str)
for i < len(b) {
if c := b[i]; c < utf8.RuneSelf {
// fast path for ASCII
if c != ' ' && c != '\t' {
break
}
i++
continue
}
// unicode case
r, size := utf8.DecodeRune(b[i:])
if !unicode.IsSpace(r) {
break
}
i += size
}
if i >= len(b) {
return len(base) == 0
}
for _, baseRune := range base {
if i >= len(b) {
break
}
if c := b[i]; c < utf8.RuneSelf {
// single byte rune case (ASCII)
if baseRune >= utf8.RuneSelf {
return false
}
baseChar := byte(baseRune)
if c != baseChar &&
!('a' <= c && c <= 'z' && c-'a'+'A' == baseChar) {
return false
}
baseIndex++
i++
continue
}
// unicode case
r, size := utf8.DecodeRune(b[i:])
if unicode.ToUpper(r) != baseRune {
return false
}
baseIndex++
i += size
}
if baseIndex != len(base) {
return false
}
// all passed: now we should only have blanks
for i < len(b) {
if c := b[i]; c < utf8.RuneSelf {
// fast path for ASCII
if c != ' ' && c != '\t' {
return false
}
i++
continue
}
// unicode case
r, size := utf8.DecodeRune(b[i:])
if !unicode.IsSpace(r) {
return false
}
i += size
}
return true
}

8
vendor/github.com/go-openapi/swag/string_bytes.go generated vendored Normal file
View File

@ -0,0 +1,8 @@
package swag
import "unsafe"
// hackStringBytes returns the (unsafe) underlying bytes slice of a string.
func hackStringBytes(str string) []byte {
return unsafe.Slice(unsafe.StringData(str), len(str))
}

View File

@ -18,76 +18,25 @@ import (
"reflect"
"strings"
"unicode"
"unicode/utf8"
)
// commonInitialisms are common acronyms that are kept as whole uppercased words.
var commonInitialisms *indexOfInitialisms
// initialisms is a slice of sorted initialisms
var initialisms []string
var isInitialism func(string) bool
// GoNamePrefixFunc sets an optional rule to prefix go names
// which do not start with a letter.
//
// The prefix function is assumed to return a string that starts with an upper case letter.
//
// e.g. to help convert "123" into "{prefix}123"
//
// The default is to prefix with "X"
var GoNamePrefixFunc func(string) string
func init() {
// Taken from https://github.com/golang/lint/blob/3390df4df2787994aea98de825b964ac7944b817/lint.go#L732-L769
var configuredInitialisms = map[string]bool{
"ACL": true,
"API": true,
"ASCII": true,
"CPU": true,
"CSS": true,
"DNS": true,
"EOF": true,
"GUID": true,
"HTML": true,
"HTTPS": true,
"HTTP": true,
"ID": true,
"IP": true,
"IPv4": true,
"IPv6": true,
"JSON": true,
"LHS": true,
"OAI": true,
"QPS": true,
"RAM": true,
"RHS": true,
"RPC": true,
"SLA": true,
"SMTP": true,
"SQL": true,
"SSH": true,
"TCP": true,
"TLS": true,
"TTL": true,
"UDP": true,
"UI": true,
"UID": true,
"UUID": true,
"URI": true,
"URL": true,
"UTF8": true,
"VM": true,
"XML": true,
"XMPP": true,
"XSRF": true,
"XSS": true,
func prefixFunc(name, in string) string {
if GoNamePrefixFunc == nil {
return "X" + in
}
// a thread-safe index of initialisms
commonInitialisms = newIndexOfInitialisms().load(configuredInitialisms)
initialisms = commonInitialisms.sorted()
// a test function
isInitialism = commonInitialisms.isInitialism
return GoNamePrefixFunc(name) + in
}
const (
@ -156,25 +105,9 @@ func SplitByFormat(data, format string) []string {
return result
}
type byInitialism []string
func (s byInitialism) Len() int {
return len(s)
}
func (s byInitialism) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s byInitialism) Less(i, j int) bool {
if len(s[i]) != len(s[j]) {
return len(s[i]) < len(s[j])
}
return strings.Compare(s[i], s[j]) > 0
}
// Removes leading whitespaces
func trim(str string) string {
return strings.Trim(str, " ")
return strings.TrimSpace(str)
}
// Shortcut to strings.ToUpper()
@ -188,15 +121,20 @@ func lower(str string) string {
}
// Camelize an uppercased word
func Camelize(word string) (camelized string) {
func Camelize(word string) string {
camelized := poolOfBuffers.BorrowBuffer(len(word))
defer func() {
poolOfBuffers.RedeemBuffer(camelized)
}()
for pos, ru := range []rune(word) {
if pos > 0 {
camelized += string(unicode.ToLower(ru))
camelized.WriteRune(unicode.ToLower(ru))
} else {
camelized += string(unicode.ToUpper(ru))
camelized.WriteRune(unicode.ToUpper(ru))
}
}
return
return camelized.String()
}
// ToFileName lowercases and underscores a go type name
@ -224,33 +162,40 @@ func ToCommandName(name string) string {
// ToHumanNameLower represents a code name as a human series of words
func ToHumanNameLower(name string) string {
in := newSplitter(withPostSplitInitialismCheck).split(name)
out := make([]string, 0, len(in))
s := poolOfSplitters.BorrowSplitter(withPostSplitInitialismCheck)
in := s.split(name)
poolOfSplitters.RedeemSplitter(s)
out := make([]string, 0, len(*in))
for _, w := range in {
for _, w := range *in {
if !w.IsInitialism() {
out = append(out, lower(w.GetOriginal()))
} else {
out = append(out, w.GetOriginal())
out = append(out, trim(w.GetOriginal()))
}
}
poolOfLexems.RedeemLexems(in)
return strings.Join(out, " ")
}
// ToHumanNameTitle represents a code name as a human series of words with the first letters titleized
func ToHumanNameTitle(name string) string {
in := newSplitter(withPostSplitInitialismCheck).split(name)
s := poolOfSplitters.BorrowSplitter(withPostSplitInitialismCheck)
in := s.split(name)
poolOfSplitters.RedeemSplitter(s)
out := make([]string, 0, len(in))
for _, w := range in {
original := w.GetOriginal()
out := make([]string, 0, len(*in))
for _, w := range *in {
original := trim(w.GetOriginal())
if !w.IsInitialism() {
out = append(out, Camelize(original))
} else {
out = append(out, original)
}
}
poolOfLexems.RedeemLexems(in)
return strings.Join(out, " ")
}
@ -264,7 +209,7 @@ func ToJSONName(name string) string {
out = append(out, lower(w))
continue
}
out = append(out, Camelize(w))
out = append(out, Camelize(trim(w)))
}
return strings.Join(out, "")
}
@ -283,35 +228,70 @@ func ToVarName(name string) string {
// ToGoName translates a swagger name which can be underscored or camel cased to a name that golint likes
func ToGoName(name string) string {
lexems := newSplitter(withPostSplitInitialismCheck).split(name)
s := poolOfSplitters.BorrowSplitter(withPostSplitInitialismCheck)
lexems := s.split(name)
poolOfSplitters.RedeemSplitter(s)
defer func() {
poolOfLexems.RedeemLexems(lexems)
}()
lexemes := *lexems
result := ""
for _, lexem := range lexems {
if len(lexemes) == 0 {
return ""
}
result := poolOfBuffers.BorrowBuffer(len(name))
defer func() {
poolOfBuffers.RedeemBuffer(result)
}()
// check if not starting with a letter, upper case
firstPart := lexemes[0].GetUnsafeGoName()
if lexemes[0].IsInitialism() {
firstPart = upper(firstPart)
}
if c := firstPart[0]; c < utf8.RuneSelf {
// ASCII
switch {
case 'A' <= c && c <= 'Z':
result.WriteString(firstPart)
case 'a' <= c && c <= 'z':
result.WriteByte(c - 'a' + 'A')
result.WriteString(firstPart[1:])
default:
result.WriteString(prefixFunc(name, firstPart))
// NOTE: no longer check if prefixFunc returns a string that starts with uppercase:
// assume this is always the case
}
} else {
// unicode
firstRune, _ := utf8.DecodeRuneInString(firstPart)
switch {
case !unicode.IsLetter(firstRune):
result.WriteString(prefixFunc(name, firstPart))
case !unicode.IsUpper(firstRune):
result.WriteString(prefixFunc(name, firstPart))
/*
result.WriteRune(unicode.ToUpper(firstRune))
result.WriteString(firstPart[offset:])
*/
default:
result.WriteString(firstPart)
}
}
for _, lexem := range lexemes[1:] {
goName := lexem.GetUnsafeGoName()
// to support old behavior
if lexem.IsInitialism() {
goName = upper(goName)
}
result += goName
result.WriteString(goName)
}
if len(result) > 0 {
// Only prefix with X when the first character isn't an ascii letter
first := []rune(result)[0]
if !unicode.IsLetter(first) || (first > unicode.MaxASCII && !unicode.IsUpper(first)) {
if GoNamePrefixFunc == nil {
return "X" + result
}
result = GoNamePrefixFunc(name) + result
}
first = []rune(result)[0]
if unicode.IsLetter(first) && !unicode.IsUpper(first) {
result = string(append([]rune{unicode.ToUpper(first)}, []rune(result)[1:]...))
}
}
return result
return result.String()
}
// ContainsStrings searches a slice of strings for a case-sensitive match
@ -341,13 +321,22 @@ type zeroable interface {
// IsZero returns true when the value passed into the function is a zero value.
// This allows for safer checking of interface values.
func IsZero(data interface{}) bool {
v := reflect.ValueOf(data)
// check for nil data
switch v.Kind() { //nolint:exhaustive
case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
if v.IsNil() {
return true
}
}
// check for things that have an IsZero method instead
if vv, ok := data.(zeroable); ok {
return vv.IsZero()
}
// continue with slightly more complex reflection
v := reflect.ValueOf(data)
switch v.Kind() {
switch v.Kind() { //nolint:exhaustive
case reflect.String:
return v.Len() == 0
case reflect.Bool:
@ -358,24 +347,13 @@ func IsZero(data interface{}) bool {
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return v.IsNil()
case reflect.Struct, reflect.Array:
return reflect.DeepEqual(data, reflect.Zero(v.Type()).Interface())
case reflect.Invalid:
return true
}
default:
return false
}
// AddInitialisms add additional initialisms
func AddInitialisms(words ...string) {
for _, word := range words {
// commonInitialisms[upper(word)] = true
commonInitialisms.add(upper(word))
}
// sort again
initialisms = commonInitialisms.sorted()
}
// CommandLineOptionsGroup represents a group of user-defined command line options

View File

@ -16,8 +16,11 @@ package swag
import (
"encoding/json"
"errors"
"fmt"
"path/filepath"
"reflect"
"sort"
"strconv"
"github.com/mailru/easyjson/jlexer"
@ -48,7 +51,7 @@ func BytesToYAMLDoc(data []byte) (interface{}, error) {
return nil, err
}
if document.Kind != yaml.DocumentNode || len(document.Content) != 1 || document.Content[0].Kind != yaml.MappingNode {
return nil, fmt.Errorf("only YAML documents that are objects are supported")
return nil, errors.New("only YAML documents that are objects are supported")
}
return &document, nil
}
@ -147,7 +150,7 @@ func yamlScalar(node *yaml.Node) (interface{}, error) {
case yamlTimestamp:
return node.Value, nil
case yamlNull:
return nil, nil
return nil, nil //nolint:nilnil
default:
return nil, fmt.Errorf("YAML tag %q is not supported", node.LongTag())
}
@ -245,7 +248,27 @@ func (s JSONMapSlice) MarshalYAML() (interface{}, error) {
return yaml.Marshal(&n)
}
func isNil(input interface{}) bool {
if input == nil {
return true
}
kind := reflect.TypeOf(input).Kind()
switch kind { //nolint:exhaustive
case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan:
return reflect.ValueOf(input).IsNil()
default:
return false
}
}
func json2yaml(item interface{}) (*yaml.Node, error) {
if isNil(item) {
return &yaml.Node{
Kind: yaml.ScalarNode,
Value: "null",
}, nil
}
switch val := item.(type) {
case JSONMapSlice:
var n yaml.Node
@ -265,7 +288,14 @@ func json2yaml(item interface{}) (*yaml.Node, error) {
case map[string]interface{}:
var n yaml.Node
n.Kind = yaml.MappingNode
for k, v := range val {
keys := make([]string, 0, len(val))
for k := range val {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := val[k]
childNode, err := json2yaml(v)
if err != nil {
return nil, err
@ -318,8 +348,9 @@ func json2yaml(item interface{}) (*yaml.Node, error) {
Tag: yamlBoolScalar,
Value: strconv.FormatBool(val),
}, nil
default:
return nil, fmt.Errorf("unhandled type: %T", val)
}
return nil, nil
}
// JSONMapItem represents the value of a key in a JSON object held by JSONMapSlice

26
vendor/github.com/mohae/deepcopy/.gitignore generated vendored Normal file
View File

@ -0,0 +1,26 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*~
*.out
*.log

11
vendor/github.com/mohae/deepcopy/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,11 @@
language: go
go:
- tip
matrix:
allow_failures:
- go: tip
script:
- go test ./...

21
vendor/github.com/mohae/deepcopy/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Joel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

8
vendor/github.com/mohae/deepcopy/README.md generated vendored Normal file
View File

@ -0,0 +1,8 @@
deepCopy
========
[![GoDoc](https://godoc.org/github.com/mohae/deepcopy?status.svg)](https://godoc.org/github.com/mohae/deepcopy)[![Build Status](https://travis-ci.org/mohae/deepcopy.png)](https://travis-ci.org/mohae/deepcopy)
DeepCopy makes deep copies of things: unexported field values are not copied.
## Usage
cpy := deepcopy.Copy(orig)

125
vendor/github.com/mohae/deepcopy/deepcopy.go generated vendored Normal file
View File

@ -0,0 +1,125 @@
// deepcopy makes deep copies of things. A standard copy will copy the
// pointers: deep copy copies the values pointed to. Unexported field
// values are not copied.
//
// Copyright (c)2014-2016, Joel Scoble (github.com/mohae), all rights reserved.
// License: MIT, for more details check the included LICENSE file.
package deepcopy
import (
"reflect"
"time"
)
// Interface for delegating copy process to type
type Interface interface {
DeepCopy() interface{}
}
// Iface is an alias to Copy; this exists for backwards compatibility reasons.
func Iface(iface interface{}) interface{} {
return Copy(iface)
}
// Copy creates a deep copy of whatever is passed to it and returns the copy
// in an interface{}. The returned value will need to be asserted to the
// correct type.
func Copy(src interface{}) interface{} {
if src == nil {
return nil
}
// Make the interface a reflect.Value
original := reflect.ValueOf(src)
// Make a copy of the same type as the original.
cpy := reflect.New(original.Type()).Elem()
// Recursively copy the original.
copyRecursive(original, cpy)
// Return the copy as an interface.
return cpy.Interface()
}
// copyRecursive does the actual copying of the interface. It currently has
// limited support for what it can handle. Add as needed.
func copyRecursive(original, cpy reflect.Value) {
// check for implement deepcopy.Interface
if original.CanInterface() {
if copier, ok := original.Interface().(Interface); ok {
cpy.Set(reflect.ValueOf(copier.DeepCopy()))
return
}
}
// handle according to original's Kind
switch original.Kind() {
case reflect.Ptr:
// Get the actual value being pointed to.
originalValue := original.Elem()
// if it isn't valid, return.
if !originalValue.IsValid() {
return
}
cpy.Set(reflect.New(originalValue.Type()))
copyRecursive(originalValue, cpy.Elem())
case reflect.Interface:
// If this is a nil, don't do anything
if original.IsNil() {
return
}
// Get the value for the interface, not the pointer.
originalValue := original.Elem()
// Get the value by calling Elem().
copyValue := reflect.New(originalValue.Type()).Elem()
copyRecursive(originalValue, copyValue)
cpy.Set(copyValue)
case reflect.Struct:
t, ok := original.Interface().(time.Time)
if ok {
cpy.Set(reflect.ValueOf(t))
return
}
// Go through each field of the struct and copy it.
for i := 0; i < original.NumField(); i++ {
// The Type's StructField for a given field is checked to see if StructField.PkgPath
// is set to determine if the field is exported or not because CanSet() returns false
// for settable fields. I'm not sure why. -mohae
if original.Type().Field(i).PkgPath != "" {
continue
}
copyRecursive(original.Field(i), cpy.Field(i))
}
case reflect.Slice:
if original.IsNil() {
return
}
// Make a new slice and copy each element.
cpy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap()))
for i := 0; i < original.Len(); i++ {
copyRecursive(original.Index(i), cpy.Index(i))
}
case reflect.Map:
if original.IsNil() {
return
}
cpy.Set(reflect.MakeMap(original.Type()))
for _, key := range original.MapKeys() {
originalValue := original.MapIndex(key)
copyValue := reflect.New(originalValue.Type()).Elem()
copyRecursive(originalValue, copyValue)
copyKey := Copy(key.Interface())
cpy.SetMapIndex(reflect.ValueOf(copyKey), copyValue)
}
default:
cpy.Set(original)
}
}

20
vendor/github.com/oasdiff/yaml/.gitignore generated vendored Normal file
View File

@ -0,0 +1,20 @@
# OSX leaves these everywhere on SMB shares
._*
# Eclipse files
.classpath
.project
.settings/**
# Emacs save files
*~
# Vim-related files
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
# Go test binaries
*.test

16
vendor/github.com/oasdiff/yaml/.golangci.toml generated vendored Normal file
View File

@ -0,0 +1,16 @@
[run]
timeout = "120s"
[output]
format = "colored-line-number"
[linters]
enable = [
"gocyclo", "unconvert", "goimports", "unused", "unused",
"vetshadow", "nakedret", "errcheck", "revive", "ineffassign",
"goconst", "vet", "unparam", "gofmt"
]
[issues]
exclude-use-default = false

50
vendor/github.com/oasdiff/yaml/LICENSE generated vendored Normal file
View File

@ -0,0 +1,50 @@
The MIT License (MIT)
Copyright (c) 2014 Sam Ghods
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

133
vendor/github.com/oasdiff/yaml/README.md generated vendored Normal file
View File

@ -0,0 +1,133 @@
# YAML marshaling and unmarshaling support for Go
[![Lint](https://github.com/invopop/yaml/actions/workflows/lint.yaml/badge.svg)](https://github.com/invopop/yaml/actions/workflows/lint.yaml)
[![Test Go](https://github.com/invopop/yaml/actions/workflows/test.yaml/badge.svg)](https://github.com/invopop/yaml/actions/workflows/test.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/invopop/yaml)](https://goreportcard.com/report/github.com/invopop/yaml)
![Latest Tag](https://img.shields.io/github/v/tag/invopop/yaml)
## Fork
This fork is an improved version of the invopop/yaml package, designed to include line and column location information for YAML elements during unmarshalling.
To include location information use ```UnmarshalWithOrigin``` instead of ```Unmarshal```.
The heavy lifting is done by the underlying [oasdiff/yaml3](https://github.com/oasdiff/yaml3) package.
## Introduction
A wrapper around [go-yaml](https://github.com/go-yaml/yaml) designed to enable a better way of handling YAML when marshaling to and from structs.
This is a fork and split of the original [ghodss/yaml](https://github.com/ghodss/yaml) repository which no longer appears to be maintained.
In short, this library first converts YAML to JSON using go-yaml and then uses `json.Marshal` and `json.Unmarshal` to convert to or from the struct. This means that it effectively reuses the JSON struct tags as well as the custom JSON methods `MarshalJSON` and `UnmarshalJSON` unlike go-yaml. For a detailed overview of the rationale behind this method, [see this blog post](https://web.archive.org/web/20150812020634/http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/).
## Compatibility
This package uses [go-yaml](https://github.com/go-yaml/yaml) and therefore supports [everything go-yaml supports](https://github.com/go-yaml/yaml#compatibility).
Tested against Go versions 1.14 and onwards.
## Caveats
**Caveat #1:** When using `yaml.Marshal` and `yaml.Unmarshal`, binary data should NOT be preceded with the `!!binary` YAML tag. If you do, go-yaml will convert the binary data from base64 to native binary data, which is not compatible with JSON. You can still use binary in your YAML files though - just store them without the `!!binary` tag and decode the base64 in your code (e.g. in the custom JSON methods `MarshalJSON` and `UnmarshalJSON`). This also has the benefit that your YAML and your JSON binary data will be decoded exactly the same way. As an example:
```
BAD:
exampleKey: !!binary gIGC
GOOD:
exampleKey: gIGC
... and decode the base64 data in your code.
```
**Caveat #2:** When using `YAMLToJSON` directly, maps with keys that are maps will result in an error since this is not supported by JSON. This error will occur in `Unmarshal` as well since you can't unmarshal map keys anyways since struct fields can't be keys.
## Installation and usage
To install, run:
```
$ go get github.com/invopop/yaml
```
And import using:
```
import "github.com/invopop/yaml"
```
Usage is very similar to the JSON library:
```go
package main
import (
"fmt"
"github.com/invopop/yaml"
)
type Person struct {
Name string `json:"name"` // Affects YAML field names too.
Age int `json:"age"`
}
func main() {
// Marshal a Person struct to YAML.
p := Person{"John", 30}
y, err := yaml.Marshal(p)
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
fmt.Println(string(y))
/* Output:
age: 30
name: John
*/
// Unmarshal the YAML back into a Person struct.
var p2 Person
err = yaml.Unmarshal(y, &p2)
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
fmt.Println(p2)
/* Output:
{John 30}
*/
}
```
`yaml.YAMLToJSON` and `yaml.JSONToYAML` methods are also available:
```go
package main
import (
"fmt"
"github.com/invopop/yaml"
)
func main() {
j := []byte(`{"name": "John", "age": 30}`)
y, err := yaml.JSONToYAML(j)
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
fmt.Println(string(y))
/* Output:
name: John
age: 30
*/
j2, err := yaml.YAMLToJSON(y)
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
fmt.Println(string(j2))
/* Output:
{"age":30,"name":"John"}
*/
}
```

499
vendor/github.com/oasdiff/yaml/fields.go generated vendored Normal file
View File

@ -0,0 +1,499 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package yaml
import (
"bytes"
"encoding"
"encoding/json"
"reflect"
"sort"
"strings"
"sync"
"unicode"
"unicode/utf8"
)
// indirect walks down v allocating pointers as needed,
// until it gets to a non-pointer.
// if it encounters an Unmarshaler, indirect stops and returns that.
// if decodingNull is true, indirect stops at the last pointer so it can be set to nil.
func indirect(v reflect.Value, decodingNull bool) (json.Unmarshaler, encoding.TextUnmarshaler, reflect.Value) {
// If v is a named type and is addressable,
// start with its address, so that if the type has pointer methods,
// we find them.
if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
v = v.Addr()
}
for {
// Load value from interface, but only if the result will be
// usefully addressable.
if v.Kind() == reflect.Interface && !v.IsNil() {
e := v.Elem()
if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
v = e
continue
}
}
if v.Kind() != reflect.Ptr {
break
}
if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() {
break
}
if v.IsNil() {
if v.CanSet() {
v.Set(reflect.New(v.Type().Elem()))
} else {
v = reflect.New(v.Type().Elem())
}
}
if v.Type().NumMethod() > 0 {
if u, ok := v.Interface().(json.Unmarshaler); ok {
return u, nil, reflect.Value{}
}
if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
return nil, u, reflect.Value{}
}
}
v = v.Elem()
}
return nil, nil, v
}
// A field represents a single field found in a struct.
type field struct {
name string
nameBytes []byte // []byte(name)
equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent
tag bool
index []int
typ reflect.Type
omitEmpty bool
quoted bool
}
func fillField(f field) field {
f.nameBytes = []byte(f.name)
f.equalFold = foldFunc(f.nameBytes)
return f
}
// byName sorts field by name, breaking ties with depth,
// then breaking ties with "name came from json tag", then
// breaking ties with index sequence.
type byName []field
func (x byName) Len() int { return len(x) }
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x byName) Less(i, j int) bool {
if x[i].name != x[j].name {
return x[i].name < x[j].name
}
if len(x[i].index) != len(x[j].index) {
return len(x[i].index) < len(x[j].index)
}
if x[i].tag != x[j].tag {
return x[i].tag
}
return byIndex(x).Less(i, j)
}
// byIndex sorts field by index sequence.
type byIndex []field
func (x byIndex) Len() int { return len(x) }
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x byIndex) Less(i, j int) bool {
for k, xik := range x[i].index {
if k >= len(x[j].index) {
return false
}
if xik != x[j].index[k] {
return xik < x[j].index[k]
}
}
return len(x[i].index) < len(x[j].index)
}
// typeFields returns a list of fields that JSON should recognize for the given type.
// The algorithm is breadth-first search over the set of structs to include - the top struct
// and then any reachable anonymous structs.
func typeFields(t reflect.Type) []field {
// Anonymous fields to explore at the current level and the next.
current := []field{}
next := []field{{typ: t}}
// Count of queued names for current level and the next.
var count, nextCount map[reflect.Type]int
// Types already visited at an earlier level.
visited := map[reflect.Type]bool{}
// Fields found.
var fields []field
for len(next) > 0 {
current, next = next, current[:0]
count, nextCount = nextCount, map[reflect.Type]int{}
for _, f := range current {
if visited[f.typ] {
continue
}
visited[f.typ] = true
// Scan f.typ for fields to include.
for i := 0; i < f.typ.NumField(); i++ {
sf := f.typ.Field(i)
if sf.PkgPath != "" { // unexported
continue
}
tag := sf.Tag.Get("json")
if tag == "-" {
continue
}
name, opts := parseTag(tag)
if !isValidTag(name) {
name = ""
}
index := make([]int, len(f.index)+1)
copy(index, f.index)
index[len(f.index)] = i
ft := sf.Type
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
// Follow pointer.
ft = ft.Elem()
}
// Record found field and index sequence.
if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
tagged := name != ""
if name == "" {
name = sf.Name
}
fields = append(fields, fillField(field{
name: name,
tag: tagged,
index: index,
typ: ft,
omitEmpty: opts.Contains("omitempty"),
quoted: opts.Contains("string"),
}))
if count[f.typ] > 1 {
// If there were multiple instances, add a second,
// so that the annihilation code will see a duplicate.
// It only cares about the distinction between 1 or 2,
// so don't bother generating any more copies.
fields = append(fields, fields[len(fields)-1])
}
continue
}
// Record new anonymous struct to explore in next round.
nextCount[ft]++
if nextCount[ft] == 1 {
next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft}))
}
}
}
}
sort.Sort(byName(fields))
// Delete all fields that are hidden by the Go rules for embedded fields,
// except that fields with JSON tags are promoted.
// The fields are sorted in primary order of name, secondary order
// of field index length. Loop over names; for each name, delete
// hidden fields by choosing the one dominant field that survives.
out := fields[:0]
for advance, i := 0, 0; i < len(fields); i += advance {
// One iteration per name.
// Find the sequence of fields with the name of this first field.
fi := fields[i]
name := fi.name
for advance = 1; i+advance < len(fields); advance++ {
fj := fields[i+advance]
if fj.name != name {
break
}
}
if advance == 1 { // Only one field with this name
out = append(out, fi)
continue
}
dominant, ok := dominantField(fields[i : i+advance])
if ok {
out = append(out, dominant)
}
}
fields = out
sort.Sort(byIndex(fields))
return fields
}
// dominantField looks through the fields, all of which are known to
// have the same name, to find the single field that dominates the
// others using Go's embedding rules, modified by the presence of
// JSON tags. If there are multiple top-level fields, the boolean
// will be false: This condition is an error in Go and we skip all
// the fields.
func dominantField(fields []field) (field, bool) {
// The fields are sorted in increasing index-length order. The winner
// must therefore be one with the shortest index length. Drop all
// longer entries, which is easy: just truncate the slice.
length := len(fields[0].index)
tagged := -1 // Index of first tagged field.
for i, f := range fields {
if len(f.index) > length {
fields = fields[:i]
break
}
if f.tag {
if tagged >= 0 {
// Multiple tagged fields at the same level: conflict.
// Return no field.
return field{}, false
}
tagged = i
}
}
if tagged >= 0 {
return fields[tagged], true
}
// All remaining fields have the same length. If there's more than one,
// we have a conflict (two fields named "X" at the same level) and we
// return no field.
if len(fields) > 1 {
return field{}, false
}
return fields[0], true
}
var fieldCache struct {
sync.RWMutex
m map[reflect.Type][]field
}
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
func cachedTypeFields(t reflect.Type) []field {
fieldCache.RLock()
f := fieldCache.m[t]
fieldCache.RUnlock()
if f != nil {
return f
}
// Compute fields without lock.
// Might duplicate effort but won't hold other computations back.
f = typeFields(t)
if f == nil {
f = []field{}
}
fieldCache.Lock()
if fieldCache.m == nil {
fieldCache.m = map[reflect.Type][]field{}
}
fieldCache.m[t] = f
fieldCache.Unlock()
return f
}
func isValidTag(s string) bool {
if s == "" {
return false
}
for _, c := range s {
switch {
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
// Backslash and quote chars are reserved, but
// otherwise any punctuation chars are allowed
// in a tag name.
default:
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
return false
}
}
}
return true
}
const (
caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
kelvin = '\u212a'
smallLongEss = '\u017f'
)
// foldFunc returns one of four different case folding equivalence
// functions, from most general (and slow) to fastest:
//
// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8
// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S')
// 3) asciiEqualFold, no special, but includes non-letters (including _)
// 4) simpleLetterEqualFold, no specials, no non-letters.
//
// The letters S and K are special because they map to 3 runes, not just 2:
// - S maps to s and to U+017F 'ſ' Latin small letter long s
// - k maps to K and to U+212A '' Kelvin sign
//
// See http://play.golang.org/p/tTxjOc0OGo
//
// The returned function is specialized for matching against s and
// should only be given s. It's not curried for performance reasons.
func foldFunc(s []byte) func(s, t []byte) bool {
nonLetter := false
special := false // special letter
for _, b := range s {
if b >= utf8.RuneSelf {
return bytes.EqualFold
}
upper := b & caseMask
if upper < 'A' || upper > 'Z' {
nonLetter = true
} else if upper == 'K' || upper == 'S' {
// See above for why these letters are special.
special = true
}
}
if special {
return equalFoldRight
}
if nonLetter {
return asciiEqualFold
}
return simpleLetterEqualFold
}
// equalFoldRight is a specialization of bytes.EqualFold when s is
// known to be all ASCII (including punctuation), but contains an 's',
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
// See comments on foldFunc.
func equalFoldRight(s, t []byte) bool {
for _, sb := range s {
if len(t) == 0 {
return false
}
tb := t[0]
if tb < utf8.RuneSelf {
if sb != tb {
sbUpper := sb & caseMask
if 'A' <= sbUpper && sbUpper <= 'Z' {
if sbUpper != tb&caseMask {
return false
}
} else {
return false
}
}
t = t[1:]
continue
}
// sb is ASCII and t is not. t must be either kelvin
// sign or long s; sb must be s, S, k, or K.
tr, size := utf8.DecodeRune(t)
switch sb {
case 's', 'S':
if tr != smallLongEss {
return false
}
case 'k', 'K':
if tr != kelvin {
return false
}
default:
return false
}
t = t[size:]
}
return len(t) <= 0
}
// asciiEqualFold is a specialization of bytes.EqualFold for use when
// s is all ASCII (but may contain non-letters) and contains no
// special-folding letters.
// See comments on foldFunc.
func asciiEqualFold(s, t []byte) bool {
if len(s) != len(t) {
return false
}
for i, sb := range s {
tb := t[i]
if sb == tb {
continue
}
if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
if sb&caseMask != tb&caseMask {
return false
}
} else {
return false
}
}
return true
}
// simpleLetterEqualFold is a specialization of bytes.EqualFold for
// use when s is all ASCII letters (no underscores, etc) and also
// doesn't contain 'k', 'K', 's', or 'S'.
// See comments on foldFunc.
func simpleLetterEqualFold(s, t []byte) bool {
if len(s) != len(t) {
return false
}
for i, b := range s {
if b&caseMask != t[i]&caseMask {
return false
}
}
return true
}
// tagOptions is the string following a comma in a struct field's "json"
// tag, or the empty string. It does not include the leading comma.
type tagOptions string
// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:])
}
return tag, tagOptions("")
}
// Contains reports whether a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 {
return false
}
s := string(o)
for s != "" {
var next string
i := strings.Index(s, ",")
if i >= 0 {
s, next = s[:i], s[i+1:]
}
if s == optionName {
return true
}
s = next
}
return false
}

322
vendor/github.com/oasdiff/yaml/yaml.go generated vendored Normal file
View File

@ -0,0 +1,322 @@
// Package yaml provides a wrapper around go-yaml designed to enable a better
// way of handling YAML when marshaling to and from structs.
//
// In short, this package first converts YAML to JSON using go-yaml and then
// uses json.Marshal and json.Unmarshal to convert to or from the struct. This
// means that it effectively reuses the JSON struct tags as well as the custom
// JSON methods MarshalJSON and UnmarshalJSON unlike go-yaml.
package yaml // import "github.com/invopop/yaml"
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"reflect"
"strconv"
"github.com/oasdiff/yaml3"
)
// Marshal the object into JSON then converts JSON to YAML and returns the
// YAML.
func Marshal(o interface{}) ([]byte, error) {
j, err := json.Marshal(o)
if err != nil {
return nil, fmt.Errorf("error marshaling into JSON: %v", err)
}
y, err := JSONToYAML(j)
if err != nil {
return nil, fmt.Errorf("error converting JSON to YAML: %v", err)
}
return y, nil
}
// JSONOpt is a decoding option for decoding from JSON format.
type JSONOpt func(*json.Decoder) *json.Decoder
// YAMLOpt is a decoding option for decoding from YAML format.
type YAMLOpt func(*yaml.Decoder) *yaml.Decoder
// Unmarshal converts YAML to JSON then uses JSON to unmarshal into an object,
// optionally configuring the behavior of the JSON unmarshal.
func Unmarshal(y []byte, o interface{}, opts ...JSONOpt) error {
return UnmarshalWithOrigin(y, o, false, opts...)
}
// UnmarshalWithOrigin is like Unmarshal but if withOrigin is true, it will
// include the origin information in the output.
func UnmarshalWithOrigin(y []byte, o interface{}, withOrigin bool, opts ...JSONOpt) error {
dec := yaml.NewDecoder(bytes.NewReader(y))
dec.Origin(withOrigin)
return unmarshal(dec, o, opts)
}
func unmarshal(dec *yaml.Decoder, o interface{}, opts []JSONOpt) error {
vo := reflect.ValueOf(o)
j, err := yamlToJSON(dec, &vo)
if err != nil {
return fmt.Errorf("error converting YAML to JSON: %v", err)
}
err = jsonUnmarshal(bytes.NewReader(j), o, opts...)
if err != nil {
return fmt.Errorf("error unmarshaling JSON: %v", err)
}
return nil
}
// jsonUnmarshal unmarshals the JSON byte stream from the given reader into the
// object, optionally applying decoder options prior to decoding. We are not
// using json.Unmarshal directly as we want the chance to pass in non-default
// options.
func jsonUnmarshal(r io.Reader, o interface{}, opts ...JSONOpt) error {
d := json.NewDecoder(r)
for _, opt := range opts {
d = opt(d)
}
if err := d.Decode(&o); err != nil {
return fmt.Errorf("while decoding JSON: %v", err)
}
return nil
}
// JSONToYAML converts JSON to YAML.
func JSONToYAML(j []byte) ([]byte, error) {
// Convert the JSON to an object.
var jsonObj interface{}
// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
// Go JSON library doesn't try to pick the right number type (int, float,
// etc.) when unmarshalling to interface{}, it just picks float64
// universally. go-yaml does go through the effort of picking the right
// number type, so we can preserve number type throughout this process.
err := yaml.Unmarshal(j, &jsonObj)
if err != nil {
return nil, err
}
// Marshal this object into YAML.
return yaml.Marshal(jsonObj)
}
// YAMLToJSON converts YAML to JSON. Since JSON is a subset of YAML,
// passing JSON through this method should be a no-op.
//
// Things YAML can do that are not supported by JSON:
// - In YAML you can have binary and null keys in your maps. These are invalid
// in JSON. (int and float keys are converted to strings.)
// - Binary data in YAML with the !!binary tag is not supported. If you want to
// use binary data with this library, encode the data as base64 as usual but do
// not use the !!binary tag in your YAML. This will ensure the original base64
// encoded data makes it all the way through to the JSON.
func YAMLToJSON(y []byte) ([]byte, error) { //nolint:revive
dec := yaml.NewDecoder(bytes.NewReader(y))
return yamlToJSON(dec, nil)
}
func yamlToJSON(dec *yaml.Decoder, jsonTarget *reflect.Value) ([]byte, error) {
// Convert the YAML to an object.
var yamlObj interface{}
if err := dec.Decode(&yamlObj); err != nil {
// Functionality changed in v3 which means we need to ignore EOF error.
// See https://github.com/go-yaml/yaml/issues/639
if !errors.Is(err, io.EOF) {
return nil, err
}
}
// YAML objects are not completely compatible with JSON objects (e.g. you
// can have non-string keys in YAML). So, convert the YAML-compatible object
// to a JSON-compatible object, failing with an error if irrecoverable
// incompatibilities happen along the way.
jsonObj, err := convertToJSONableObject(yamlObj, jsonTarget)
if err != nil {
return nil, err
}
// Convert this object to JSON and return the data.
return json.Marshal(jsonObj)
}
func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (interface{}, error) { //nolint:gocyclo
var err error
// Resolve jsonTarget to a concrete value (i.e. not a pointer or an
// interface). We pass decodingNull as false because we're not actually
// decoding into the value, we're just checking if the ultimate target is a
// string.
if jsonTarget != nil {
ju, tu, pv := indirect(*jsonTarget, false)
// We have a JSON or Text Umarshaler at this level, so we can't be trying
// to decode into a string.
if ju != nil || tu != nil {
jsonTarget = nil
} else {
jsonTarget = &pv
}
}
// go-yaml v3 changed from v2 and now will provide map[string]interface{} by
// default and map[interface{}]interface{} when none of the keys strings.
// To get around this, we run a pre-loop to convert the map.
// JSON only supports strings as keys, so we must convert.
switch typedYAMLObj := yamlObj.(type) {
case map[interface{}]interface{}:
// From my reading of go-yaml v2 (specifically the resolve function),
// keys can only have the types string, int, int64, float64, binary
// (unsupported), or null (unsupported).
strMap := make(map[string]interface{})
for k, v := range typedYAMLObj {
// Resolve the key to a string first.
var keyString string
switch typedKey := k.(type) {
case string:
keyString = typedKey
case int:
keyString = strconv.Itoa(typedKey)
case int64:
// go-yaml will only return an int64 as a key if the system
// architecture is 32-bit and the key's value is between 32-bit
// and 64-bit. Otherwise the key type will simply be int.
keyString = strconv.FormatInt(typedKey, 10)
case float64:
// Float64 is now supported in keys
keyString = strconv.FormatFloat(typedKey, 'g', -1, 64)
case bool:
if typedKey {
keyString = "true"
} else {
keyString = "false"
}
default:
return nil, fmt.Errorf("unsupported map key of type: %s, key: %+#v, value: %+#v",
reflect.TypeOf(k), k, v)
}
strMap[keyString] = v
}
// replace yamlObj with our new string map
yamlObj = strMap
}
// If yamlObj is a number or a boolean, check if jsonTarget is a string -
// if so, coerce. Else return normal.
// If yamlObj is a map or array, find the field that each key is
// unmarshaling to, and when you recurse pass the reflect.Value for that
// field back into this function.
switch typedYAMLObj := yamlObj.(type) {
case map[string]interface{}:
for k, v := range typedYAMLObj {
// jsonTarget should be a struct or a map. If it's a struct, find
// the field it's going to map to and pass its reflect.Value. If
// it's a map, find the element type of the map and pass the
// reflect.Value created from that type. If it's neither, just pass
// nil - JSON conversion will error for us if it's a real issue.
if jsonTarget != nil {
t := *jsonTarget
if t.Kind() == reflect.Struct {
keyBytes := []byte(k)
// Find the field that the JSON library would use.
var f *field
fields := cachedTypeFields(t.Type())
for i := range fields {
ff := &fields[i]
if bytes.Equal(ff.nameBytes, keyBytes) {
f = ff
break
}
// Do case-insensitive comparison.
if f == nil && ff.equalFold(ff.nameBytes, keyBytes) {
f = ff
}
}
if f != nil {
// Find the reflect.Value of the most preferential
// struct field.
jtf := t.Field(f.index[0])
typedYAMLObj[k], err = convertToJSONableObject(v, &jtf)
if err != nil {
return nil, err
}
continue
}
} else if t.Kind() == reflect.Map {
// Create a zero value of the map's element type to use as
// the JSON target.
jtv := reflect.Zero(t.Type().Elem())
typedYAMLObj[k], err = convertToJSONableObject(v, &jtv)
if err != nil {
return nil, err
}
continue
}
}
typedYAMLObj[k], err = convertToJSONableObject(v, nil)
if err != nil {
return nil, err
}
}
return typedYAMLObj, nil
case []interface{}:
// We need to recurse into arrays in case there are any
// map[interface{}]interface{}'s inside and to convert any
// numbers to strings.
// If jsonTarget is a slice (which it really should be), find the
// thing it's going to map to. If it's not a slice, just pass nil
// - JSON conversion will error for us if it's a real issue.
var jsonSliceElemValue *reflect.Value
if jsonTarget != nil {
t := *jsonTarget
if t.Kind() == reflect.Slice {
// By default slices point to nil, but we need a reflect.Value
// pointing to a value of the slice type, so we create one here.
ev := reflect.Indirect(reflect.New(t.Type().Elem()))
jsonSliceElemValue = &ev
}
}
// Make and use a new array.
arr := make([]interface{}, len(typedYAMLObj))
for i, v := range typedYAMLObj {
arr[i], err = convertToJSONableObject(v, jsonSliceElemValue)
if err != nil {
return nil, err
}
}
return arr, nil
default:
// If the target type is a string and the YAML type is a number,
// convert the YAML type to a string.
if jsonTarget != nil && (*jsonTarget).Kind() == reflect.String {
// Based on my reading of go-yaml, it may return int, int64,
// float64, or uint64.
var s string
switch typedVal := typedYAMLObj.(type) {
case int:
s = strconv.FormatInt(int64(typedVal), 10)
case int64:
s = strconv.FormatInt(typedVal, 10)
case float64:
s = strconv.FormatFloat(typedVal, 'g', -1, 64)
case uint64:
s = strconv.FormatUint(typedVal, 10)
case bool:
if typedVal {
s = "true"
} else {
s = "false"
}
}
if len(s) > 0 {
yamlObj = interface{}(s)
}
}
return yamlObj, nil
}
}

Some files were not shown because too many files have changed in this diff Show More