diff --git a/go.mod b/go.mod index 241973a..26b2fca 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.13 require ( github.com/rs/cors v1.7.0 github.com/spaceapi-community/go-spaceapi-validator v0.2.0 + github.com/swaggo/http-swagger v1.3.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect goji.io v2.0.2+incompatible golang.org/x/time v0.0.0-20191024005414-555d28b269f0 diff --git a/go.sum b/go.sum index 9d9a7ee..5969ce5 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,69 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= +github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +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.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaceapi-community/go-spaceapi-validator v0.2.0 h1:Um+nDKIRhA7zhuxU3LQ28y4gFwLFn+wJ8MBofovfj2Q= github.com/spaceapi-community/go-spaceapi-validator v0.2.0/go.mod h1:QwRul/7SjshUowS6hZsh+T9WOOpR+NAQGSc0kesyIZY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= +github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= +github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww= +github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ= +github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI= +github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -16,7 +71,63 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= goji.io v2.0.2+incompatible h1:uIssv/elbKRLznFUy3Xj4+2Mz/qKhek/9aZQDUMae7c= goji.io v2.0.2+incompatible/go.mod h1:sbqFwrtqZACxLBTQcdgVjFh54yGVCvwq8+w49MVMMIk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 3e17db5..b3cdb4a 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,8 @@ import ( "github.com/rs/cors" "github.com/spaceapi/validator/v1" "github.com/spaceapi/validator/v2" + "github.com/spaceapi/validator/v3" + "github.com/swaggo/http-swagger" "goji.io" "goji.io/pat" "log" @@ -19,7 +21,14 @@ func main() { root.Use(c.Handler) root.HandleFunc(pat.Get("/"), versionRedirect) + root.HandleFunc(pat.Get("/openapi.json"), openAPI) + root.Handle(pat.New("/swagger/*"), httpSwagger.Handler( + httpSwagger.URL("http://localhost:8080/openapi.json"), + )) + root.HandleFunc(pat.Get("/swagger"), func(writer http.ResponseWriter, request *http.Request) { + http.Redirect(writer, request, "/swagger/index.html", 302) + }) root.HandleFunc(pat.Get("/v1"), func(writer http.ResponseWriter, request *http.Request) { http.Redirect(writer, request, "/v1/", 302) @@ -27,16 +36,20 @@ func main() { root.HandleFunc(pat.Get("/v2"), func(writer http.ResponseWriter, request *http.Request) { http.Redirect(writer, request, "/v2/", 302) }) + root.HandleFunc(pat.Get("/v3"), func(writer http.ResponseWriter, request *http.Request) { + http.Redirect(writer, request, "/v3/", 302) + }) root.Handle(pat.New("/v1/*"), v1.GetSubMux()) root.Handle(pat.New("/v2/*"), v2.GetSubMux()) + root.Handle(pat.New("/v3/*"), v3.GetSubMux()) log.Println("starting validator on port 8080...") log.Fatal(http.ListenAndServe(":8080", root)) } func versionRedirect(writer http.ResponseWriter, request *http.Request) { - http.Redirect(writer, request, "/v1/", 302) + http.Redirect(writer, request, "/v3/", 302) } func openAPI(writer http.ResponseWriter, _ *http.Request) { diff --git a/main_test.go b/main_test.go index 8a94fb1..cb9ee1e 100644 --- a/main_test.go +++ b/main_test.go @@ -22,7 +22,7 @@ func TestRootRedirect(t *testing.T) { status, http.StatusFound) } - if location := rr.Header().Get("Location"); location != "/v1/" { + if location := rr.Header().Get("Location"); location != "/v3/" { t.Errorf("handler returned wrong status code: got %v want %v", location, "/v1/") } diff --git a/openapi.go b/openapi.go index 962cd6f..cad37c8 100644 --- a/openapi.go +++ b/openapi.go @@ -4,7 +4,7 @@ var openapi = `{ "openapi": "3.0.2", "info": { "description": "This is the SpaceApi Validator api", - "version": "1.2.0", + "version": "1.3.0", "title": "SpaceApi Validator" }, "servers": [ @@ -76,6 +76,7 @@ var openapi = `{ "tags": [ "v2" ], + "deprecated": true, "responses": { "200": { "description": "get default information about the server", @@ -95,6 +96,7 @@ var openapi = `{ "tags": [ "v2" ], + "deprecated": true, "summary": "validate an input against the SpaceApi schema", "requestBody": { "required": true, @@ -131,6 +133,7 @@ var openapi = `{ "tags": [ "v2" ], + "deprecated": true, "summary": "validate an input against the SpaceApi schema", "requestBody": { "required": true, @@ -161,6 +164,101 @@ var openapi = `{ } } } + }, + "/v3": { + "get": { + "tags": [ + "v3" + ], + "summary": "A short summary of the service and how to use it.", + "responses": { + "200": { + "description": "A short summart of the service and how to use it.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServerInformation" + } + } + } + } + } + } + }, + "/v3/validateURL": { + "post": { + "tags": [ + "v3" + ], + "summary": "Validate a SpaceAPI endpoint URL against all SpaceApi schema versions it claims to support.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidateUrlV3" + } + } + } + }, + "responses": { + "200": { + "description": "Successful opperation. Whether the document passed validation can be taken from the 'valid' key in the response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidateUrlV3Response" + } + } + } + }, + "400": { + "description": "The request body is malformed." + }, + "429": { + "description": "The request was rate limited." + }, + "500": { + "description": "Something went wrong." + } + } + } + }, + "/v3/validateJSON": { + "post": { + "tags": [ + "v3" + ], + "summary": "Validate a JSON document against all SpaceApi schema versions it claims to support.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidateJsonV3" + } + } + } + }, + "responses": { + "200": { + "description": "Successful opperation. Whether the document passed validation can be taken from the 'valid' key in the response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidateJsonV3Response" + } + } + } + }, + "400": { + "description": "The request body is malformed." + }, + "500": { + "description": "Something went wrong." + } + } + } } }, "components": { @@ -168,13 +266,19 @@ var openapi = `{ "ServerInformation": { "properties": { "description": { - "type": "string" + "type": "string", + "description": "A short description of the service.", + "example": "The SpaceAPI validator service." }, "usage": { - "type": "string" + "type": "string", + "description": "A short summary of how to use the service.", + "example": "To validate a JSON document, send a POST request to /v3/validateJSON. To validate an endpoint URL, send a POST request to /v3/validateURL. See /swagger for an API documentation." }, "version": { - "type": "string" + "type": "string", + "description": "Server version information.", + "example": "1.3.0" } }, "required": [ @@ -280,7 +384,7 @@ var openapi = `{ }, "message": { "type": "string" - }, + }, "checkedVersions": { "type": "array", "items": { @@ -302,6 +406,191 @@ var openapi = `{ "message" ] }, + "ValidateJsonV3": { + "type": "object", + "description": "The JSON document to validate." + }, + "ValidateJsonV3Response": { + "properties": { + "valid": { + "type": "boolean", + "description": "Whether the JSON document was valid according to all SpaceAPI schema versions it claims to support." + }, + "message": { + "type": "string" + }, + "checkedVersions": { + "type": "array", + "description": "The list of SpaceAPI schema versions the document was validated against, both from the 'api' and 'api_compatibility' keys.", + "items": { + "type": "string" + } + }, + "validatedJson": { + "type": "object", + "description": "The JSON document that was validated." + }, + "schemaErrors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SchemaError" + } + } + }, + "required": [ + "valid", + "checkedVersions" + ] + }, + "ValidateUrlV3": { + "properties": { + "url": { + "type": "string", + "description": "The URL of the endpoint to validate.", + "pattern": "uri", + "example": "http://example.org/spaceapi" + }, + "extendedValidation": { + "type": "boolean", + "description": "Whether to perform some extended validation (e.g. IP dual-stack capability, User-Agent filtering). The exact set of validation parameters is implementation dependent, but is returned in the response. If false, the response is guaranteed to only contain a single result. If true, multiple results may be returned in the response.", + "default": false + }, + "withValidatedJson": { + "type": "boolean", + "description": "Whether the endpoint response should be included in the validator response.", + "default": true + } + }, + "required": [ + "url" + ] + }, + "ValidateUrlV3Parameters": { + "properties": { + "addressFamily": { + "type": "string", + "description": "The IP address family restriction used to connect to the endpoint.", + "enum": [ + "any", + "ipv6", + "ipv4" + ] + }, + "userAgent": { + "type": "string", + "description": "The User-Agent header used to connect to the endpoint.", + "example": "Go-http-client/1.1" + } + }, + "required": [ + "addressFamily", + "userAgent" + ] + }, + "ValidateUrlV3Result": { + "properties": { + "parameters": { + "$ref": "#/components/schemas/ValidateUrlV3Parameters", + "description": "The validation parameters (e.g. IP address family, HTTP User-Agent) this result applies to." + }, + "originalUrl": { + "type": "string", + "description": "The URL as it was submitted to the validator, possibly with some normalizations (e.g. fragment removal) applied.", + "pattern": "uri", + "example": "http://example.org/spaceapi" + }, + "redirectedUrl": { + "type": "string", + "description": "The final URL encountered during the request and subsequent redirects. If no redirect occurred, this is the same as 'originalUrl'.", + "pattern": "uri", + "example": "https://example.org/spaceapi" + }, + "reachable": { + "type": "boolean", + "description": "True if the endpoint accepted the connection and responded to the HTTP request with a (not necessarily successful) HTTP response. (NOTE: This is different from v2, where 'reachable' also implied a HTTP 200 OK response.)", + "example": true + }, + "httpStatus": { + "type": "integer", + "description": "The HTTP status code to the final request. 0 if 'reachable' is false.", + "minimum": 0, + "maximum": 999, + "example": 200 + }, + "isHttps": { + "type": "boolean", + "description": "Whether the final request was done using HTTPS.", + "example": true + }, + "httpsRedirect": { + "type": "boolean", + "description": "Whether the request was upgraded from HTTP to HTTPS.", + "example": true + }, + "permanentRedirect": { + "type": "boolean", + "description": "True if all HTTP redirects encountered were permanent (HTTP status code 301 or 308). Absent if no redirect was encountered.", + "example": true + }, + "cors": { + "type": "boolean", + "description": "True if all HTTP responses (including redirects) set an 'Access-Control-Allow-Origin: *' header.", + "example": true + }, + "contentType": { + "type": "boolean", + "description": "Whether the final response serves the correct content type header: 'Content-Type: application/json'", + "example": true + }, + "invalidCert": { + "type": "boolean", + "description": "Whether there was a problem with HTTPS certificate validation. Always true for plain HTTP." + }, + "validation": { + "$ref": "#/components/schemas/ValidateJsonV3Response", + "description": "The result of the SpaceAPI schema validation itself." + } + }, + "required": [ + "parameters", + "originalUrl", + "redirectedUrl", + "reachable", + "isHttps", + "httpsRedirect", + "cors", + "contentType", + "invalidCert", + "validation" + ] + }, + "ValidateUrlV3Response": { + "properties": { + "results": { + "type": "array", + "description": "The list of paramter sets and their results.", + "items": { + "$ref": "#/components/schemas/ValidateUrlV3Result" + } + }, + "reachable": { + "type": "boolean", + "description": "True if the endpoint was reachable under at least one parameter set.", + "example": true + }, + "valid": { + "type": "boolean", + "description": "True if the endpoint was reachable under ALL parameter sets it was reachable under.", + "example": true + } + }, + "required": [ + "results", + "reachable", + "valid", + "inconsistencies" + ] + }, "SchemaError": { "properties": { "field": { diff --git a/v2/validator.go b/v2/validator.go index 672c4a2..12cbf29 100644 --- a/v2/validator.go +++ b/v2/validator.go @@ -3,6 +3,7 @@ package v2 import ( "crypto/tls" "encoding/json" + "errors" "fmt" spaceapivalidator "github.com/spaceapi-community/go-spaceapi-validator" "goji.io" @@ -190,6 +191,9 @@ func fetchURL(validationResponse *urlValidationResponse, url *url.URL, skipVerif client := http.Client{ Timeout: time.Second * 10, CheckRedirect: func(req *http.Request, via []*http.Request) error { + if len(via) >= 10 { + return errors.New("Too many redirects") + } if req.URL.Scheme == "https" { validationResponse.HTTPSForward = true } diff --git a/v3/addressfamily.go b/v3/addressfamily.go new file mode 100644 index 0000000..f357e77 --- /dev/null +++ b/v3/addressfamily.go @@ -0,0 +1,57 @@ +package v3 + +import ( + "bytes" + "encoding/json" +) + +type AddressFamily int + +const ( + Any AddressFamily = iota + IPv6 + IPv4 +) + +var toString = map[AddressFamily]string{ + Any: "any", + IPv6: "ipv6", + IPv4: "ipv4", +} + +var toAddressFamily = map[string]AddressFamily{ + "any": Any, + "ipv6": IPv6, + "ipv4": IPv4, +} + +var toTcpStack = map[AddressFamily]string{ + Any: "tcp", + IPv6: "tcp6", + IPv4: "tcp4", +} + +func (f AddressFamily) String() string { + return toString[f] +} + +func (f AddressFamily) TcpStack() string { + return toTcpStack[f] +} + +func (f AddressFamily) MarshalJSON() ([]byte, error) { + buffer := bytes.NewBufferString(`"`) + buffer.WriteString(toString[f]) + buffer.WriteString(`"`) + return buffer.Bytes(), nil +} + +func (f *AddressFamily) UnmarshalJSON(b []byte) error { + var str string + err := json.Unmarshal(b, &str) + if err != nil { + return err + } + *f = toAddressFamily[str] + return nil +} diff --git a/v3/validator.go b/v3/validator.go new file mode 100644 index 0000000..55581be --- /dev/null +++ b/v3/validator.go @@ -0,0 +1,361 @@ +package v3 + +import ( + "context" + "crypto/tls" + "encoding/json" + "errors" + spaceapivalidator "github.com/spaceapi-community/go-spaceapi-validator" + "goji.io" + "goji.io/pat" + "golang.org/x/time/rate" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "strings" + "time" +) + +const ( + defaultUserAgent = "SpaceApi Validator/1.3.0 (https://github.com/SpaceApi/validator)" +) + +type serverInfo struct { + Description string `json:"description"` + Usage string `json:"usage"` + Version string `json:"version"` +} + +type jsonValidationResponse struct { + Valid bool `json:"valid"` + Message string `json:"message,omitempty"` + CheckedVersions []string `json:"checkedVersions"` + ValidatedJson interface{} `json:"validatedJson,omitempty"` + SchemaErrors []schemaError `json:"schemaErrors,omitempty"` +} + +type urlValidationRequest struct { + URL string `json:"url"` + ExtendedValidation bool `json:"extendedValidation,omitempty"` + WithValidatedJson bool `json:"withValidatedJson,omitempty"` +} + +type urlValidationParameters struct { + AddressFamily AddressFamily `json:"addressFamily"` + UserAgent string `json:"userAgent"` +} + +type urlValidationResult struct { + Parameters urlValidationParameters `json:"parameters"` + OriginalURL string `json:"originalUrl"` + RedirectedURL string `json:"redirectedUrl"` + Reachable bool `json:"reachable"` + HTTPStatus int `json:"httpStatus"` + IsHTTPS bool `json:"isHttps"` + HTTPSRedirect bool `json:"httpsRedirect"` + PermanentRedirect bool `json:"permanentRedirect"` + CORS bool `json:"cors"` + ContentType bool `json:"contentType"` + InvalidCert bool `json:"invalidCert"` + Validation jsonValidationResponse `json:"validation"` +} + +type urlValidationResponse struct { + Results []urlValidationResult `json:"results"` + Reachable bool `json:"reachable"` + Valid bool `json:"valid"` +} + +type schemaError struct { + Field string `json:"field"` + Message string `json:"message"` +} + +// GetSubMux returns the versions subrouter +func GetSubMux() *goji.Mux { + v3 := goji.SubMux() + v3.HandleFunc(pat.Get("/"), getInfo) + v3.HandleFunc(pat.Post("/validateJSON"), postValidateJSON) + v3.Handle( + pat.Post("/validateURL"), + limit( + http.HandlerFunc(postValidateURL), + rate.NewLimiter(200, 500), // (rate, burst) + ), + ) + + return v3 +} + +func limit(next http.Handler, limiter *rate.Limiter) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if limiter.Allow() == false { + http.Error(w, http.StatusText(429), http.StatusTooManyRequests) + return + } + + next.ServeHTTP(w, r) + }) +} + +func getInfo(writer http.ResponseWriter, _ *http.Request) { + serverInfo := serverInfo{ + Description: "Space API Validator API", + Usage: "To validate a JSON document, send a POST request to /v3/validateJSON. To validate an endpoint URL, send a POST request to /v3/validateURL. See /swagger for an API documentation.", + Version: "1.3.0", + } + + err := json.NewEncoder(writer).Encode(serverInfo) + if err != nil { + http.Error(writer, err.Error(), http.StatusInternalServerError) + return + } +} + +func validateJSON(document []byte, withValidatedJson bool) (*jsonValidationResponse, error) { + + res, err := spaceapivalidator.Validate(string(document)) + if err != nil { + return nil, err + } + + var raw map[string]interface{} + if err := json.Unmarshal(document, &raw); err != nil { + return nil, err + } + + resp := jsonValidationResponse{ + Valid: res.Valid, + } + if withValidatedJson { + resp.ValidatedJson = raw + } + + for _, schema := range res.Schemas { + resp.CheckedVersions = append(resp.CheckedVersions, schema.Version) + } + + var errMsg string + for _, validatorError := range res.Errors { + errMsg = errMsg + validatorError.Context + ": " + validatorError.Description + "\n" + resp.SchemaErrors = append(resp.SchemaErrors, schemaError{ + Field: validatorError.Context, + Message: validatorError.Description, + }) + } + resp.Message = errMsg + + return &resp, nil +} + +func postValidateJSON(writer http.ResponseWriter, request *http.Request) { + if request.Body == nil { + http.Error(writer, "body can't be empty", http.StatusBadRequest) + return + } + body, err := ioutil.ReadAll(request.Body) + if err != nil { + http.Error(writer, err.Error(), http.StatusInternalServerError) + return + } + + resp, err := validateJSON(body, true) + if err != nil { + http.Error(writer, err.Error(), http.StatusBadRequest) + return + } + + writer.Header().Add("Content-Type", "application/json") + err = json.NewEncoder(writer).Encode(resp) + if err != nil { + http.Error(writer, err.Error(), http.StatusInternalServerError) + return + } +} + +func getTestCases(extended bool) []urlValidationParameters { + cases := []urlValidationParameters{} + if (extended) { + cases = append(cases, urlValidationParameters { + AddressFamily: IPv6, + UserAgent: defaultUserAgent, + }) + cases = append(cases, urlValidationParameters { + AddressFamily: IPv4, + UserAgent: defaultUserAgent, + }) + cases = append(cases, urlValidationParameters { + AddressFamily: Any, + UserAgent: "curl/8.16.0", + }) + cases = append(cases, urlValidationParameters { + AddressFamily: Any, + UserAgent: "Go-http-client/1.1", + }) + } else { + cases = append(cases, urlValidationParameters { + AddressFamily: Any, + UserAgent: defaultUserAgent, + }) + } + return cases +} + +func validateURL(params urlValidationParameters, req urlValidationRequest) (*urlValidationResult, error) { + result := urlValidationResult{ + Parameters: params, + } + + u, err := url.ParseRequestURI(req.URL) + if err != nil { + return nil, err + } + u.Fragment = "" + result.OriginalURL = u.String() + result.IsHTTPS = u.Scheme == "https" + + header, body, err := fetchURL(&result, ¶ms, u, false) + if err != nil { + return nil, err + } + result.Reachable = true + + if header != nil { + checkHeader(&result, header) + } + + if body == "" { + return &result, nil + } + + validation, err := validateJSON([]byte(body), req.WithValidatedJson) + if err != nil { + return nil, err + } + + result.Validation = *validation + return &result, nil +} + +func postValidateURL(writer http.ResponseWriter, request *http.Request) { + if request.Body == nil { + http.Error(writer, "body can't be empty", http.StatusBadRequest) + return + } + + var valReq urlValidationRequest + var valRes urlValidationResponse + + err := json.NewDecoder(request.Body).Decode(&valReq) + if err != nil { + http.Error(writer, err.Error(), http.StatusBadRequest) + return + } + testCases := getTestCases(valReq.ExtendedValidation) + + valRes.Results = make([]urlValidationResult, len(testCases)) + for i, testCase := range testCases { + result, err := validateURL(testCase, valReq) + if err != nil { + http.Error(writer, err.Error(), http.StatusBadRequest) + return + } + valRes.Results[i] = *result + } + + // Set "convenience flags" in the outer response + valRes.Valid = true + valRes.Reachable = false + for _, result := range valRes.Results { + if result.Reachable { + valRes.Reachable = true + if !result.Validation.Valid { + valRes.Valid = false + } + } + } + + writer.Header().Add("Content-Type", "application/json") + err = json.NewEncoder(writer).Encode(valRes) + if err != nil { + http.Error(writer, err.Error(), http.StatusInternalServerError) + return + } +} + +func checkHeader(result *urlValidationResult, header http.Header) { + acao := header.Get("Access-Control-Allow-Origin") + if acao == "*" { + result.CORS = true + } + + if strings.HasPrefix(header.Get("Content-Type"), "application/json") { + result.ContentType = true + } +} + +func fetchURL(result *urlValidationResult, params *urlValidationParameters, url *url.URL, skipVerify bool) (http.Header, string, error) { + dialer := net.Dialer{} + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: skipVerify}, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialer.DialContext(ctx, params.AddressFamily.TcpStack(), addr) + }, + } + + result.PermanentRedirect = true + client := http.Client{ + Timeout: time.Second * 10, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + if len(via) >= 10 { + return errors.New("Too many redirects") + } + result.RedirectedURL = req.URL.String() + if req.Response.StatusCode != http.StatusMovedPermanently && req.Response.StatusCode != http.StatusPermanentRedirect { + result.PermanentRedirect = false + } + if req.URL.Scheme == "https" { + result.HTTPSRedirect = true + } + return nil + }, + Transport: tr, + } + defer client.CloseIdleConnections() + + req, err := http.NewRequest("GET", url.String(), nil) + if err != nil { + return nil, "", err + } + + req.Header.Add("Origin", "https://validator.spaceapi.io") + req.Header.Add("User-Agent", params.UserAgent) + response, err := client.Do(req) + if err != nil { + if skipVerify == false { + return fetchURL(result, params, url, true) + } + + return nil, "", nil + } + + defer func() { + err := response.Body.Close() + if err != nil { + panic(err) + } + }() + + result.Reachable = true + result.HTTPStatus = response.StatusCode + if response.StatusCode >= 400 { + _, _ = io.Copy(ioutil.Discard, response.Body) + return nil, "", nil + } + + bodyArray, _ := ioutil.ReadAll(response.Body) + result.InvalidCert = !(result.IsHTTPS || result.HTTPSRedirect) || skipVerify + return response.Header, string(bodyArray), nil +} diff --git a/v3/validator_test.go b/v3/validator_test.go new file mode 100644 index 0000000..8e28723 --- /dev/null +++ b/v3/validator_test.go @@ -0,0 +1,390 @@ +package v3 + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +var validSpace = `{ + "api": "0.13", + "space": "my cool space", + "logo": "https://example.com/logo.png", + "url": "https://example.com", + "location": { + "address": "Ulmer Strasse 255, 70327 Stuttgart, Germany", + "lon": 9.236, + "lat": 48.777 + }, + "state": { + "open": false + }, + "contact": { + }, + "issue_report_channels": [ + "email" + ] +}` + +var invalidSpace = `{ "data": "asd" }` + +var invalidSpaceApiVersion = `{ + "api": 0.13, + "space": "my cool space", + "logo": "https://example.com/logo.png", + "url": "https://example.com", + "location": { + "address": "Ulmer Strasse 255, 70327 Stuttgart, Germany", + "lon": 9.236, + "lat": 48.777 + }, + "state": { + "open": false + }, + "contact": { + }, + "issue_report_channels": [ + "email" + ] +}` + +func forgeValidateJSONRequest(t *testing.T, body io.Reader) *httptest.ResponseRecorder { + req, err := http.NewRequest("POST", "/v3/validateJSON", body) + if err != nil { + t.Fatal(err) + } + rr := httptest.NewRecorder() + handler := http.HandlerFunc(validateJSON) + handler.ServeHTTP(rr, req) + return rr +} + +func forgeValidateURLRequest(t *testing.T, body io.Reader) *httptest.ResponseRecorder { + req, err := http.NewRequest("POST", "/v3/validateURL", body) + if err != nil { + t.Fatal(err) + } + rr := httptest.NewRecorder() + handler := http.HandlerFunc(validateURL) + handler.ServeHTTP(rr, req) + return rr +} + +//// VALIDATE JSON //// + +func TestValidateJsonWithValid(t *testing.T) { + rr := forgeValidateJSONRequest(t, strings.NewReader(validSpace)) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + resp := jsonValidationResponse{} + err := json.NewDecoder(rr.Body).Decode(&resp) + if err != nil { + t.Fatal(err) + } + + if resp.Valid != true { + t.Errorf("handler returned wrong response: got %v want %v", + resp.Valid, true) + } +} + +func TestValidateJsonWithInvalid(t *testing.T) { + rr := forgeValidateJSONRequest(t, strings.NewReader(invalidSpace)) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + resp := jsonValidationResponse{} + err := json.NewDecoder(rr.Body).Decode(&resp) + if err != nil { + t.Fatal(err) + } + + if resp.Valid != false { + t.Errorf("handler returned wrong response: got %v want %v", + resp.Valid, false) + } +} + +func TestValidateJsonWithInvalidSpaceApiVersion(t *testing.T) { + rr := forgeValidateJSONRequest(t, strings.NewReader(invalidSpaceApiVersion)) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + resp := jsonValidationResponse{} + err := json.NewDecoder(rr.Body).Decode(&resp) + if err != nil { + t.Fatal(err) + } + + if resp.Valid != false { + t.Errorf("handler returned wrong response: got %v want %v", + resp.Valid, false) + } +} + +func TestValidateJsonWithInvalidJson(t *testing.T) { + rr := forgeValidateJSONRequest(t, strings.NewReader("foo")) + + if status := rr.Code; status != http.StatusBadRequest { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusBadRequest) + } +} + +func TestValidateJsonWithEmptyBody(t *testing.T) { + rr := forgeValidateJSONRequest(t, nil) + + if status := rr.Code; status != http.StatusBadRequest { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusBadRequest) + } +} + +//// VALIDATE URL //// + +func TestValidateUrlWithValid(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(validSpace)) + })) + defer ts.Close() + + rr := forgeValidateURLRequest(t, strings.NewReader(`{ "url": "`+ts.URL+`" }`)) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + resp := urlValidationResponse{} + err := json.NewDecoder(rr.Body).Decode(&resp) + if err != nil { + t.Fatal(err) + } + + if resp.Valid != true { + t.Errorf("handler returned wrong response: got %v want %v", + resp.Valid, true) + } +} + +func TestValidateUrlWithUnreachablePath(t *testing.T) { + rr := forgeValidateURLRequest(t, strings.NewReader(`{ "url": "https://example.com/status.json" }`)) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + resp := urlValidationResponse{} + err := json.NewDecoder(rr.Body).Decode(&resp) + if err != nil { + t.Fatal(err) + } + + if resp.Valid != false { + t.Errorf("handler returned wrong response: got %v want %v", + resp.Valid, true) + } + + if resp.Reachable != false { + t.Errorf("handler returned wrong reachability: got %v want %v", + resp.Reachable, false) + } +} + +func TestValidateUrlWithUnreachableServer(t *testing.T) { + rr := forgeValidateURLRequest(t, strings.NewReader(`{ "url": "http://localhost:666/status.json" }`)) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + resp := urlValidationResponse{} + err := json.NewDecoder(rr.Body).Decode(&resp) + if err != nil { + t.Fatal(err) + } + + if resp.Valid != false { + t.Errorf("handler returned wrong response: got %v want %v", + resp.Valid, true) + } + + if resp.Reachable != false { + t.Errorf("handler returned wrong reachability: got %v want %v", + resp.Reachable, false) + } +} + +func TestValidateUrlWithEmptyBody(t *testing.T) { + rr := forgeValidateURLRequest(t, nil) + + if status := rr.Code; status != http.StatusBadRequest { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusBadRequest) + } +} + +func TestValidateUrlWithEmptyStringBody(t *testing.T) { + rr := forgeValidateURLRequest(t, strings.NewReader("")) + + if status := rr.Code; status != http.StatusBadRequest { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusBadRequest) + } +} + +func TestValidateUrlWithInvalidBody(t *testing.T) { + rr := forgeValidateURLRequest(t, strings.NewReader(`{}`)) + + if status := rr.Code; status != http.StatusBadRequest { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusBadRequest) + } +} + +func TestValidateUrlCors(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Access-Control-Allow-Origin", "*") + w.Header().Add("Content-Type", "application/json; charset=UTF-8") + _, _ = w.Write([]byte(validSpace)) + })) + defer ts.Close() + + rr := forgeValidateURLRequest(t, strings.NewReader(`{ "url": "`+ts.URL+`" }`)) + + resp := urlValidationResponse{} + err := json.NewDecoder(rr.Body).Decode(&resp) + if err != nil { + t.Fatal(err) + } + + if resp.Cors != true { + t.Errorf("cors check failed: got %v want %v", + resp.Cors, true) + } + + if resp.ContentType != true { + t.Errorf("content type check failed: got %v want %v", + resp.ContentType, true) + } +} + +func TestValidateUrlCorsFalse(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(validSpace)) + })) + defer ts.Close() + + rr := forgeValidateURLRequest(t, strings.NewReader(`{ "url": "`+ts.URL+`" }`)) + + resp := urlValidationResponse{} + err := json.NewDecoder(rr.Body).Decode(&resp) + if err != nil { + t.Fatal(err) + } + + if resp.Cors != false { + t.Errorf("cors check failed: got %v want %v", + resp.Cors, false) + } + if resp.ContentType != false { + t.Errorf("content type check failed: got %v want %v", + resp.ContentType, false) + } +} + +func TestValidateUrlInvalidSpace(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(invalidSpace)) + })) + defer ts.Close() + + rr := forgeValidateURLRequest(t, strings.NewReader(`{ "url": "`+ts.URL+`" }`)) + t.Logf("%v", rr.Body) + resp := urlValidationResponse{} + err := json.NewDecoder(rr.Body).Decode(&resp) + if err != nil { + t.Fatal(err) + } + + if resp.Valid != false { + t.Errorf("cors check failed: got %v want %v", + resp.Valid, false) + } + + if resp.Message == "" { + t.Errorf("message should not be empty") + } +} + +func TestValidateUrlInvalidTls(t *testing.T) { + ts := httptest.NewTLSServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(validSpace)) + })) + defer ts.Close() + + rr := forgeValidateURLRequest(t, strings.NewReader(`{ "url": "`+ts.URL+`" }`)) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + resp := urlValidationResponse{} + err := json.NewDecoder(rr.Body).Decode(&resp) + if err != nil { + t.Fatal(err) + } + + if resp.IsHTTPS != true { + t.Errorf("https check failed: got %v want %v", + resp.IsHTTPS, true) + } + + if resp.CertValid != false { + t.Errorf("cert check failed: got %v want %v", + resp.CertValid, false) + } +} + +func TestServerInfo(t *testing.T) { + req, err := http.NewRequest("POST", "/v3", nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(info) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + resp := serverInfo{} + err = json.NewDecoder(rr.Body).Decode(&resp) + if err != nil { + t.Fatal(err) + } +}