mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-24 03:29:50 +02:00
Serve OpenAPI 3.0 spec at /openapi.v1.json (#37038)
Add a build-time conversion step that transforms the existing Swagger 2.0 spec into an OpenAPI 3.0 spec. The OAS3 spec is served alongside the existing Swagger 2.0 spec, enabling API clients that require OAS3 to generate code directly from Gitea's API. This is not to be an answer to how gitea handles OAS3 long term, but a way to use what we have to move a step forward. --------- Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
39 files changed
+34700
-99
No files matched your search
+1
-1
@@ -18,7 +18,7 @@ indent_style = tab
|
||||
[templates/custom/*.tmpl]
|
||||
insert_final_newline = false
|
||||
|
||||
[templates/swagger/v1_json.tmpl]
|
||||
[templates/swagger/*_json.tmpl]
|
||||
indent_style = space
|
||||
insert_final_newline = false
|
||||
|
||||
|
||||
@@ -151,6 +151,7 @@ ESLINT_CONCURRENCY ?= 2
|
||||
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
|
||||
SWAGGER_SPEC_INPUT := templates/swagger/v1_input.json
|
||||
SWAGGER_EXCLUDE := code.gitea.io/sdk
|
||||
OPENAPI3_SPEC := templates/swagger/v1_openapi3_json.tmpl
|
||||
|
||||
TEST_MYSQL_HOST ?= mysql:3306
|
||||
TEST_MYSQL_DBNAME ?= testgitea
|
||||
@@ -233,7 +234,7 @@ TAGS_PREREQ := $(TAGS_EVIDENCE)
|
||||
endif
|
||||
|
||||
.PHONY: generate-swagger
|
||||
generate-swagger: $(SWAGGER_SPEC) ## generate the swagger spec from code comments
|
||||
generate-swagger: $(SWAGGER_SPEC) $(OPENAPI3_SPEC) ## generate the swagger spec from code comments
|
||||
|
||||
$(SWAGGER_SPEC): $(GO_SOURCES) $(SWAGGER_SPEC_INPUT)
|
||||
$(GO) run $(SWAGGER_PACKAGE) generate spec --exclude "$(SWAGGER_EXCLUDE)" --input "$(SWAGGER_SPEC_INPUT)" --output './$(SWAGGER_SPEC)'
|
||||
@@ -255,6 +256,21 @@ swagger-validate: ## check if the swagger spec is valid
|
||||
$(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)'
|
||||
@$(SED_INPLACE) -E -e 's|"basePath":( *)"/(.*)"|"basePath":\1"\2"|g' './$(SWAGGER_SPEC)' # remove the prefix slash from basePath
|
||||
|
||||
.PHONY: generate-openapi3
|
||||
generate-openapi3: $(OPENAPI3_SPEC) ## generate the OpenAPI 3.0 spec from the Swagger 2.0 spec
|
||||
|
||||
$(OPENAPI3_SPEC): $(SWAGGER_SPEC) build/generate-openapi.go $(wildcard build/openapi3gen/*.go)
|
||||
$(GO) run build/generate-openapi.go
|
||||
|
||||
.PHONY: openapi3-check
|
||||
openapi3-check: generate-openapi3
|
||||
@diff=$$(git diff --color=always '$(OPENAPI3_SPEC)'); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make generate-openapi3' and commit the result:"; \
|
||||
printf "%s" "$${diff}"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: checks
|
||||
checks: checks-frontend checks-backend ## run various consistency checks
|
||||
|
||||
@@ -262,7 +278,7 @@ checks: checks-frontend checks-backend ## run various consistency checks
|
||||
checks-frontend: lockfile-check svg-check ## check frontend files
|
||||
|
||||
.PHONY: checks-backend
|
||||
checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check ## check backend files
|
||||
checks-backend: tidy-check swagger-check openapi3-check fmt-check swagger-validate security-check ## check backend files
|
||||
|
||||
.PHONY: lint
|
||||
lint: lint-frontend lint-backend lint-spell ## lint everything
|
||||
|
||||
Generated
+40
@@ -489,6 +489,11 @@
|
||||
"path": "github.com/fxamacker/cbor/v2/LICENSE",
|
||||
"licenseText": "MIT License\n\nCopyright (c) 2019-present Faye Amacker\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
|
||||
},
|
||||
{
|
||||
"name": "github.com/getkin/kin-openapi",
|
||||
"path": "github.com/getkin/kin-openapi/LICENSE",
|
||||
"licenseText": "MIT License\n\nCopyright (c) 2017-2018 the project authors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
||||
},
|
||||
{
|
||||
"name": "github.com/git-lfs/pktline",
|
||||
"path": "github.com/git-lfs/pktline/LICENSE.md",
|
||||
@@ -554,6 +559,16 @@
|
||||
"path": "github.com/go-ldap/ldap/v3/LICENSE",
|
||||
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com)\nPortions copyright (c) 2015-2024 go-ldap Authors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
||||
},
|
||||
{
|
||||
"name": "github.com/go-openapi/jsonpointer",
|
||||
"path": "github.com/go-openapi/jsonpointer/LICENSE",
|
||||
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
|
||||
},
|
||||
{
|
||||
"name": "github.com/go-openapi/swag",
|
||||
"path": "github.com/go-openapi/swag/LICENSE",
|
||||
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
|
||||
},
|
||||
{
|
||||
"name": "github.com/go-redsync/redsync/v4",
|
||||
"path": "github.com/go-redsync/redsync/v4/LICENSE",
|
||||
@@ -929,6 +944,11 @@
|
||||
"path": "github.com/modern-go/reflect2/LICENSE",
|
||||
"licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under thiLine truncated
|
||||
},
|
||||
{
|
||||
"name": "github.com/mohae/deepcopy",
|
||||
"path": "github.com/mohae/deepcopy/LICENSE",
|
||||
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2014 Joel\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
||||
},
|
||||
{
|
||||
"name": "github.com/mschoch/smat",
|
||||
"path": "github.com/mschoch/smat/LICENSE",
|
||||
@@ -954,6 +974,16 @@
|
||||
"path": "github.com/nwaples/rardecode/v2/LICENSE",
|
||||
"licenseText": "Copyright (c) 2015, Nicholas Waples\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
||||
},
|
||||
{
|
||||
"name": "github.com/oasdiff/yaml",
|
||||
"path": "github.com/oasdiff/yaml/LICENSE",
|
||||
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2014 Sam Ghods\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n\nCopyright (c) 2012 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
|
||||
},
|
||||
{
|
||||
"name": "github.com/oasdiff/yaml3",
|
||||
"path": "github.com/oasdiff/yaml3/LICENSE",
|
||||
"licenseText": "\nThis project is covered by two different licenses: MIT and Apache.\n\n#### MIT License ####\n\nThe following files were ported to Go from C files of libyaml, and thus\nare still covered by their original MIT license, with the additional\ncopyright staring in 2011 when the project was ported over:\n\n apic.go emitterc.go parserc.go readerc.go scannerc.go\n writerc.go yamlh.go yamlprivateh.go\n\nCopyright (c) 2006-2010 Kirill Simonov\nCopyright (c) 2006-2011 Kirill Simonov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n### Apache License ###\n\nAll the remaining project files are covered by the Apache license:\n\nCopyright (c) 2011-2019 Canonical Ltd\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
|
||||
},
|
||||
{
|
||||
"name": "github.com/olekukonko/cat",
|
||||
"path": "github.com/olekukonko/cat/LICENSE",
|
||||
@@ -994,6 +1024,11 @@
|
||||
"path": "github.com/opencontainers/image-spec/LICENSE",
|
||||
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
|
||||
},
|
||||
{
|
||||
"name": "github.com/perimeterx/marshmallow",
|
||||
"path": "github.com/perimeterx/marshmallow/LICENSE",
|
||||
"licenseText": "MIT License\n\nCopyright (c) 2022 PerimeterX\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
|
||||
},
|
||||
{
|
||||
"name": "github.com/philhofer/fwd",
|
||||
"path": "github.com/philhofer/fwd/LICENSE.md",
|
||||
@@ -1169,6 +1204,11 @@
|
||||
"path": "github.com/wneessen/go-mail/smtp/LICENSE",
|
||||
"licenseText": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
},
|
||||
{
|
||||
"name": "github.com/woodsbury/decimal128",
|
||||
"path": "github.com/woodsbury/decimal128/LICENCE",
|
||||
"licenseText": "BSD Zero Clause License\n\nCopyright (c) 2022 Wade Smith\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n"
|
||||
},
|
||||
{
|
||||
"name": "github.com/x448/float16",
|
||||
"path": "github.com/x448/float16/LICENSE",
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// generate-openapi converts Gitea's Swagger 2.0 spec into an OpenAPI 3.0 spec.
|
||||
//
|
||||
// Gitea generates a Swagger 2.0 spec from code annotations (make generate-swagger).
|
||||
// This tool converts it to OAS3 so that SDK generators and tools that require
|
||||
// OAS3 (e.g. progenitor for Rust) can consume it directly. The conversion also
|
||||
// deduplicates inline enum definitions into named schema components, producing
|
||||
// cleaner SDK output with proper enum types instead of anonymous strings.
|
||||
//
|
||||
// Run: go run build/generate-openapi.go
|
||||
// Output: templates/swagger/v1_openapi3_json.tmpl
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/build/openapi3gen"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
const (
|
||||
swaggerSpecPath = "templates/swagger/v1_json.tmpl"
|
||||
openapi3OutPath = "templates/swagger/v1_openapi3_json.tmpl"
|
||||
|
||||
appSubUrlVar = "{{.SwaggerAppSubUrl}}"
|
||||
appVerVar = "{{.SwaggerAppVer}}"
|
||||
|
||||
appSubUrlPlaceholder = "GITEA_APP_SUB_URL_PLACEHOLDER"
|
||||
appVerPlaceholder = "0.0.0-gitea-placeholder"
|
||||
)
|
||||
|
||||
var (
|
||||
appSubUrlRe = regexp.MustCompile(regexp.QuoteMeta(appSubUrlVar))
|
||||
appVerRe = regexp.MustCompile(regexp.QuoteMeta(appVerVar))
|
||||
|
||||
enumScanDirs = []string{
|
||||
"modules/structs",
|
||||
"modules/commitstatus",
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
astEnumMap, err := openapi3gen.ScanSwaggerEnumTypes(enumScanDirs)
|
||||
if err != nil {
|
||||
log.Fatalf("scanning swagger:enum annotations: %v", err)
|
||||
}
|
||||
names := make([]string, 0, len(astEnumMap))
|
||||
for _, n := range astEnumMap {
|
||||
names = append(names, n)
|
||||
}
|
||||
sort.Strings(names)
|
||||
fmt.Fprintf(os.Stderr, "discovered %d swagger:enum types: %s\n", len(names), strings.Join(names, ", "))
|
||||
|
||||
data, err := os.ReadFile(swaggerSpecPath)
|
||||
if err != nil {
|
||||
log.Fatalf("reading swagger spec: %v", err)
|
||||
}
|
||||
|
||||
cleaned := appSubUrlRe.ReplaceAll(data, []byte(appSubUrlPlaceholder))
|
||||
cleaned = appVerRe.ReplaceAll(cleaned, []byte(appVerPlaceholder))
|
||||
|
||||
oas3, err := openapi3gen.Convert(cleaned, astEnumMap)
|
||||
if err != nil {
|
||||
log.Fatalf("converting to openapi 3.0: %v", err)
|
||||
}
|
||||
|
||||
oas3.Servers = openapi3.Servers{
|
||||
{URL: appSubUrlPlaceholder + "/api/v1"},
|
||||
}
|
||||
|
||||
out, err := json.MarshalIndent(oas3, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("marshaling openapi 3.0: %v", err)
|
||||
}
|
||||
|
||||
result := strings.ReplaceAll(string(out), appSubUrlPlaceholder, appSubUrlVar)
|
||||
result = strings.ReplaceAll(result, appVerPlaceholder, appVerVar)
|
||||
result = strings.TrimSpace(result)
|
||||
|
||||
if err := os.WriteFile(openapi3OutPath, []byte(result), 0o644); err != nil {
|
||||
log.Fatalf("writing openapi 3.0 spec: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Generated %s\n", openapi3OutPath)
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package openapi3gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi2"
|
||||
"github.com/getkin/kin-openapi/openapi2conv"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
// rxDeprecated matches "deprecated" as a word at the start of a description
|
||||
// or preceded by whitespace/punctuation that indicates a leading marker (e.g.
|
||||
// "Deprecated: true", "deprecated (use X instead)"). Rejects negated phrases
|
||||
// like "not deprecated" or "previously deprecated, now supported".
|
||||
var rxDeprecated = regexp.MustCompile(`(?i)(?:^|[\n.;])\s*deprecated\b`)
|
||||
|
||||
// Convert parses a Swagger 2.0 spec and returns an OAS3 spec, applying
|
||||
// Gitea-specific post-processing: file-schema fixups, URI formats,
|
||||
// deprecated flags, and shared-enum extraction.
|
||||
//
|
||||
// astEnumMap is a value-set-key → Go-type-name map (built by
|
||||
// ScanSwaggerEnumTypes). If a shared enum in the spec has no entry in the
|
||||
// map, Convert returns an error — no fallback naming.
|
||||
func Convert(swaggerJSON []byte, astEnumMap map[string]string) (*openapi3.T, error) {
|
||||
var swagger2 openapi2.T
|
||||
if err := json.Unmarshal(swaggerJSON, &swagger2); err != nil {
|
||||
return nil, fmt.Errorf("parsing swagger 2.0: %w", err)
|
||||
}
|
||||
|
||||
oas3, err := openapi2conv.ToV3(&swagger2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting to openapi 3.0: %w", err)
|
||||
}
|
||||
|
||||
fixFileSchemas(oas3)
|
||||
addURIFormats(oas3)
|
||||
addDeprecatedFlags(oas3)
|
||||
if err := extractSharedEnums(oas3, astEnumMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return oas3, nil
|
||||
}
|
||||
|
||||
func fixFileSchemas(doc *openapi3.T) {
|
||||
for _, pathItem := range doc.Paths.Map() {
|
||||
for _, op := range []*openapi3.Operation{
|
||||
pathItem.Get, pathItem.Post, pathItem.Put, pathItem.Patch,
|
||||
pathItem.Delete, pathItem.Head, pathItem.Options, pathItem.Trace,
|
||||
} {
|
||||
if op == nil {
|
||||
continue
|
||||
}
|
||||
for _, resp := range op.Responses.Map() {
|
||||
if resp.Value == nil {
|
||||
continue
|
||||
}
|
||||
for _, mediaType := range resp.Value.Content {
|
||||
fixSchema(mediaType.Schema)
|
||||
}
|
||||
}
|
||||
if op.RequestBody != nil && op.RequestBody.Value != nil {
|
||||
for _, mediaType := range op.RequestBody.Value.Content {
|
||||
fixSchema(mediaType.Schema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fixSchema rewrites any "type: file" schemas to the OAS3 equivalent
|
||||
// (type: string, format: binary), recursing into Properties, Items, and
|
||||
// AllOf/OneOf/AnyOf/Not branches. $ref nodes are skipped so shared schemas
|
||||
// are rewritten exactly once when visited through their declaration.
|
||||
func fixSchema(ref *openapi3.SchemaRef) {
|
||||
if ref == nil || ref.Value == nil || ref.Ref != "" {
|
||||
return
|
||||
}
|
||||
s := ref.Value
|
||||
if s.Type.Is("file") {
|
||||
s.Type = &openapi3.Types{"string"}
|
||||
s.Format = "binary"
|
||||
}
|
||||
for _, p := range s.Properties {
|
||||
fixSchema(p)
|
||||
}
|
||||
fixSchema(s.Items)
|
||||
for _, sub := range s.AllOf {
|
||||
fixSchema(sub)
|
||||
}
|
||||
for _, sub := range s.OneOf {
|
||||
fixSchema(sub)
|
||||
}
|
||||
for _, sub := range s.AnyOf {
|
||||
fixSchema(sub)
|
||||
}
|
||||
fixSchema(s.Not)
|
||||
}
|
||||
|
||||
// addURIFormats sets format: uri on string properties whose names indicate
|
||||
// they hold URLs. This information is lost in Swagger 2.0 but is valuable
|
||||
// for code generators.
|
||||
func addURIFormats(doc *openapi3.T) {
|
||||
if doc.Components == nil {
|
||||
return
|
||||
}
|
||||
for _, schemaRef := range doc.Components.Schemas {
|
||||
if schemaRef.Value == nil {
|
||||
continue
|
||||
}
|
||||
for propName, propRef := range schemaRef.Value.Properties {
|
||||
if propRef == nil || propRef.Value == nil || propRef.Ref != "" {
|
||||
continue
|
||||
}
|
||||
prop := propRef.Value
|
||||
if !prop.Type.Is("string") || prop.Format != "" {
|
||||
continue
|
||||
}
|
||||
if isURLProperty(propName) {
|
||||
prop.Format = "uri"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isURLProperty(name string) bool {
|
||||
if strings.HasSuffix(name, "_url") {
|
||||
return true
|
||||
}
|
||||
switch name {
|
||||
case "url", "html_url", "clone_url":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// addDeprecatedFlags sets deprecated: true on schema properties whose
|
||||
// description starts with a "deprecated" marker (e.g. "Deprecated: true"
|
||||
// or "deprecated (use X instead)"). Does not match negated phrases.
|
||||
func addDeprecatedFlags(doc *openapi3.T) {
|
||||
if doc.Components == nil {
|
||||
return
|
||||
}
|
||||
for _, schemaRef := range doc.Components.Schemas {
|
||||
if schemaRef.Value == nil {
|
||||
continue
|
||||
}
|
||||
for _, propRef := range schemaRef.Value.Properties {
|
||||
if propRef == nil || propRef.Value == nil || propRef.Ref != "" {
|
||||
continue
|
||||
}
|
||||
if rxDeprecated.MatchString(propRef.Value.Description) {
|
||||
propRef.Value.Deprecated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type enumUsage struct {
|
||||
schemaName string
|
||||
propName string
|
||||
propRef *openapi3.SchemaRef
|
||||
inItems bool
|
||||
}
|
||||
|
||||
// extractSharedEnums finds identical enum arrays used by multiple schema
|
||||
// properties, creates a standalone named schema for each, and replaces
|
||||
// the inline enums with $ref pointers.
|
||||
//
|
||||
// If the derived enum name collides with an existing component schema, or
|
||||
// no // swagger:enum annotation matches the value set, generation aborts
|
||||
// with an actionable error — there are no silent fallbacks.
|
||||
func extractSharedEnums(doc *openapi3.T, astEnumMap map[string]string) error {
|
||||
if doc.Components == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
enumGroups := map[string][]enumUsage{}
|
||||
|
||||
for schemaName, schemaRef := range doc.Components.Schemas {
|
||||
if schemaRef.Value == nil {
|
||||
continue
|
||||
}
|
||||
for propName, propRef := range schemaRef.Value.Properties {
|
||||
if propRef == nil || propRef.Value == nil || propRef.Ref != "" {
|
||||
continue
|
||||
}
|
||||
if len(propRef.Value.Enum) > 1 && propRef.Value.Type.Is("string") {
|
||||
key := EnumKey(propRef.Value.Enum)
|
||||
enumGroups[key] = append(enumGroups[key], enumUsage{schemaName, propName, propRef, false})
|
||||
}
|
||||
if propRef.Value.Type.Is("array") && propRef.Value.Items != nil &&
|
||||
propRef.Value.Items.Value != nil && propRef.Value.Items.Ref == "" &&
|
||||
len(propRef.Value.Items.Value.Enum) > 1 && propRef.Value.Items.Value.Type.Is("string") {
|
||||
key := EnumKey(propRef.Value.Items.Value.Enum)
|
||||
enumGroups[key] = append(enumGroups[key], enumUsage{schemaName, propName, propRef, true})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, usages := range enumGroups {
|
||||
if len(usages) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
enumName, err := deriveEnumName(key, usages, astEnumMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, exists := doc.Components.Schemas[enumName]; exists {
|
||||
return fmt.Errorf("enum name collision: %s already exists as a component schema", enumName)
|
||||
}
|
||||
|
||||
var enumValues []any
|
||||
if usages[0].inItems {
|
||||
enumValues = usages[0].propRef.Value.Items.Value.Enum
|
||||
} else {
|
||||
enumValues = usages[0].propRef.Value.Enum
|
||||
}
|
||||
|
||||
doc.Components.Schemas[enumName] = &openapi3.SchemaRef{
|
||||
Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"string"},
|
||||
Enum: enumValues,
|
||||
},
|
||||
}
|
||||
|
||||
ref := "#/components/schemas/" + enumName
|
||||
|
||||
for _, usage := range usages {
|
||||
if usage.inItems {
|
||||
usage.propRef.Value.Items = &openapi3.SchemaRef{Ref: ref}
|
||||
} else {
|
||||
old := usage.propRef.Value
|
||||
if old.Description == "" && !old.Deprecated && old.Format == "" {
|
||||
usage.propRef.Ref = ref
|
||||
usage.propRef.Value = nil
|
||||
} else {
|
||||
usage.propRef.Value = &openapi3.Schema{
|
||||
AllOf: openapi3.SchemaRefs{
|
||||
{Ref: ref},
|
||||
},
|
||||
Description: old.Description,
|
||||
Deprecated: old.Deprecated,
|
||||
Format: old.Format,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// deriveEnumName looks up a shared enum's Go type name from astEnumMap by
|
||||
// value-set key. If no annotation matches, returns an error identifying the
|
||||
// offending properties and the fix.
|
||||
func deriveEnumName(key string, usages []enumUsage, astEnumMap map[string]string) (string, error) {
|
||||
if name, ok := astEnumMap[key]; ok {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
props := map[string]bool{}
|
||||
for _, u := range usages {
|
||||
props[fmt.Sprintf("%s.%s", u.schemaName, u.propName)] = true
|
||||
}
|
||||
propList := make([]string, 0, len(props))
|
||||
for p := range props {
|
||||
propList = append(propList, p)
|
||||
}
|
||||
return "", fmt.Errorf(
|
||||
"no swagger:enum annotation matches value-set %q used by %d properties: %v; "+
|
||||
"fix by adding a named string type with // swagger:enum to modules/structs or modules/commitstatus",
|
||||
key, len(usages), propList,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package openapi3gen
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
func TestDeriveEnumName_hit(t *testing.T) {
|
||||
key := EnumKey([]any{"red", "green", "blue"})
|
||||
astMap := map[string]string{key: "Color"}
|
||||
usages := []enumUsage{{schemaName: "Paint", propName: "color"}}
|
||||
got, err := deriveEnumName(key, usages, astMap)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != "Color" {
|
||||
t.Fatalf("got %q, want %q", got, "Color")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveEnumName_miss(t *testing.T) {
|
||||
key := EnumKey([]any{"x", "y"})
|
||||
usages := []enumUsage{{schemaName: "Thing", propName: "kind"}}
|
||||
_, err := deriveEnumName(key, usages, map[string]string{})
|
||||
if err == nil {
|
||||
t.Fatal("expected miss error, got nil")
|
||||
}
|
||||
msg := err.Error()
|
||||
if !strings.Contains(msg, "Thing.kind") {
|
||||
t.Fatalf("error %q should list the missing usage", msg)
|
||||
}
|
||||
if !strings.Contains(msg, "swagger:enum") {
|
||||
t.Fatalf("error %q should hint at the fix", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractSharedEnums_usesASTMap(t *testing.T) {
|
||||
doc := &openapi3.T{
|
||||
Components: &openapi3.Components{
|
||||
Schemas: openapi3.Schemas{
|
||||
"A": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"object"},
|
||||
Properties: openapi3.Schemas{
|
||||
"color": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"string"},
|
||||
Enum: []any{"red", "green", "blue"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
"B": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"object"},
|
||||
Properties: openapi3.Schemas{
|
||||
"color": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"string"},
|
||||
Enum: []any{"red", "green", "blue"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
astMap := map[string]string{EnumKey([]any{"red", "green", "blue"}): "Color"}
|
||||
if err := extractSharedEnums(doc, astMap); err != nil {
|
||||
t.Fatalf("extractSharedEnums: %v", err)
|
||||
}
|
||||
if _, ok := doc.Components.Schemas["Color"]; !ok {
|
||||
t.Fatalf("expected Color schema to be extracted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFixFileSchemas_recursesIntoNested(t *testing.T) {
|
||||
fileType := func() *openapi3.SchemaRef {
|
||||
return &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"file"}}}
|
||||
}
|
||||
doc := &openapi3.T{
|
||||
Paths: openapi3.NewPaths(),
|
||||
}
|
||||
doc.Paths.Set("/upload", &openapi3.PathItem{
|
||||
Post: &openapi3.Operation{
|
||||
RequestBody: &openapi3.RequestBodyRef{
|
||||
Value: &openapi3.RequestBody{
|
||||
Content: openapi3.Content{
|
||||
"multipart/form-data": {
|
||||
Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"object"},
|
||||
Properties: openapi3.Schemas{
|
||||
"attachment": fileType(),
|
||||
"items": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"array"},
|
||||
Items: fileType(),
|
||||
}},
|
||||
"alt": {Value: &openapi3.Schema{
|
||||
AllOf: openapi3.SchemaRefs{fileType()},
|
||||
}},
|
||||
"one": {Value: &openapi3.Schema{
|
||||
OneOf: openapi3.SchemaRefs{fileType()},
|
||||
}},
|
||||
"any": {Value: &openapi3.Schema{
|
||||
AnyOf: openapi3.SchemaRefs{fileType()},
|
||||
}},
|
||||
"not": {Value: &openapi3.Schema{
|
||||
Not: fileType(),
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Responses: openapi3.NewResponses(),
|
||||
},
|
||||
})
|
||||
|
||||
fixFileSchemas(doc)
|
||||
|
||||
props := doc.Paths.Value("/upload").Post.RequestBody.Value.Content["multipart/form-data"].Schema.Value.Properties
|
||||
if !props["attachment"].Value.Type.Is("string") || props["attachment"].Value.Format != "binary" {
|
||||
t.Errorf("nested property not fixed: %+v", props["attachment"].Value)
|
||||
}
|
||||
if !props["items"].Value.Items.Value.Type.Is("string") || props["items"].Value.Items.Value.Format != "binary" {
|
||||
t.Errorf("array items not fixed: %+v", props["items"].Value.Items.Value)
|
||||
}
|
||||
if !props["alt"].Value.AllOf[0].Value.Type.Is("string") || props["alt"].Value.AllOf[0].Value.Format != "binary" {
|
||||
t.Errorf("allOf branch not fixed: %+v", props["alt"].Value.AllOf[0].Value)
|
||||
}
|
||||
if !props["one"].Value.OneOf[0].Value.Type.Is("string") || props["one"].Value.OneOf[0].Value.Format != "binary" {
|
||||
t.Errorf("oneOf branch not fixed: %+v", props["one"].Value.OneOf[0].Value)
|
||||
}
|
||||
if !props["any"].Value.AnyOf[0].Value.Type.Is("string") || props["any"].Value.AnyOf[0].Value.Format != "binary" {
|
||||
t.Errorf("anyOf branch not fixed: %+v", props["any"].Value.AnyOf[0].Value)
|
||||
}
|
||||
if !props["not"].Value.Not.Value.Type.Is("string") || props["not"].Value.Not.Value.Format != "binary" {
|
||||
t.Errorf("not branch not fixed: %+v", props["not"].Value.Not.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractSharedEnums_missReturnsError(t *testing.T) {
|
||||
doc := &openapi3.T{
|
||||
Components: &openapi3.Components{
|
||||
Schemas: openapi3.Schemas{
|
||||
"A": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"object"},
|
||||
Properties: openapi3.Schemas{
|
||||
"color": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"string"},
|
||||
Enum: []any{"red", "green"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
"B": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"object"},
|
||||
Properties: openapi3.Schemas{
|
||||
"color": {Value: &openapi3.Schema{
|
||||
Type: &openapi3.Types{"string"},
|
||||
Enum: []any{"red", "green"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := extractSharedEnums(doc, map[string]string{}); err == nil {
|
||||
t.Fatal("expected miss error")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package openapi3gen converts Gitea's Swagger 2.0 spec to an OpenAPI 3.0
|
||||
// spec. It discovers Go enum type names by scanning swagger:enum annotations
|
||||
// in the source tree, then names extracted shared-enum schemas accordingly.
|
||||
package openapi3gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EnumKey returns a canonical key for a set of enum values: values are
|
||||
// stringified, sorted, and joined with "|". Used to match enum value sets
|
||||
// across spec properties and scanned Go type declarations.
|
||||
func EnumKey(values []any) string {
|
||||
strs := make([]string, len(values))
|
||||
for i, v := range values {
|
||||
strs[i] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
sort.Strings(strs)
|
||||
return strings.Join(strs, "|")
|
||||
}
|
||||
|
||||
var rxSwaggerEnum = regexp.MustCompile(`swagger:enum\s+(\w+)`)
|
||||
|
||||
// ScanSwaggerEnumTypes walks .go files under each dir and returns a map from
|
||||
// a canonical value-set key (see EnumKey) to the Go type name declared with
|
||||
// // swagger:enum TypeName.
|
||||
//
|
||||
// Returns an error on parse failure, on an annotation for a type whose
|
||||
// constants can't be extracted, or on value-set collisions between two
|
||||
// different enum types.
|
||||
func ScanSwaggerEnumTypes(dirs []string) (map[string]string, error) {
|
||||
fset := token.NewFileSet()
|
||||
parsed := []*ast.File{}
|
||||
|
||||
for _, dir := range dirs {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading %s: %w", dir, err)
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(entry.Name(), "_test.go") {
|
||||
continue
|
||||
}
|
||||
path := filepath.Join(dir, entry.Name())
|
||||
file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", path, err)
|
||||
}
|
||||
parsed = append(parsed, file)
|
||||
}
|
||||
}
|
||||
|
||||
enumTypes := map[string]string{} // typeName → "" (presence marker)
|
||||
enumValues := map[string][]any{} // typeName → values
|
||||
|
||||
// Pass 1: collect every // swagger:enum TypeName declaration.
|
||||
for _, file := range parsed {
|
||||
for _, decl := range file.Decls {
|
||||
gd, ok := decl.(*ast.GenDecl)
|
||||
if !ok || gd.Tok != token.TYPE {
|
||||
continue
|
||||
}
|
||||
if err := collectEnumType(gd, enumTypes); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", fset.Position(gd.Pos()).Filename, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: collect const values; now every annotated type is visible.
|
||||
for _, file := range parsed {
|
||||
for _, decl := range file.Decls {
|
||||
gd, ok := decl.(*ast.GenDecl)
|
||||
if !ok || gd.Tok != token.CONST {
|
||||
continue
|
||||
}
|
||||
collectEnumValues(gd, enumTypes, enumValues)
|
||||
}
|
||||
}
|
||||
|
||||
result := map[string]string{}
|
||||
for typeName := range enumTypes {
|
||||
values, ok := enumValues[typeName]
|
||||
if !ok || len(values) == 0 {
|
||||
return nil, fmt.Errorf("swagger:enum %s has no const block with typed string values", typeName)
|
||||
}
|
||||
key := EnumKey(values)
|
||||
if existing, ok := result[key]; ok && existing != typeName {
|
||||
return nil, fmt.Errorf("swagger:enum value-set collision: %s and %s both use %q", existing, typeName, key)
|
||||
}
|
||||
result[key] = typeName
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// collectEnumType scans a `type` GenDecl for // swagger:enum annotations,
|
||||
// handling both the lone form (`// swagger:enum Foo\n type Foo string`)
|
||||
// where the comment group is attached to the GenDecl, and the grouped form:
|
||||
//
|
||||
// type (
|
||||
// // swagger:enum Foo
|
||||
// Foo string
|
||||
// )
|
||||
//
|
||||
// where the comment group is attached to each TypeSpec. Caveat: Go's parser
|
||||
// only attaches a CommentGroup when it is immediately adjacent to the decl.
|
||||
// A blank line (not a `//` continuation line) between the comment and the
|
||||
// declaration drops the Doc, so annotations MUST sit directly above their
|
||||
// type. All current annotated files obey this — the rule is noted here so
|
||||
// a future edit that inserts a blank line fails fast rather than silently.
|
||||
func collectEnumType(gd *ast.GenDecl, enumTypes map[string]string) error {
|
||||
if err := registerEnumAnnotation(gd.Doc, gd.Specs, enumTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, spec := range gd.Specs {
|
||||
ts, ok := spec.(*ast.TypeSpec)
|
||||
if !ok || ts.Doc == nil {
|
||||
continue
|
||||
}
|
||||
if err := registerEnumAnnotation(ts.Doc, []ast.Spec{ts}, enumTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerEnumAnnotation(doc *ast.CommentGroup, specs []ast.Spec, enumTypes map[string]string) error {
|
||||
if doc == nil {
|
||||
return nil
|
||||
}
|
||||
matches := rxSwaggerEnum.FindStringSubmatch(doc.Text())
|
||||
if len(matches) < 2 {
|
||||
return nil
|
||||
}
|
||||
annotated := matches[1]
|
||||
for _, spec := range specs {
|
||||
ts, ok := spec.(*ast.TypeSpec)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if ts.Name.Name == annotated {
|
||||
enumTypes[annotated] = ""
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("swagger:enum %s: no type declaration with that name in the same decl group; check for a typo", annotated)
|
||||
}
|
||||
|
||||
func collectEnumValues(gd *ast.GenDecl, enumTypes map[string]string, enumValues map[string][]any) {
|
||||
for _, spec := range gd.Specs {
|
||||
vs, ok := spec.(*ast.ValueSpec)
|
||||
if !ok || vs.Type == nil {
|
||||
continue
|
||||
}
|
||||
ident, ok := vs.Type.(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if _, isEnum := enumTypes[ident.Name]; !isEnum {
|
||||
continue
|
||||
}
|
||||
for _, val := range vs.Values {
|
||||
lit, ok := val.(*ast.BasicLit)
|
||||
if !ok || lit.Kind != token.STRING {
|
||||
continue
|
||||
}
|
||||
unquoted, err := strconv.Unquote(lit.Value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
enumValues[ident.Name] = append(enumValues[ident.Name], unquoted)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package openapi3gen
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEnumKey_sortsAndJoins(t *testing.T) {
|
||||
key := EnumKey([]any{"b", "a", "c"})
|
||||
if key != "a|b|c" {
|
||||
t.Fatalf("EnumKey = %q, want %q", key, "a|b|c")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnumKey_handlesNonStringValues(t *testing.T) {
|
||||
key := EnumKey([]any{2, 1, 3})
|
||||
if key != "1|2|3" {
|
||||
t.Fatalf("EnumKey = %q, want %q", key, "1|2|3")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_basic(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
src := `package fixture
|
||||
|
||||
// Color is a primary color.
|
||||
// swagger:enum Color
|
||||
type Color string
|
||||
|
||||
const (
|
||||
ColorRed Color = "red"
|
||||
ColorGreen Color = "green"
|
||||
ColorBlue Color = "blue"
|
||||
)
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "color.go"), []byte(src), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err != nil {
|
||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||
}
|
||||
wantKey := EnumKey([]any{"red", "green", "blue"})
|
||||
if got[wantKey] != "Color" {
|
||||
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Color")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_orphanAnnotation(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
src := `package fixture
|
||||
|
||||
// swagger:enum Sttype
|
||||
type StateType string
|
||||
|
||||
const (
|
||||
StateOpen StateType = "open"
|
||||
)
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "typo.go"), []byte(src), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for annotation referencing a non-matching type name")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Sttype") {
|
||||
t.Fatalf("error %q should mention the typo'd name Sttype", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_collision(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
src := `package fixture
|
||||
|
||||
// swagger:enum Alpha
|
||||
type Alpha string
|
||||
const (
|
||||
AlphaX Alpha = "x"
|
||||
AlphaY Alpha = "y"
|
||||
)
|
||||
|
||||
// swagger:enum Beta
|
||||
type Beta string
|
||||
const (
|
||||
BetaX Beta = "x"
|
||||
BetaY Beta = "y"
|
||||
)
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "dup.go"), []byte(src), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err == nil {
|
||||
t.Fatal("expected collision error, got nil")
|
||||
}
|
||||
msg := err.Error()
|
||||
if !strings.Contains(msg, "Alpha") || !strings.Contains(msg, "Beta") {
|
||||
t.Fatalf("error %q should mention both Alpha and Beta", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_parseFailure(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "bad.go"), []byte("package fixture\nfunc Foo() {"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err == nil {
|
||||
t.Fatal("expected parse error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_annotationWithoutConsts(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
src := `package fixture
|
||||
|
||||
// swagger:enum Lonely
|
||||
type Lonely string
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "lonely.go"), []byte(src), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for annotation without consts")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Lonely") {
|
||||
t.Fatalf("error %q should mention Lonely", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_constsAndTypeInDifferentFiles(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
// Name ordering: `a_consts.go` < `b_type.go`, so readdir returns consts first.
|
||||
// Old single-pass scanner would miss the values; two-pass must not.
|
||||
constsSrc := `package fixture
|
||||
|
||||
const (
|
||||
HueA Hue = "a"
|
||||
HueB Hue = "b"
|
||||
)
|
||||
`
|
||||
typeSrc := `package fixture
|
||||
|
||||
// swagger:enum Hue
|
||||
type Hue string
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "a_consts.go"), []byte(constsSrc), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "b_type.go"), []byte(typeSrc), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err != nil {
|
||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||
}
|
||||
wantKey := EnumKey([]any{"a", "b"})
|
||||
if got[wantKey] != "Hue" {
|
||||
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Hue")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_constsBeforeType(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
src := `package fixture
|
||||
|
||||
const (
|
||||
ShadeDark Shade = "dark"
|
||||
ShadeLight Shade = "light"
|
||||
)
|
||||
|
||||
// swagger:enum Shade
|
||||
type Shade string
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "shade.go"), []byte(src), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err != nil {
|
||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||
}
|
||||
wantKey := EnumKey([]any{"dark", "light"})
|
||||
if got[wantKey] != "Shade" {
|
||||
t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Shade")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanSwaggerEnumTypes_groupedTypeDecl(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
src := `package fixture
|
||||
|
||||
type (
|
||||
// swagger:enum Color
|
||||
Color string
|
||||
// swagger:enum Shade
|
||||
Shade string
|
||||
)
|
||||
|
||||
const (
|
||||
ColorRed Color = "red"
|
||||
ColorBlue Color = "blue"
|
||||
)
|
||||
|
||||
const (
|
||||
ShadeDark Shade = "dark"
|
||||
ShadeLight Shade = "light"
|
||||
)
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "grouped.go"), []byte(src), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := ScanSwaggerEnumTypes([]string{dir})
|
||||
if err != nil {
|
||||
t.Fatalf("ScanSwaggerEnumTypes: %v", err)
|
||||
}
|
||||
colorKey := EnumKey([]any{"red", "blue"})
|
||||
shadeKey := EnumKey([]any{"dark", "light"})
|
||||
if got[colorKey] != "Color" {
|
||||
t.Fatalf("Color: map[%q] = %q, want %q", colorKey, got[colorKey], "Color")
|
||||
}
|
||||
if got[shadeKey] != "Shade" {
|
||||
t.Fatalf("Shade: map[%q] = %q, want %q", shadeKey, got[shadeKey], "Shade")
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,7 @@ require (
|
||||
github.com/ethantkoenig/rupture v1.0.1
|
||||
github.com/felixge/fgprof v0.9.5
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/getkin/kin-openapi v0.134.0
|
||||
github.com/gliderlabs/ssh v0.3.8
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/go-chi/cors v1.2.2
|
||||
@@ -192,6 +193,8 @@ require (
|
||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/go-webauthn/x v0.2.3 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
@@ -232,15 +235,19 @@ require (
|
||||
github.com/minio/minlz v1.1.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/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.2.2 // indirect
|
||||
github.com/oasdiff/yaml v0.0.0-20260313112342-a3ea61cb4d4c // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.0-20260224194419-61cd415a242b // indirect
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||
github.com/olekukonko/errors v1.2.0 // indirect
|
||||
github.com/olekukonko/ll v0.1.8 // indirect
|
||||
github.com/olekukonko/tablewriter v1.1.4 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.26 // indirect
|
||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||
@@ -260,6 +267,7 @@ require (
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/tinylib/msgp v1.6.4 // indirect
|
||||
github.com/unknwon/com v1.0.1 // indirect
|
||||
github.com/woodsbury/decimal128 v1.3.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
|
||||
@@ -275,6 +275,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
|
||||
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/getkin/kin-openapi v0.134.0 h1:/L5+1+kfe6dXh8Ot/wqiTgUkjOIEJiC0bbYVziHB8rU=
|
||||
github.com/getkin/kin-openapi v0.134.0/go.mod h1:wK6ZLG/VgoETO9pcLJ/VmAtIcl/DNlMayNTb716EUxE=
|
||||
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0=
|
||||
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
@@ -310,6 +312,10 @@ github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZR
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ=
|
||||
github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI=
|
||||
@@ -547,6 +553,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
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/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM=
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
|
||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||
@@ -565,6 +573,10 @@ github.com/nwaples/rardecode/v2 v2.2.2/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsR
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oasdiff/yaml v0.0.0-20260313112342-a3ea61cb4d4c h1:7ACFcSaQsrWtrH4WHHfUqE1C+f8r2uv8KGaW0jTNjus=
|
||||
github.com/oasdiff/yaml v0.0.0-20260313112342-a3ea61cb4d4c/go.mod h1:JKox4Gszkxt57kj27u7rvi7IFoIULvCZHUsBTUmQM/s=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20260224194419-61cd415a242b h1:vivRhVUAa9t1q0Db4ZmezBP8pWQWnXHFokZj0AOea2g=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20260224194419-61cd415a242b/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
|
||||
github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo=
|
||||
@@ -591,6 +603,8 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
@@ -700,6 +714,8 @@ github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77ro
|
||||
github.com/tstranex/u2f v1.0.0 h1:HhJkSzDDlVSVIVt7pDJwCHQj67k7A5EeBgPmeD+pVsQ=
|
||||
github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
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.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
@@ -712,6 +728,8 @@ github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZ
|
||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
|
||||
github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
|
||||
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
|
||||
github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
|
||||
@@ -615,6 +615,7 @@ var (
|
||||
"sitemap.xml", // search engine sitemap
|
||||
"ssh_info", // agit info
|
||||
"swagger.v1.json",
|
||||
"openapi3.v1.json",
|
||||
|
||||
"ghost", // reserved name for deleted users (id: -1)
|
||||
"gitea-actions", // gitea builtin user (id: -2)
|
||||
|
||||
@@ -30,7 +30,7 @@ type CreateUserOption struct {
|
||||
// Whether the user has restricted access privileges
|
||||
Restricted *bool `json:"restricted"`
|
||||
// User visibility level: public, limited, or private
|
||||
Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
|
||||
Visibility UserVisibility `json:"visibility" binding:"In(,public,limited,private)"`
|
||||
|
||||
// For explicitly setting the user creation timestamp. Useful when users are
|
||||
// migrated from other systems. When omitted, the user's creation timestamp
|
||||
@@ -79,5 +79,5 @@ type EditUserOption struct {
|
||||
// Whether the user has restricted access privileges
|
||||
Restricted *bool `json:"restricted"`
|
||||
// User visibility level: public, limited, or private
|
||||
Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
|
||||
Visibility UserVisibility `json:"visibility" binding:"In(,public,limited,private)"`
|
||||
}
|
||||
@@ -22,7 +22,7 @@ type Organization struct {
|
||||
// The location of the organization
|
||||
Location string `json:"location"`
|
||||
// The visibility level of the organization (public, limited, private)
|
||||
Visibility string `json:"visibility"`
|
||||
Visibility UserVisibility `json:"visibility"`
|
||||
// Whether repository administrators can change team access
|
||||
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
|
||||
// username of the organization
|
||||
@@ -60,8 +60,7 @@ type CreateOrgOption struct {
|
||||
// The location of the organization
|
||||
Location string `json:"location" binding:"MaxSize(50)"`
|
||||
// possible values are `public` (default), `limited` or `private`
|
||||
// enum: ["public","limited","private"]
|
||||
Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
|
||||
Visibility UserVisibility `json:"visibility" binding:"In(,public,limited,private)"`
|
||||
// Whether repository administrators can change team access
|
||||
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
|
||||
}
|
||||
@@ -79,8 +78,7 @@ type EditOrgOption struct {
|
||||
// The location of the organization
|
||||
Location *string `json:"location" binding:"MaxSize(50)"`
|
||||
// possible values are `public`, `limited` or `private`
|
||||
// enum: ["public","limited","private"]
|
||||
Visibility *string `json:"visibility" binding:"In(,public,limited,private)"`
|
||||
Visibility *UserVisibility `json:"visibility" binding:"In(,public,limited,private)"`
|
||||
// Whether repository administrators can change team access
|
||||
RepoAdminChangeTeamAccess *bool `json:"repo_admin_change_team_access"`
|
||||
}
|
||||
|
||||
@@ -15,9 +15,8 @@ type Team struct {
|
||||
// The organization that the team belongs to
|
||||
Organization *Organization `json:"organization"`
|
||||
// Whether the team has access to all repositories in the organization
|
||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||
// enum: ["none","read","write","admin","owner"]
|
||||
Permission string `json:"permission"`
|
||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||
Permission AccessLevelName `json:"permission"`
|
||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
|
||||
// Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
|
||||
Units []string `json:"units"`
|
||||
@@ -34,9 +33,8 @@ type CreateTeamOption struct {
|
||||
// The description of the team
|
||||
Description string `json:"description" binding:"MaxSize(255)"`
|
||||
// Whether the team has access to all repositories in the organization
|
||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||
// enum: ["read","write","admin"]
|
||||
Permission string `json:"permission"`
|
||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||
Permission RepoWritePermission `json:"permission"`
|
||||
// example: ["repo.actions","repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.ext_wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
|
||||
// Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
|
||||
Units []string `json:"units"`
|
||||
@@ -53,9 +51,8 @@ type EditTeamOption struct {
|
||||
// The description of the team
|
||||
Description *string `json:"description" binding:"MaxSize(255)"`
|
||||
// Whether the team has access to all repositories in the organization
|
||||
IncludesAllRepositories *bool `json:"includes_all_repositories"`
|
||||
// enum: ["read","write","admin"]
|
||||
Permission string `json:"permission"`
|
||||
IncludesAllRepositories *bool `json:"includes_all_repositories"`
|
||||
Permission RepoWritePermission `json:"permission"`
|
||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
|
||||
// Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
|
||||
Units []string `json:"units"`
|
||||
|
||||
+11
-4
@@ -8,6 +8,15 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ObjectFormatName is the git hash algorithm used by a repository.
|
||||
// swagger:enum ObjectFormatName
|
||||
type ObjectFormatName string
|
||||
|
||||
const (
|
||||
ObjectFormatSHA1 ObjectFormatName = "sha1"
|
||||
ObjectFormatSHA256 ObjectFormatName = "sha256"
|
||||
)
|
||||
|
||||
// Permission represents a set of permissions
|
||||
type Permission struct {
|
||||
Admin bool `json:"admin"` // Admin indicates if the user is an administrator of the repository.
|
||||
@@ -114,8 +123,7 @@ type Repository struct {
|
||||
Internal bool `json:"internal"`
|
||||
MirrorInterval string `json:"mirror_interval"`
|
||||
// ObjectFormatName of the underlying git repository
|
||||
// enum: ["sha1","sha256"]
|
||||
ObjectFormatName string `json:"object_format_name"`
|
||||
ObjectFormatName ObjectFormatName `json:"object_format_name"`
|
||||
// swagger:strfmt date-time
|
||||
MirrorUpdated time.Time `json:"mirror_updated"`
|
||||
RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"`
|
||||
@@ -153,8 +161,7 @@ type CreateRepoOption struct {
|
||||
// enum: ["default","collaborator","committer","collaboratorcommitter"]
|
||||
TrustModel string `json:"trust_model"`
|
||||
// ObjectFormatName of the underlying git repository, empty string for default (sha1)
|
||||
// enum: ["sha1","sha256"]
|
||||
ObjectFormatName string `json:"object_format_name" binding:"MaxSize(6)"`
|
||||
ObjectFormatName ObjectFormatName `json:"object_format_name" binding:"MaxSize(6)"`
|
||||
}
|
||||
|
||||
// EditRepoOption options when editing a repository's properties
|
||||
|
||||
@@ -3,17 +3,41 @@
|
||||
|
||||
package structs
|
||||
|
||||
// RepoWritePermission is a permission level callers may grant to a team or
|
||||
// collaborator on input. Output fields use AccessLevelName instead.
|
||||
// swagger:enum RepoWritePermission
|
||||
type RepoWritePermission string
|
||||
|
||||
const (
|
||||
RepoWritePermissionRead RepoWritePermission = "read"
|
||||
RepoWritePermissionWrite RepoWritePermission = "write"
|
||||
RepoWritePermissionAdmin RepoWritePermission = "admin"
|
||||
)
|
||||
|
||||
// AccessLevelName is the string rendering of a perm.AccessMode produced on
|
||||
// API responses. Callers must not send these values; use RepoWritePermission
|
||||
// on input.
|
||||
// swagger:enum AccessLevelName
|
||||
type AccessLevelName string
|
||||
|
||||
const (
|
||||
AccessLevelNameNone AccessLevelName = "none"
|
||||
AccessLevelNameRead AccessLevelName = "read"
|
||||
AccessLevelNameWrite AccessLevelName = "write"
|
||||
AccessLevelNameAdmin AccessLevelName = "admin"
|
||||
AccessLevelNameOwner AccessLevelName = "owner"
|
||||
)
|
||||
|
||||
// AddCollaboratorOption options when adding a user as a collaborator of a repository
|
||||
type AddCollaboratorOption struct {
|
||||
// enum: ["read","write","admin"]
|
||||
// Permission level to grant the collaborator
|
||||
Permission *string `json:"permission"`
|
||||
Permission *RepoWritePermission `json:"permission"`
|
||||
}
|
||||
|
||||
// RepoCollaboratorPermission to get repository permission for a collaborator
|
||||
type RepoCollaboratorPermission struct {
|
||||
// Permission level of the collaborator
|
||||
Permission string `json:"permission"`
|
||||
Permission AccessLevelName `json:"permission"`
|
||||
// RoleName is the name of the permission role
|
||||
RoleName string `json:"role_name"`
|
||||
// User information of the collaborator
|
||||
|
||||
@@ -51,7 +51,7 @@ type User struct {
|
||||
// the user's description
|
||||
Description string `json:"description"`
|
||||
// User visibility level option: public, limited, private
|
||||
Visibility string `json:"visibility"`
|
||||
Visibility UserVisibility `json:"visibility"`
|
||||
|
||||
// user counts
|
||||
Followers int `json:"followers_count"`
|
||||
|
||||
@@ -56,3 +56,14 @@ func ExtractKeysFromMapString(in map[string]VisibleType) (keys []string) {
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// UserVisibility defines the visibility level of a user or organization as
|
||||
// rendered in API payloads. The DB representation is VisibleType (int).
|
||||
// swagger:enum UserVisibility
|
||||
type UserVisibility string
|
||||
|
||||
const (
|
||||
UserVisibilityPublic UserVisibility = "public"
|
||||
UserVisibilityLimited UserVisibility = "limited"
|
||||
UserVisibilityPrivate UserVisibility = "private"
|
||||
)
|
||||
@@ -48,7 +48,7 @@ func CreateOrg(ctx *context.APIContext) {
|
||||
|
||||
visibility := api.VisibleTypePublic
|
||||
if form.Visibility != "" {
|
||||
visibility = api.VisibilityModes[form.Visibility]
|
||||
visibility = api.VisibilityModes[string(form.Visibility)]
|
||||
}
|
||||
|
||||
org := &organization.Organization{
|
||||
|
||||
@@ -123,7 +123,7 @@ func CreateUser(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if form.Visibility != "" {
|
||||
visibility := api.VisibilityModes[form.Visibility]
|
||||
visibility := api.VisibilityModes[string(form.Visibility)]
|
||||
overwriteDefault.Visibility = &visibility
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ func EditUser(ctx *context.APIContext) {
|
||||
Description: optional.FromPtr(form.Description),
|
||||
IsActive: optional.FromPtr(form.Active),
|
||||
IsAdmin: user_service.UpdateOptionFieldFromPtr(form.Admin),
|
||||
Visibility: optional.FromMapLookup(api.VisibilityModes, form.Visibility),
|
||||
Visibility: optional.FromMapLookup(api.VisibilityModes, string(form.Visibility)),
|
||||
AllowGitHook: optional.FromPtr(form.AllowGitHook),
|
||||
AllowImportLocal: optional.FromPtr(form.AllowImportLocal),
|
||||
MaxRepoCreation: optional.FromPtr(form.MaxRepoCreation),
|
||||
|
||||
@@ -258,7 +258,7 @@ func Create(ctx *context.APIContext) {
|
||||
|
||||
visibility := api.VisibleTypePublic
|
||||
if form.Visibility != "" {
|
||||
visibility = api.VisibilityModes[form.Visibility]
|
||||
visibility = api.VisibilityModes[string(form.Visibility)]
|
||||
}
|
||||
|
||||
org := &organization.Organization{
|
||||
@@ -402,7 +402,7 @@ func Edit(ctx *context.APIContext) {
|
||||
Description: optional.FromPtr(form.Description),
|
||||
Website: optional.FromPtr(form.Website),
|
||||
Location: optional.FromPtr(form.Location),
|
||||
Visibility: optional.FromMapLookup(api.VisibilityModes, optional.FromPtr(form.Visibility).Value()),
|
||||
Visibility: optional.FromMapLookup(api.VisibilityModes, string(optional.FromPtr(form.Visibility).Value())),
|
||||
RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil {
|
||||
|
||||
@@ -210,7 +210,7 @@ func CreateTeam(ctx *context.APIContext) {
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
form := web.GetForm(ctx).(*api.CreateTeamOption)
|
||||
teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin)
|
||||
teamPermission := perm.ParseAccessMode(string(form.Permission), perm.AccessModeNone, perm.AccessModeAdmin)
|
||||
team := &organization.Team{
|
||||
OrgID: ctx.Org.Organization.ID,
|
||||
Name: form.Name,
|
||||
@@ -224,7 +224,7 @@ func CreateTeam(ctx *context.APIContext) {
|
||||
if len(form.UnitsMap) > 0 {
|
||||
attachTeamUnitsMap(team, form.UnitsMap)
|
||||
} else if len(form.Units) > 0 {
|
||||
unitPerm := perm.ParseAccessMode(form.Permission, perm.AccessModeRead, perm.AccessModeWrite)
|
||||
unitPerm := perm.ParseAccessMode(string(form.Permission), perm.AccessModeRead, perm.AccessModeWrite)
|
||||
attachTeamUnits(team, unitPerm, form.Units)
|
||||
} else {
|
||||
ctx.APIErrorInternal(errors.New("units permission should not be empty"))
|
||||
@@ -298,7 +298,7 @@ func EditTeam(ctx *context.APIContext) {
|
||||
isAuthChanged := false
|
||||
isIncludeAllChanged := false
|
||||
if !team.IsOwnerTeam() && len(form.Permission) != 0 {
|
||||
teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin)
|
||||
teamPermission := perm.ParseAccessMode(string(form.Permission), perm.AccessModeNone, perm.AccessModeAdmin)
|
||||
if team.AccessMode != teamPermission {
|
||||
isAuthChanged = true
|
||||
team.AccessMode = teamPermission
|
||||
@@ -314,7 +314,7 @@ func EditTeam(ctx *context.APIContext) {
|
||||
if len(form.UnitsMap) > 0 {
|
||||
attachTeamUnitsMap(team, form.UnitsMap)
|
||||
} else if len(form.Units) > 0 {
|
||||
unitPerm := perm.ParseAccessMode(form.Permission, perm.AccessModeRead, perm.AccessModeWrite)
|
||||
unitPerm := perm.ParseAccessMode(string(form.Permission), perm.AccessModeRead, perm.AccessModeWrite)
|
||||
attachTeamUnits(team, unitPerm, form.Units)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -181,7 +181,7 @@ func AddOrUpdateCollaborator(ctx *context.APIContext) {
|
||||
|
||||
p := perm.AccessModeWrite
|
||||
if form.Permission != nil {
|
||||
p = perm.ParseAccessMode(*form.Permission, perm.AccessModeRead, perm.AccessModeWrite, perm.AccessModeAdmin)
|
||||
p = perm.ParseAccessMode(string(*form.Permission), perm.AccessModeRead, perm.AccessModeWrite, perm.AccessModeAdmin)
|
||||
}
|
||||
|
||||
if err := repo_service.AddOrUpdateCollaborator(ctx, ctx.Repo.Repository, collaborator, p); err != nil {
|
||||
|
||||
@@ -262,7 +262,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
|
||||
DefaultBranch: opt.DefaultBranch,
|
||||
TrustModel: repo_model.ToTrustModel(opt.TrustModel),
|
||||
IsTemplate: opt.Template,
|
||||
ObjectFormatName: opt.ObjectFormatName,
|
||||
ObjectFormatName: string(opt.ObjectFormatName),
|
||||
})
|
||||
if err != nil {
|
||||
if repo_model.IsErrRepoAlreadyExist(err) {
|
||||
|
||||
@@ -16,3 +16,10 @@ func SwaggerV1Json(ctx *context.Context) {
|
||||
ctx.Data["SwaggerAppSubUrl"] = setting.AppSubURL // it is JS-safe
|
||||
ctx.JSONTemplate("swagger/v1_json")
|
||||
}
|
||||
|
||||
// OpenAPI3Json render OpenAPI 3.0 json (auto-converted from Swagger 2.0)
|
||||
func OpenAPI3Json(ctx *context.Context) {
|
||||
ctx.Data["SwaggerAppVer"] = template.HTML(template.JSEscapeString(setting.AppVer))
|
||||
ctx.Data["SwaggerAppSubUrl"] = setting.AppSubURL // it is JS-safe
|
||||
ctx.JSONTemplate("swagger/v1_openapi3_json")
|
||||
}
|
||||
@@ -1751,6 +1751,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
|
||||
if setting.API.EnableSwagger {
|
||||
m.Get("/swagger.v1.json", SwaggerV1Json)
|
||||
m.Get("/openapi3.v1.json", OpenAPI3Json)
|
||||
}
|
||||
|
||||
if !setting.IsProd || setting.IsInE2eTesting() {
|
||||
|
||||
@@ -711,7 +711,7 @@ func ToOrganization(ctx context.Context, org *organization.Organization) *api.Or
|
||||
Description: org.Description,
|
||||
Website: org.Website,
|
||||
Location: org.Location,
|
||||
Visibility: org.Visibility.String(),
|
||||
Visibility: api.UserVisibility(org.Visibility.String()),
|
||||
RepoAdminChangeTeamAccess: org.RepoAdminChangeTeamAccess,
|
||||
}
|
||||
}
|
||||
@@ -740,7 +740,7 @@ func ToTeams(ctx context.Context, teams []*organization.Team, loadOrgs bool) ([]
|
||||
Description: t.Description,
|
||||
IncludesAllRepositories: t.IncludesAllRepositories,
|
||||
CanCreateOrgRepo: t.CanCreateOrgRepo,
|
||||
Permission: t.AccessMode.ToString(),
|
||||
Permission: api.AccessLevelName(t.AccessMode.ToString()),
|
||||
Units: t.GetUnitNames(),
|
||||
UnitsMap: t.GetUnitsMap(),
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
|
||||
MirrorUpdated: mirrorUpdated,
|
||||
RepoTransfer: transfer,
|
||||
Topics: util.SliceNilAsEmpty(repo.Topics),
|
||||
ObjectFormatName: repo.ObjectFormatName,
|
||||
ObjectFormatName: api.ObjectFormatName(repo.ObjectFormatName),
|
||||
Licenses: util.SliceNilAsEmpty(repoLicenses.StringList()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func toUser(ctx context.Context, user *user_model.User, signed, authed bool) *ap
|
||||
StarredRepos: user.NumStars,
|
||||
}
|
||||
|
||||
result.Visibility = user.Visibility.String()
|
||||
result.Visibility = api.UserVisibility(user.Visibility.String())
|
||||
|
||||
// hide primary email if API caller is anonymous or user keep email private
|
||||
if signed && (!user.KeepEmailPrivate || authed) {
|
||||
@@ -104,7 +104,7 @@ func User2UserSettings(user *user_model.User) api.UserSettings {
|
||||
func ToUserAndPermission(ctx context.Context, user, doer *user_model.User, accessMode perm.AccessMode) api.RepoCollaboratorPermission {
|
||||
return api.RepoCollaboratorPermission{
|
||||
User: ToUser(ctx, user, doer),
|
||||
Permission: accessMode.ToString(),
|
||||
Permission: api.AccessLevelName(accessMode.ToString()),
|
||||
RoleName: accessMode.ToString(),
|
||||
}
|
||||
}
|
||||
@@ -29,11 +29,11 @@ func TestUser_ToUser(t *testing.T) {
|
||||
|
||||
apiUser = toUser(t.Context(), user1, false, false)
|
||||
assert.False(t, apiUser.IsAdmin)
|
||||
assert.Equal(t, api.VisibleTypePublic.String(), apiUser.Visibility)
|
||||
assert.Equal(t, api.UserVisibilityPublic, apiUser.Visibility)
|
||||
|
||||
user31 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31, IsAdmin: false, Visibility: api.VisibleTypePrivate})
|
||||
|
||||
apiUser = toUser(t.Context(), user31, true, true)
|
||||
assert.False(t, apiUser.IsAdmin)
|
||||
assert.Equal(t, api.VisibleTypePrivate.String(), apiUser.Visibility)
|
||||
assert.Equal(t, api.UserVisibilityPrivate, apiUser.Visibility)
|
||||
}
|
||||
Generated
+50
-9
@@ -22299,12 +22299,14 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"permission": {
|
||||
"description": "Permission level to grant the collaborator\nread RepoWritePermissionRead\nwrite RepoWritePermissionWrite\nadmin RepoWritePermissionAdmin",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"admin"
|
||||
],
|
||||
"x-go-enum-desc": "read RepoWritePermissionRead\nwrite RepoWritePermissionWrite\nadmin RepoWritePermissionAdmin",
|
||||
"x-go-name": "Permission"
|
||||
}
|
||||
},
|
||||
@@ -24031,13 +24033,14 @@
|
||||
"x-go-name": "UserName"
|
||||
},
|
||||
"visibility": {
|
||||
"description": "possible values are `public` (default), `limited` or `private`",
|
||||
"description": "possible values are `public` (default), `limited` or `private`\npublic UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"public",
|
||||
"limited",
|
||||
"private"
|
||||
],
|
||||
"x-go-enum-desc": "public UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
|
||||
"x-go-name": "Visibility"
|
||||
},
|
||||
"website": {
|
||||
@@ -24322,12 +24325,13 @@
|
||||
"x-go-name": "Name"
|
||||
},
|
||||
"object_format_name": {
|
||||
"description": "ObjectFormatName of the underlying git repository, empty string for default (sha1)",
|
||||
"description": "ObjectFormatName of the underlying git repository, empty string for default (sha1)\nsha1 ObjectFormatSHA1\nsha256 ObjectFormatSHA256",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"sha1",
|
||||
"sha256"
|
||||
],
|
||||
"x-go-enum-desc": "sha1 ObjectFormatSHA1\nsha256 ObjectFormatSHA256",
|
||||
"x-go-name": "ObjectFormatName"
|
||||
},
|
||||
"private": {
|
||||
@@ -24480,6 +24484,7 @@
|
||||
"write",
|
||||
"admin"
|
||||
],
|
||||
"x-go-enum-desc": "read RepoWritePermissionRead\nwrite RepoWritePermissionWrite\nadmin RepoWritePermissionAdmin",
|
||||
"x-go-name": "Permission"
|
||||
},
|
||||
"units": {
|
||||
@@ -24574,8 +24579,14 @@
|
||||
"x-go-name": "Username"
|
||||
},
|
||||
"visibility": {
|
||||
"description": "User visibility level: public, limited, or private",
|
||||
"description": "User visibility level: public, limited, or private\npublic UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"public",
|
||||
"limited",
|
||||
"private"
|
||||
],
|
||||
"x-go-enum-desc": "public UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
|
||||
"x-go-name": "Visibility"
|
||||
}
|
||||
},
|
||||
@@ -25200,13 +25211,14 @@
|
||||
"x-go-name": "RepoAdminChangeTeamAccess"
|
||||
},
|
||||
"visibility": {
|
||||
"description": "possible values are `public`, `limited` or `private`",
|
||||
"description": "possible values are `public`, `limited` or `private`\npublic UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"public",
|
||||
"limited",
|
||||
"private"
|
||||
],
|
||||
"x-go-enum-desc": "public UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
|
||||
"x-go-name": "Visibility"
|
||||
},
|
||||
"website": {
|
||||
@@ -25570,6 +25582,7 @@
|
||||
"write",
|
||||
"admin"
|
||||
],
|
||||
"x-go-enum-desc": "read RepoWritePermissionRead\nwrite RepoWritePermissionWrite\nadmin RepoWritePermissionAdmin",
|
||||
"x-go-name": "Permission"
|
||||
},
|
||||
"units": {
|
||||
@@ -25700,8 +25713,14 @@
|
||||
"x-go-name": "SourceID"
|
||||
},
|
||||
"visibility": {
|
||||
"description": "User visibility level: public, limited, or private",
|
||||
"description": "User visibility level: public, limited, or private\npublic UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"public",
|
||||
"limited",
|
||||
"private"
|
||||
],
|
||||
"x-go-enum-desc": "public UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
|
||||
"x-go-name": "Visibility"
|
||||
},
|
||||
"website": {
|
||||
@@ -27652,8 +27671,14 @@
|
||||
"x-go-name": "UserName"
|
||||
},
|
||||
"visibility": {
|
||||
"description": "The visibility level of the organization (public, limited, private)",
|
||||
"description": "The visibility level of the organization (public, limited, private)\npublic UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"public",
|
||||
"limited",
|
||||
"private"
|
||||
],
|
||||
"x-go-enum-desc": "public UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
|
||||
"x-go-name": "Visibility"
|
||||
},
|
||||
"website": {
|
||||
@@ -28637,8 +28662,16 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"permission": {
|
||||
"description": "Permission level of the collaborator",
|
||||
"description": "Permission level of the collaborator\nnone AccessLevelNameNone\nread AccessLevelNameRead\nwrite AccessLevelNameWrite\nadmin AccessLevelNameAdmin\nowner AccessLevelNameOwner",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"read",
|
||||
"write",
|
||||
"admin",
|
||||
"owner"
|
||||
],
|
||||
"x-go-enum-desc": "none AccessLevelNameNone\nread AccessLevelNameRead\nwrite AccessLevelNameWrite\nadmin AccessLevelNameAdmin\nowner AccessLevelNameOwner",
|
||||
"x-go-name": "Permission"
|
||||
},
|
||||
"role_name": {
|
||||
@@ -28915,12 +28948,13 @@
|
||||
"x-go-name": "Name"
|
||||
},
|
||||
"object_format_name": {
|
||||
"description": "ObjectFormatName of the underlying git repository",
|
||||
"description": "ObjectFormatName of the underlying git repository\nsha1 ObjectFormatSHA1\nsha256 ObjectFormatSHA256",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"sha1",
|
||||
"sha256"
|
||||
],
|
||||
"x-go-enum-desc": "sha1 ObjectFormatSHA1\nsha256 ObjectFormatSHA256",
|
||||
"x-go-name": "ObjectFormatName"
|
||||
},
|
||||
"open_issues_count": {
|
||||
@@ -29294,6 +29328,7 @@
|
||||
"admin",
|
||||
"owner"
|
||||
],
|
||||
"x-go-enum-desc": "none AccessLevelNameNone\nread AccessLevelNameRead\nwrite AccessLevelNameWrite\nadmin AccessLevelNameAdmin\nowner AccessLevelNameOwner",
|
||||
"x-go-name": "Permission"
|
||||
},
|
||||
"units": {
|
||||
@@ -29840,8 +29875,14 @@
|
||||
"x-go-name": "StarredRepos"
|
||||
},
|
||||
"visibility": {
|
||||
"description": "User visibility level option: public, limited, private",
|
||||
"description": "User visibility level option: public, limited, private\npublic UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"public",
|
||||
"limited",
|
||||
"private"
|
||||
],
|
||||
"x-go-enum-desc": "public UserVisibilityPublic\nlimited UserVisibilityLimited\nprivate UserVisibilityPrivate",
|
||||
"x-go-name": "Visibility"
|
||||
},
|
||||
"website": {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -96,12 +96,12 @@ func doAPIEditRepository(ctx APITestContext, editRepoOption *api.EditRepoOption,
|
||||
|
||||
func doAPIAddCollaborator(ctx APITestContext, username string, mode perm.AccessMode) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
permission := "read"
|
||||
permission := api.RepoWritePermissionRead
|
||||
|
||||
if mode == perm.AccessModeAdmin {
|
||||
permission = "admin"
|
||||
permission = api.RepoWritePermissionAdmin
|
||||
} else if mode > perm.AccessModeRead {
|
||||
permission = "write"
|
||||
permission = api.RepoWritePermissionWrite
|
||||
}
|
||||
addCollaboratorOption := &api.AddCollaboratorOption{
|
||||
Permission: &permission,
|
||||
|
||||
@@ -127,7 +127,7 @@ func testAPIOrgGeneral(t *testing.T) {
|
||||
apiOrgList := DecodeJSON(t, resp, []*api.Organization{})
|
||||
assert.Len(t, apiOrgList, 13)
|
||||
assert.Equal(t, "Limited Org 36", apiOrgList[1].FullName)
|
||||
assert.Equal(t, "limited", apiOrgList[1].Visibility)
|
||||
assert.Equal(t, api.UserVisibilityLimited, apiOrgList[1].Visibility)
|
||||
|
||||
// accessing without a token will return only public orgs
|
||||
req = NewRequest(t, "GET", "/api/v1/orgs")
|
||||
@@ -136,7 +136,7 @@ func testAPIOrgGeneral(t *testing.T) {
|
||||
apiOrgList = DecodeJSON(t, resp, []*api.Organization{})
|
||||
assert.Len(t, apiOrgList, 9)
|
||||
assert.Equal(t, "org 17", apiOrgList[0].FullName)
|
||||
assert.Equal(t, "public", apiOrgList[0].Visibility)
|
||||
assert.Equal(t, api.UserVisibilityPublic, apiOrgList[0].Visibility)
|
||||
})
|
||||
|
||||
t.Run("OrgEdit", func(t *testing.T) {
|
||||
@@ -148,7 +148,7 @@ func testAPIOrgGeneral(t *testing.T) {
|
||||
Description: new("new description"),
|
||||
Website: new("https://org3-new-website.example.com"),
|
||||
Location: new("new location"),
|
||||
Visibility: new("limited"),
|
||||
Visibility: new(api.UserVisibilityLimited),
|
||||
Email: new("org3-new-email@example.com"),
|
||||
}
|
||||
req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org3Edit).AddTokenAuth(user1Token)
|
||||
@@ -178,7 +178,7 @@ func testAPIOrgGeneral(t *testing.T) {
|
||||
|
||||
t.Run("OrgEditInvalidVisibility", func(t *testing.T) {
|
||||
org := api.EditOrgOption{
|
||||
Visibility: new("invalid-visibility"),
|
||||
Visibility: new(api.UserVisibility("invalid-visibility")),
|
||||
}
|
||||
req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).AddTokenAuth(user1Token)
|
||||
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) {
|
||||
|
||||
repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{})
|
||||
|
||||
assert.Equal(t, "owner", repoPermission.Permission)
|
||||
assert.Equal(t, api.AccessLevelNameOwner, repoPermission.Permission)
|
||||
})
|
||||
|
||||
t.Run("CollaboratorWithReadAccess", func(t *testing.T) {
|
||||
@@ -50,7 +50,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) {
|
||||
|
||||
repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{})
|
||||
|
||||
assert.Equal(t, "read", repoPermission.Permission)
|
||||
assert.Equal(t, api.AccessLevelNameRead, repoPermission.Permission)
|
||||
})
|
||||
|
||||
t.Run("CollaboratorWithWriteAccess", func(t *testing.T) {
|
||||
@@ -62,7 +62,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) {
|
||||
|
||||
repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{})
|
||||
|
||||
assert.Equal(t, "write", repoPermission.Permission)
|
||||
assert.Equal(t, api.AccessLevelNameWrite, repoPermission.Permission)
|
||||
})
|
||||
|
||||
t.Run("CollaboratorWithAdminAccess", func(t *testing.T) {
|
||||
@@ -74,7 +74,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) {
|
||||
|
||||
repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{})
|
||||
|
||||
assert.Equal(t, "admin", repoPermission.Permission)
|
||||
assert.Equal(t, api.AccessLevelNameAdmin, repoPermission.Permission)
|
||||
})
|
||||
|
||||
t.Run("CollaboratorNotFound", func(t *testing.T) {
|
||||
@@ -101,7 +101,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) {
|
||||
|
||||
repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{})
|
||||
|
||||
assert.Equal(t, "read", repoPermission.Permission)
|
||||
assert.Equal(t, api.AccessLevelNameRead, repoPermission.Permission)
|
||||
|
||||
t.Run("CollaboratorCanReadOwnPermission", func(t *testing.T) {
|
||||
session := loginUser(t, user5.Name)
|
||||
@@ -112,7 +112,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) {
|
||||
|
||||
repoCollPerm := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{})
|
||||
|
||||
assert.Equal(t, "read", repoCollPerm.Permission)
|
||||
assert.Equal(t, api.AccessLevelNameRead, repoCollPerm.Permission)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -128,7 +128,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) {
|
||||
|
||||
repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{})
|
||||
|
||||
assert.Equal(t, "read", repoPermission.Permission)
|
||||
assert.Equal(t, api.AccessLevelNameRead, repoPermission.Permission)
|
||||
})
|
||||
|
||||
t.Run("RepoAdminCanQueryACollaboratorsPermissions", func(t *testing.T) {
|
||||
@@ -144,6 +144,6 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) {
|
||||
|
||||
repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{})
|
||||
|
||||
assert.Equal(t, "read", repoPermission.Permission)
|
||||
assert.Equal(t, api.AccessLevelNameRead, repoPermission.Permission)
|
||||
})
|
||||
}
|
||||
@@ -39,12 +39,12 @@ func TestAPIRepoTeams(t *testing.T) {
|
||||
assert.Equal(t, "Owners", teams[0].Name)
|
||||
assert.True(t, teams[0].CanCreateOrgRepo)
|
||||
assert.True(t, util.SliceSortedEqual(unit.AllUnitKeyNames(), teams[0].Units), "%v == %v", unit.AllUnitKeyNames(), teams[0].Units)
|
||||
assert.Equal(t, "owner", teams[0].Permission)
|
||||
assert.Equal(t, api.AccessLevelNameOwner, teams[0].Permission)
|
||||
|
||||
assert.Equal(t, "test_team", teams[1].Name)
|
||||
assert.False(t, teams[1].CanCreateOrgRepo)
|
||||
assert.Equal(t, []string{"repo.issues"}, teams[1].Units)
|
||||
assert.Equal(t, "write", teams[1].Permission)
|
||||
assert.Equal(t, api.AccessLevelNameWrite, teams[1].Permission)
|
||||
}
|
||||
|
||||
// IsTeam
|
||||
|
||||
@@ -75,9 +75,9 @@ func TestAPITeam(t *testing.T) {
|
||||
resp = MakeRequest(t, req, http.StatusCreated)
|
||||
apiTeam = DecodeJSON(t, resp, &api.Team{})
|
||||
checkTeamResponse(t, "CreateTeam1", apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
|
||||
"none", teamToCreate.Units, nil)
|
||||
api.AccessLevelNameNone, teamToCreate.Units, nil)
|
||||
checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
|
||||
"none", teamToCreate.Units, nil)
|
||||
api.AccessLevelNameNone, teamToCreate.Units, nil)
|
||||
teamID := apiTeam.ID
|
||||
|
||||
// Edit team.
|
||||
@@ -96,9 +96,9 @@ func TestAPITeam(t *testing.T) {
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
apiTeam = DecodeJSON(t, resp, &api.Team{})
|
||||
checkTeamResponse(t, "EditTeam1", apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories,
|
||||
teamToEdit.Permission, unit.AllUnitKeyNames(), nil)
|
||||
api.AccessLevelName(teamToEdit.Permission), unit.AllUnitKeyNames(), nil)
|
||||
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories,
|
||||
teamToEdit.Permission, unit.AllUnitKeyNames(), nil)
|
||||
api.AccessLevelName(teamToEdit.Permission), unit.AllUnitKeyNames(), nil)
|
||||
|
||||
// Edit team Description only
|
||||
editDescription = "first team"
|
||||
@@ -108,9 +108,9 @@ func TestAPITeam(t *testing.T) {
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
apiTeam = DecodeJSON(t, resp, &api.Team{})
|
||||
checkTeamResponse(t, "EditTeam1_DescOnly", apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories,
|
||||
teamToEdit.Permission, unit.AllUnitKeyNames(), nil)
|
||||
api.AccessLevelName(teamToEdit.Permission), unit.AllUnitKeyNames(), nil)
|
||||
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories,
|
||||
teamToEdit.Permission, unit.AllUnitKeyNames(), nil)
|
||||
api.AccessLevelName(teamToEdit.Permission), unit.AllUnitKeyNames(), nil)
|
||||
|
||||
// Read team.
|
||||
teamRead := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
||||
@@ -120,7 +120,7 @@ func TestAPITeam(t *testing.T) {
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
apiTeam = DecodeJSON(t, resp, &api.Team{})
|
||||
checkTeamResponse(t, "ReadTeam1", apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories,
|
||||
teamRead.AccessMode.ToString(), teamRead.GetUnitNames(), teamRead.GetUnitsMap())
|
||||
api.AccessLevelName(teamRead.AccessMode.ToString()), teamRead.GetUnitNames(), teamRead.GetUnitsMap())
|
||||
|
||||
// Delete team.
|
||||
req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID).
|
||||
@@ -142,9 +142,9 @@ func TestAPITeam(t *testing.T) {
|
||||
resp = MakeRequest(t, req, http.StatusCreated)
|
||||
apiTeam = DecodeJSON(t, resp, &api.Team{})
|
||||
checkTeamResponse(t, "CreateTeam2", apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
|
||||
"none", nil, teamToCreate.UnitsMap)
|
||||
api.AccessLevelNameNone, nil, teamToCreate.UnitsMap)
|
||||
checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
|
||||
"none", nil, teamToCreate.UnitsMap)
|
||||
api.AccessLevelNameNone, nil, teamToCreate.UnitsMap)
|
||||
teamID = apiTeam.ID
|
||||
|
||||
// Edit team.
|
||||
@@ -163,9 +163,9 @@ func TestAPITeam(t *testing.T) {
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
apiTeam = DecodeJSON(t, resp, &api.Team{})
|
||||
checkTeamResponse(t, "EditTeam2", apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories,
|
||||
"none", nil, teamToEdit.UnitsMap)
|
||||
api.AccessLevelNameNone, nil, teamToEdit.UnitsMap)
|
||||
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories,
|
||||
"none", nil, teamToEdit.UnitsMap)
|
||||
api.AccessLevelNameNone, nil, teamToEdit.UnitsMap)
|
||||
|
||||
// Edit team Description only
|
||||
editDescription = "second team"
|
||||
@@ -175,9 +175,9 @@ func TestAPITeam(t *testing.T) {
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
apiTeam = DecodeJSON(t, resp, &api.Team{})
|
||||
checkTeamResponse(t, "EditTeam2_DescOnly", apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories,
|
||||
"none", nil, teamToEdit.UnitsMap)
|
||||
api.AccessLevelNameNone, nil, teamToEdit.UnitsMap)
|
||||
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories,
|
||||
"none", nil, teamToEdit.UnitsMap)
|
||||
api.AccessLevelNameNone, nil, teamToEdit.UnitsMap)
|
||||
|
||||
// Read team.
|
||||
teamRead = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
||||
@@ -187,7 +187,7 @@ func TestAPITeam(t *testing.T) {
|
||||
apiTeam = DecodeJSON(t, resp, &api.Team{})
|
||||
assert.NoError(t, teamRead.LoadUnits(t.Context()))
|
||||
checkTeamResponse(t, "ReadTeam2", apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories,
|
||||
teamRead.AccessMode.ToString(), teamRead.GetUnitNames(), teamRead.GetUnitsMap())
|
||||
api.AccessLevelName(teamRead.AccessMode.ToString()), teamRead.GetUnitNames(), teamRead.GetUnitsMap())
|
||||
|
||||
// Delete team.
|
||||
req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID).
|
||||
@@ -227,7 +227,7 @@ func TestAPITeam(t *testing.T) {
|
||||
unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID})
|
||||
}
|
||||
|
||||
func checkTeamResponse(t *testing.T, testName string, apiTeam *api.Team, name, description string, includesAllRepositories bool, permission string, units []string, unitsMap map[string]string) {
|
||||
func checkTeamResponse(t *testing.T, testName string, apiTeam *api.Team, name, description string, includesAllRepositories bool, permission api.AccessLevelName, units []string, unitsMap map[string]string) {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
assert.Equal(t, name, apiTeam.Name, "name")
|
||||
assert.Equal(t, description, apiTeam.Description, "description")
|
||||
@@ -244,7 +244,7 @@ func checkTeamResponse(t *testing.T, testName string, apiTeam *api.Team, name, d
|
||||
})
|
||||
}
|
||||
|
||||
func checkTeamBean(t *testing.T, id int64, name, description string, includesAllRepositories bool, permission string, units []string, unitsMap map[string]string) {
|
||||
func checkTeamBean(t *testing.T, id int64, name, description string, includesAllRepositories bool, permission api.AccessLevelName, units []string, unitsMap map[string]string) {
|
||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: id})
|
||||
assert.NoError(t, team.LoadUnits(t.Context()), "LoadUnits")
|
||||
apiTeam, err := convert.ToTeam(t.Context(), team)
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestAPIUserSearchLoggedIn(t *testing.T) {
|
||||
for _, user := range results.Data {
|
||||
assert.Contains(t, user.UserName, query)
|
||||
assert.NotEmpty(t, user.Email)
|
||||
assert.Equal(t, "public", user.Visibility)
|
||||
assert.Equal(t, api.UserVisibilityPublic, user.Visibility)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) {
|
||||
for _, user := range results.Data {
|
||||
assert.Contains(t, user.UserName, query)
|
||||
assert.NotEmpty(t, user.Email)
|
||||
assert.Equal(t, "private", user.Visibility)
|
||||
assert.Equal(t, api.UserVisibilityPrivate, user.Visibility)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -192,17 +192,21 @@ func testRenameInvalidUsername(t *testing.T) {
|
||||
}
|
||||
|
||||
func testRenameReservedUsername(t *testing.T) {
|
||||
reservedUsernames := []string{
|
||||
// ".", "..", ".well-known", // The names are not only reserved but also invalid
|
||||
// ".", "..", ".well-known" are also reserved but invalid as form input.
|
||||
reservedNames := []string{
|
||||
"api",
|
||||
"openapi3.v1.json",
|
||||
"swagger.v1.json",
|
||||
}
|
||||
patternNotAllowedNames := []string{
|
||||
"name.keys",
|
||||
}
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
locale := translation.NewLocale("en-US")
|
||||
for _, reservedUsername := range reservedUsernames {
|
||||
check := func(name, msgKey string) {
|
||||
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
|
||||
"name": reservedUsername,
|
||||
"name": name,
|
||||
"email": "user2@example.com",
|
||||
"language": "en-US",
|
||||
})
|
||||
@@ -212,12 +216,14 @@ func testRenameReservedUsername(t *testing.T) {
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
actualMsg := strings.TrimSpace(htmlDoc.doc.Find(".ui.negative.message").Text())
|
||||
expectedMsg := locale.TrString("user.form.name_reserved", reservedUsername)
|
||||
if strings.Contains(reservedUsername, ".") {
|
||||
expectedMsg = locale.TrString("user.form.name_pattern_not_allowed", reservedUsername)
|
||||
}
|
||||
assert.Equal(t, expectedMsg, actualMsg)
|
||||
unittest.AssertNotExistsBean(t, &user_model.User{Name: reservedUsername})
|
||||
assert.Equal(t, locale.TrString(msgKey, name), actualMsg)
|
||||
unittest.AssertNotExistsBean(t, &user_model.User{Name: name})
|
||||
}
|
||||
for _, name := range reservedNames {
|
||||
check(name, "user.form.name_reserved")
|
||||
}
|
||||
for _, name := range patternNotAllowedNames {
|
||||
check(name, "user.form.name_pattern_not_allowed")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user