pax_global_header00006660000000000000000000000064133210044470014510gustar00rootroot0000000000000052 comment=3c8fe72ed5d307113ef76c7c3035964d7e4d4b73 golang-github-go-openapi-analysis-0.15.0/000077500000000000000000000000001332100444700201775ustar00rootroot00000000000000golang-github-go-openapi-analysis-0.15.0/.github/000077500000000000000000000000001332100444700215375ustar00rootroot00000000000000golang-github-go-openapi-analysis-0.15.0/.github/CONTRIBUTING.md000066400000000000000000000114601332100444700237720ustar00rootroot00000000000000## Contribution Guidelines ### Pull requests are always welcome We are always thrilled to receive pull requests, and do our best to process them as fast as possible. Not sure if that typo is worth a pull request? Do it! We will appreciate it. If your pull request is not accepted on the first try, don't be discouraged! If there's a problem with the implementation, hopefully you received feedback on what to improve. We're trying very hard to keep go-swagger lean and focused. We don't want it to do everything for everybody. This means that we might decide against incorporating a new feature. However, there might be a way to implement that feature *on top of* go-swagger. ### Conventions Fork the repo and make changes on your fork in a feature branch: - If it's a bugfix branch, name it XXX-something where XXX is the number of the issue - If it's a feature branch, create an enhancement issue to announce your intentions, and name it XXX-something where XXX is the number of the issue. Submit unit tests for your changes. Go has a great test framework built in; use it! Take a look at existing tests for inspiration. Run the full test suite on your branch before submitting a pull request. Update the documentation when creating or modifying features. Test your documentation changes for clarity, concision, and correctness, as well as a clean documentation build. See ``docs/README.md`` for more information on building the docs and how docs get released. Write clean code. Universally formatted code promotes ease of writing, reading, and maintenance. Always run `gofmt -s -w file.go` on each changed file before committing your changes. Most editors have plugins that do this automatically. Pull requests descriptions should be as clear as possible and include a reference to all the issues that they address. Pull requests must not contain commits from other users or branches. Commit messages must start with a capitalized and short summary (max. 50 chars) written in the imperative, followed by an optional, more detailed explanatory text which is separated from the summary by an empty line. Code review comments may be added to your pull request. Discuss, then make the suggested modifications and push additional commits to your feature branch. Be sure to post a comment after pushing. The new commits will show up in the pull request automatically, but the reviewers will not be notified unless you comment. Before the pull request is merged, make sure that you squash your commits into logical units of work using `git rebase -i` and `git push -f`. After every commit the test suite should be passing. Include documentation changes in the same commit so that a revert would remove all traces of the feature or fix. Commits that fix or close an issue should include a reference like `Closes #XXX` or `Fixes #XXX`, which will automatically close the issue when merged. ### Sign your work The sign-off is a simple line at the end of the explanation for the patch, which certifies that you wrote it or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify the below (from [developercertificate.org](http://developercertificate.org/)): ``` Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 660 York Street, Suite 102, San Francisco, CA 94110 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ``` then you just add a line to every git commit message: Signed-off-by: Joe Smith using your real name (sorry, no pseudonyms or anonymous contributions.) You can add the sign off when creating the git commit via `git commit -s`. golang-github-go-openapi-analysis-0.15.0/.gitignore000066400000000000000000000000371332100444700221670ustar00rootroot00000000000000secrets.yml coverage.out .idea golang-github-go-openapi-analysis-0.15.0/.golangci.yml000066400000000000000000000004341332100444700225640ustar00rootroot00000000000000linters-settings: govet: check-shadowing: true golint: min-confidence: 0 gocyclo: min-complexity: 30 maligned: suggest-new: true dupl: threshold: 100 goconst: min-len: 2 min-occurrences: 4 linters: enable-all: true disable: - maligned golang-github-go-openapi-analysis-0.15.0/.travis.yml000066400000000000000000000022621332100444700223120ustar00rootroot00000000000000language: go go: - 1.7 - 1.8 - 1.9 - 1.10.x install: - go get -u github.com/stretchr/testify/assert - go get -u gopkg.in/yaml.v2 - go get -u github.com/go-openapi/swag - go get -u github.com/go-openapi/jsonpointer - go get -u github.com/go-openapi/spec - go get -u github.com/go-openapi/strfmt - go get -u github.com/go-openapi/loads/fmts script: - go test -v -race -cover -coverprofile=coverage.txt -covermode=atomic ./... after_success: - bash <(curl -s https://codecov.io/bash) notifications: slack: secure: Sf7kZf7ZGbnwWUMpffHwMu5A0cHkLK2MYY32LNTPj4+/3qC3Ghl7+9v4TSLOqOlCwdRNjOGblAq7s+GDJed6/xgRQl1JtCi1klzZNrYX4q01pgTPvvGcwbBkIYgeMaPeIRcK9OZnud7sRXdttozgTOpytps2U6Js32ip7uj5mHSg2ub0FwoSJwlS6dbezZ8+eDhoha0F/guY99BEwx8Bd+zROrT2TFGsSGOFGN6wFc7moCqTHO/YkWib13a2QNXqOxCCVBy/lt76Wp+JkeFppjHlzs/2lP3EAk13RIUAaesdEUHvIHrzCyNJEd3/+KO2DzsWOYfpktd+KBCvgaYOsoo7ubdT3IROeAegZdCgo/6xgCEsmFc9ZcqCfN5yNx2A+BZ2Vwmpws+bQ1E1+B5HDzzaiLcYfG4X2O210QVGVDLWsv1jqD+uPYeHY2WRfh5ZsIUFvaqgUEnwHwrK44/8REAhQavt1QAj5uJpsRd7CkRVPWRNK+yIky+wgbVUFEchRNmS55E7QWf+W4+4QZkQi7vUTMc9nbTUu2Es9NfvfudOpM2wZbn98fjpb/qq/nRv6Bk+ca+7XD5/IgNLMbWp2ouDdzbiHLCOfDUiHiDJhLfFZx9Bwo7ZwfzeOlbrQX66bx7xRKYmOe4DLrXhNcpbsMa8qbfxlZRCmYbubB/Y8h4= golang-github-go-openapi-analysis-0.15.0/CODE_OF_CONDUCT.md000066400000000000000000000062411332100444700230010ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at ivan+abuse@flanders.co.nz. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ golang-github-go-openapi-analysis-0.15.0/LICENSE000066400000000000000000000261361332100444700212140ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. golang-github-go-openapi-analysis-0.15.0/README.md000066400000000000000000000017331332100444700214620ustar00rootroot00000000000000# OpenAPI initiative analysis [![Build Status](https://travis-ci.org/go-openapi/analysis.svg?branch=master)](https://travis-ci.org/go-openapi/analysis) [![codecov](https://codecov.io/gh/go-openapi/analysis/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/analysis) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io) [![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/analysis/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/analysis?status.svg)](http://godoc.org/github.com/go-openapi/analysis) [![GolangCI](https://golangci.com/badges/github.com/go-openapi/analysis.svg)](https://golangci.com) [![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/analysis)](https://goreportcard.com/report/github.com/go-openapi/analysis) A foundational library to analyze an OAI specification document for easier reasoning about the content. golang-github-go-openapi-analysis-0.15.0/analyzer.go000066400000000000000000000657211332100444700223660ustar00rootroot00000000000000// Copyright 2015 go-swagger maintainers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package analysis import ( "fmt" slashpath "path" "strconv" "strings" "github.com/go-openapi/jsonpointer" "github.com/go-openapi/spec" "github.com/go-openapi/swag" ) type referenceAnalysis struct { schemas map[string]spec.Ref responses map[string]spec.Ref parameters map[string]spec.Ref items map[string]spec.Ref headerItems map[string]spec.Ref parameterItems map[string]spec.Ref allRefs map[string]spec.Ref pathItems map[string]spec.Ref } func (r *referenceAnalysis) addRef(key string, ref spec.Ref) { r.allRefs["#"+key] = ref } func (r *referenceAnalysis) addItemsRef(key string, items *spec.Items, location string) { r.items["#"+key] = items.Ref r.addRef(key, items.Ref) if location == "header" { // NOTE: in swagger 2.0, headers and parameters (but not body param schemas) are simple schemas // and $ref are not supported here. However it is possible to analyze this. r.headerItems["#"+key] = items.Ref } else { r.parameterItems["#"+key] = items.Ref } } func (r *referenceAnalysis) addSchemaRef(key string, ref SchemaRef) { r.schemas["#"+key] = ref.Schema.Ref r.addRef(key, ref.Schema.Ref) } func (r *referenceAnalysis) addResponseRef(key string, resp *spec.Response) { r.responses["#"+key] = resp.Ref r.addRef(key, resp.Ref) } func (r *referenceAnalysis) addParamRef(key string, param *spec.Parameter) { r.parameters["#"+key] = param.Ref r.addRef(key, param.Ref) } func (r *referenceAnalysis) addPathItemRef(key string, pathItem *spec.PathItem) { r.pathItems["#"+key] = pathItem.Ref r.addRef(key, pathItem.Ref) } type patternAnalysis struct { parameters map[string]string headers map[string]string items map[string]string schemas map[string]string allPatterns map[string]string } func (p *patternAnalysis) addPattern(key, pattern string) { p.allPatterns["#"+key] = pattern } func (p *patternAnalysis) addParameterPattern(key, pattern string) { p.parameters["#"+key] = pattern p.addPattern(key, pattern) } func (p *patternAnalysis) addHeaderPattern(key, pattern string) { p.headers["#"+key] = pattern p.addPattern(key, pattern) } func (p *patternAnalysis) addItemsPattern(key, pattern string) { p.items["#"+key] = pattern p.addPattern(key, pattern) } func (p *patternAnalysis) addSchemaPattern(key, pattern string) { p.schemas["#"+key] = pattern p.addPattern(key, pattern) } // New takes a swagger spec object and returns an analyzed spec document. // The analyzed document contains a number of indices that make it easier to // reason about semantics of a swagger specification for use in code generation // or validation etc. func New(doc *spec.Swagger) *Spec { a := &Spec{ spec: doc, consumes: make(map[string]struct{}, 150), produces: make(map[string]struct{}, 150), authSchemes: make(map[string]struct{}, 150), operations: make(map[string]map[string]*spec.Operation, 150), allSchemas: make(map[string]SchemaRef, 150), allOfs: make(map[string]SchemaRef, 150), references: referenceAnalysis{ schemas: make(map[string]spec.Ref, 150), pathItems: make(map[string]spec.Ref, 150), responses: make(map[string]spec.Ref, 150), parameters: make(map[string]spec.Ref, 150), items: make(map[string]spec.Ref, 150), headerItems: make(map[string]spec.Ref, 150), parameterItems: make(map[string]spec.Ref, 150), allRefs: make(map[string]spec.Ref, 150), }, patterns: patternAnalysis{ parameters: make(map[string]string, 150), headers: make(map[string]string, 150), items: make(map[string]string, 150), schemas: make(map[string]string, 150), allPatterns: make(map[string]string, 150), }, } a.initialize() return a } // Spec is an analyzed specification object. It takes a swagger spec object and turns it into a registry // with a bunch of utility methods to act on the information in the spec. type Spec struct { spec *spec.Swagger consumes map[string]struct{} produces map[string]struct{} authSchemes map[string]struct{} operations map[string]map[string]*spec.Operation references referenceAnalysis patterns patternAnalysis allSchemas map[string]SchemaRef allOfs map[string]SchemaRef } func (s *Spec) reset() { s.consumes = make(map[string]struct{}, 150) s.produces = make(map[string]struct{}, 150) s.authSchemes = make(map[string]struct{}, 150) s.operations = make(map[string]map[string]*spec.Operation, 150) s.allSchemas = make(map[string]SchemaRef, 150) s.allOfs = make(map[string]SchemaRef, 150) s.references.schemas = make(map[string]spec.Ref, 150) s.references.pathItems = make(map[string]spec.Ref, 150) s.references.responses = make(map[string]spec.Ref, 150) s.references.parameters = make(map[string]spec.Ref, 150) s.references.items = make(map[string]spec.Ref, 150) s.references.headerItems = make(map[string]spec.Ref, 150) s.references.parameterItems = make(map[string]spec.Ref, 150) s.references.allRefs = make(map[string]spec.Ref, 150) s.patterns.parameters = make(map[string]string, 150) s.patterns.headers = make(map[string]string, 150) s.patterns.items = make(map[string]string, 150) s.patterns.schemas = make(map[string]string, 150) s.patterns.allPatterns = make(map[string]string, 150) } func (s *Spec) reload() { s.reset() s.initialize() } func (s *Spec) initialize() { for _, c := range s.spec.Consumes { s.consumes[c] = struct{}{} } for _, c := range s.spec.Produces { s.produces[c] = struct{}{} } for _, ss := range s.spec.Security { for k := range ss { s.authSchemes[k] = struct{}{} } } for path, pathItem := range s.AllPaths() { s.analyzeOperations(path, &pathItem) } for name, parameter := range s.spec.Parameters { refPref := slashpath.Join("/parameters", jsonpointer.Escape(name)) if parameter.Items != nil { s.analyzeItems("items", parameter.Items, refPref, "parameter") } if parameter.In == "body" && parameter.Schema != nil { s.analyzeSchema("schema", *parameter.Schema, refPref) } if parameter.Pattern != "" { s.patterns.addParameterPattern(refPref, parameter.Pattern) } } for name, response := range s.spec.Responses { refPref := slashpath.Join("/responses", jsonpointer.Escape(name)) for k, v := range response.Headers { hRefPref := slashpath.Join(refPref, "headers", k) if v.Items != nil { s.analyzeItems("items", v.Items, hRefPref, "header") } if v.Pattern != "" { s.patterns.addHeaderPattern(hRefPref, v.Pattern) } } if response.Schema != nil { s.analyzeSchema("schema", *response.Schema, refPref) } } for name, schema := range s.spec.Definitions { s.analyzeSchema(name, schema, "/definitions") } // TODO: after analyzing all things and flattening schemas etc // resolve all the collected references to their final representations // best put in a separate method because this could get expensive } func (s *Spec) analyzeOperations(path string, pi *spec.PathItem) { // TODO: resolve refs here? // Currently, operations declared via pathItem $ref are known only after expansion op := pi if pi.Ref.String() != "" { key := slashpath.Join("/paths", jsonpointer.Escape(path)) s.references.addPathItemRef(key, pi) } s.analyzeOperation("GET", path, op.Get) s.analyzeOperation("PUT", path, op.Put) s.analyzeOperation("POST", path, op.Post) s.analyzeOperation("PATCH", path, op.Patch) s.analyzeOperation("DELETE", path, op.Delete) s.analyzeOperation("HEAD", path, op.Head) s.analyzeOperation("OPTIONS", path, op.Options) for i, param := range op.Parameters { refPref := slashpath.Join("/paths", jsonpointer.Escape(path), "parameters", strconv.Itoa(i)) if param.Ref.String() != "" { s.references.addParamRef(refPref, ¶m) } if param.Pattern != "" { s.patterns.addParameterPattern(refPref, param.Pattern) } if param.Items != nil { s.analyzeItems("items", param.Items, refPref, "parameter") } if param.Schema != nil { s.analyzeSchema("schema", *param.Schema, refPref) } } } func (s *Spec) analyzeItems(name string, items *spec.Items, prefix, location string) { if items == nil { return } refPref := slashpath.Join(prefix, name) s.analyzeItems(name, items.Items, refPref, location) if items.Ref.String() != "" { s.references.addItemsRef(refPref, items, location) } if items.Pattern != "" { s.patterns.addItemsPattern(refPref, items.Pattern) } } func (s *Spec) analyzeOperation(method, path string, op *spec.Operation) { if op == nil { return } for _, c := range op.Consumes { s.consumes[c] = struct{}{} } for _, c := range op.Produces { s.produces[c] = struct{}{} } for _, ss := range op.Security { for k := range ss { s.authSchemes[k] = struct{}{} } } if _, ok := s.operations[method]; !ok { s.operations[method] = make(map[string]*spec.Operation) } s.operations[method][path] = op prefix := slashpath.Join("/paths", jsonpointer.Escape(path), strings.ToLower(method)) for i, param := range op.Parameters { refPref := slashpath.Join(prefix, "parameters", strconv.Itoa(i)) if param.Ref.String() != "" { s.references.addParamRef(refPref, ¶m) } if param.Pattern != "" { s.patterns.addParameterPattern(refPref, param.Pattern) } s.analyzeItems("items", param.Items, refPref, "parameter") if param.In == "body" && param.Schema != nil { s.analyzeSchema("schema", *param.Schema, refPref) } } if op.Responses != nil { if op.Responses.Default != nil { refPref := slashpath.Join(prefix, "responses", "default") if op.Responses.Default.Ref.String() != "" { s.references.addResponseRef(refPref, op.Responses.Default) } for k, v := range op.Responses.Default.Headers { hRefPref := slashpath.Join(refPref, "headers", k) s.analyzeItems("items", v.Items, hRefPref, "header") if v.Pattern != "" { s.patterns.addHeaderPattern(hRefPref, v.Pattern) } } if op.Responses.Default.Schema != nil { s.analyzeSchema("schema", *op.Responses.Default.Schema, refPref) } } for k, res := range op.Responses.StatusCodeResponses { refPref := slashpath.Join(prefix, "responses", strconv.Itoa(k)) if res.Ref.String() != "" { s.references.addResponseRef(refPref, &res) } for k, v := range res.Headers { hRefPref := slashpath.Join(refPref, "headers", k) s.analyzeItems("items", v.Items, hRefPref, "header") if v.Pattern != "" { s.patterns.addHeaderPattern(hRefPref, v.Pattern) } } if res.Schema != nil { s.analyzeSchema("schema", *res.Schema, refPref) } } } } func (s *Spec) analyzeSchema(name string, schema spec.Schema, prefix string) { refURI := slashpath.Join(prefix, jsonpointer.Escape(name)) schRef := SchemaRef{ Name: name, Schema: &schema, Ref: spec.MustCreateRef("#" + refURI), TopLevel: prefix == "/definitions", } s.allSchemas["#"+refURI] = schRef if schema.Ref.String() != "" { s.references.addSchemaRef(refURI, schRef) } if schema.Pattern != "" { s.patterns.addSchemaPattern(refURI, schema.Pattern) } for k, v := range schema.Definitions { s.analyzeSchema(k, v, slashpath.Join(refURI, "definitions")) } for k, v := range schema.Properties { s.analyzeSchema(k, v, slashpath.Join(refURI, "properties")) } for k, v := range schema.PatternProperties { // NOTE: swagger 2.0 does not support PatternProperties. // However it is possible to analyze this in a schema s.analyzeSchema(k, v, slashpath.Join(refURI, "patternProperties")) } for i, v := range schema.AllOf { s.analyzeSchema(strconv.Itoa(i), v, slashpath.Join(refURI, "allOf")) } if len(schema.AllOf) > 0 { s.allOfs["#"+refURI] = schRef } for i, v := range schema.AnyOf { // NOTE: swagger 2.0 does not support anyOf constructs. // However it is possible to analyze this in a schema s.analyzeSchema(strconv.Itoa(i), v, slashpath.Join(refURI, "anyOf")) } for i, v := range schema.OneOf { // NOTE: swagger 2.0 does not support oneOf constructs. // However it is possible to analyze this in a schema s.analyzeSchema(strconv.Itoa(i), v, slashpath.Join(refURI, "oneOf")) } if schema.Not != nil { // NOTE: swagger 2.0 does not support "not" constructs. // However it is possible to analyze this in a schema s.analyzeSchema("not", *schema.Not, refURI) } if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil { s.analyzeSchema("additionalProperties", *schema.AdditionalProperties.Schema, refURI) } if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil { // NOTE: swagger 2.0 does not support AdditionalItems. // However it is possible to analyze this in a schema s.analyzeSchema("additionalItems", *schema.AdditionalItems.Schema, refURI) } if schema.Items != nil { if schema.Items.Schema != nil { s.analyzeSchema("items", *schema.Items.Schema, refURI) } for i, sch := range schema.Items.Schemas { s.analyzeSchema(strconv.Itoa(i), sch, slashpath.Join(refURI, "items")) } } } // SecurityRequirement is a representation of a security requirement for an operation type SecurityRequirement struct { Name string Scopes []string } // SecurityRequirementsFor gets the security requirements for the operation func (s *Spec) SecurityRequirementsFor(operation *spec.Operation) [][]SecurityRequirement { if s.spec.Security == nil && operation.Security == nil { return nil } schemes := s.spec.Security if operation.Security != nil { schemes = operation.Security } result := [][]SecurityRequirement{} for _, scheme := range schemes { if len(scheme) == 0 { // append a zero object for anonymous result = append(result, []SecurityRequirement{{}}) continue } var reqs []SecurityRequirement for k, v := range scheme { if v == nil { v = []string{} } reqs = append(reqs, SecurityRequirement{Name: k, Scopes: v}) } result = append(result, reqs) } return result } // SecurityDefinitionsForRequirements gets the matching security definitions for a set of requirements func (s *Spec) SecurityDefinitionsForRequirements(requirements []SecurityRequirement) map[string]spec.SecurityScheme { result := make(map[string]spec.SecurityScheme) for _, v := range requirements { if definition, ok := s.spec.SecurityDefinitions[v.Name]; ok { if definition != nil { result[v.Name] = *definition } } } return result } // SecurityDefinitionsFor gets the matching security definitions for a set of requirements func (s *Spec) SecurityDefinitionsFor(operation *spec.Operation) map[string]spec.SecurityScheme { requirements := s.SecurityRequirementsFor(operation) if len(requirements) == 0 { return nil } result := make(map[string]spec.SecurityScheme) for _, reqs := range requirements { for _, v := range reqs { if v.Name == "" { // optional requirement continue } if _, ok := result[v.Name]; ok { // duplicate requirement continue } if definition, ok := s.spec.SecurityDefinitions[v.Name]; ok { if definition != nil { result[v.Name] = *definition } } } } return result } // ConsumesFor gets the mediatypes for the operation func (s *Spec) ConsumesFor(operation *spec.Operation) []string { if len(operation.Consumes) == 0 { cons := make(map[string]struct{}, len(s.spec.Consumes)) for _, k := range s.spec.Consumes { cons[k] = struct{}{} } return s.structMapKeys(cons) } cons := make(map[string]struct{}, len(operation.Consumes)) for _, c := range operation.Consumes { cons[c] = struct{}{} } return s.structMapKeys(cons) } // ProducesFor gets the mediatypes for the operation func (s *Spec) ProducesFor(operation *spec.Operation) []string { if len(operation.Produces) == 0 { prod := make(map[string]struct{}, len(s.spec.Produces)) for _, k := range s.spec.Produces { prod[k] = struct{}{} } return s.structMapKeys(prod) } prod := make(map[string]struct{}, len(operation.Produces)) for _, c := range operation.Produces { prod[c] = struct{}{} } return s.structMapKeys(prod) } func mapKeyFromParam(param *spec.Parameter) string { return fmt.Sprintf("%s#%s", param.In, fieldNameFromParam(param)) } func fieldNameFromParam(param *spec.Parameter) string { // TODO: this should be x-go-name if nm, ok := param.Extensions.GetString("go-name"); ok { return nm } return swag.ToGoName(param.Name) } // ErrorOnParamFunc is a callback function to be invoked // whenever an error is encountered while resolving references // on parameters. // // This function takes as input the spec.Parameter which triggered the // error and the error itself. // // If the callback function returns false, the calling function should bail. // // If it returns true, the calling function should continue evaluating parameters. // A nil ErrorOnParamFunc must be evaluated as equivalent to panic(). type ErrorOnParamFunc func(spec.Parameter, error) bool func (s *Spec) paramsAsMap(parameters []spec.Parameter, res map[string]spec.Parameter, callmeOnError ErrorOnParamFunc) { for _, param := range parameters { pr := param if pr.Ref.String() != "" { obj, _, err := pr.Ref.GetPointer().Get(s.spec) if err != nil { if callmeOnError != nil { if callmeOnError(param, fmt.Errorf("invalid reference: %q", pr.Ref.String())) { continue } break } else { panic(fmt.Sprintf("invalid reference: %q", pr.Ref.String())) } } if objAsParam, ok := obj.(spec.Parameter); ok { pr = objAsParam } else { if callmeOnError != nil { if callmeOnError(param, fmt.Errorf("resolved reference is not a parameter: %q", pr.Ref.String())) { continue } break } else { panic(fmt.Sprintf("resolved reference is not a parameter: %q", pr.Ref.String())) } } } res[mapKeyFromParam(&pr)] = pr } } // ParametersFor the specified operation id. // // Assumes parameters properly resolve references if any and that // such references actually resolve to a parameter object. // Otherwise, panics. func (s *Spec) ParametersFor(operationID string) []spec.Parameter { return s.SafeParametersFor(operationID, nil) } // SafeParametersFor the specified operation id. // // Does not assume parameters properly resolve references or that // such references actually resolve to a parameter object. // // Upon error, invoke a ErrorOnParamFunc callback with the erroneous // parameters. If the callback is set to nil, panics upon errors. func (s *Spec) SafeParametersFor(operationID string, callmeOnError ErrorOnParamFunc) []spec.Parameter { gatherParams := func(pi *spec.PathItem, op *spec.Operation) []spec.Parameter { bag := make(map[string]spec.Parameter) s.paramsAsMap(pi.Parameters, bag, callmeOnError) s.paramsAsMap(op.Parameters, bag, callmeOnError) var res []spec.Parameter for _, v := range bag { res = append(res, v) } return res } for _, pi := range s.spec.Paths.Paths { if pi.Get != nil && pi.Get.ID == operationID { return gatherParams(&pi, pi.Get) } if pi.Head != nil && pi.Head.ID == operationID { return gatherParams(&pi, pi.Head) } if pi.Options != nil && pi.Options.ID == operationID { return gatherParams(&pi, pi.Options) } if pi.Post != nil && pi.Post.ID == operationID { return gatherParams(&pi, pi.Post) } if pi.Patch != nil && pi.Patch.ID == operationID { return gatherParams(&pi, pi.Patch) } if pi.Put != nil && pi.Put.ID == operationID { return gatherParams(&pi, pi.Put) } if pi.Delete != nil && pi.Delete.ID == operationID { return gatherParams(&pi, pi.Delete) } } return nil } // ParamsFor the specified method and path. Aggregates them with the defaults etc, so it's all the params that // apply for the method and path. // // Assumes parameters properly resolve references if any and that // such references actually resolve to a parameter object. // Otherwise, panics. func (s *Spec) ParamsFor(method, path string) map[string]spec.Parameter { return s.SafeParamsFor(method, path, nil) } // SafeParamsFor the specified method and path. Aggregates them with the defaults etc, so it's all the params that // apply for the method and path. // // Does not assume parameters properly resolve references or that // such references actually resolve to a parameter object. // // Upon error, invoke a ErrorOnParamFunc callback with the erroneous // parameters. If the callback is set to nil, panics upon errors. func (s *Spec) SafeParamsFor(method, path string, callmeOnError ErrorOnParamFunc) map[string]spec.Parameter { res := make(map[string]spec.Parameter) if pi, ok := s.spec.Paths.Paths[path]; ok { s.paramsAsMap(pi.Parameters, res, callmeOnError) s.paramsAsMap(s.operations[strings.ToUpper(method)][path].Parameters, res, callmeOnError) } return res } // OperationForName gets the operation for the given id func (s *Spec) OperationForName(operationID string) (string, string, *spec.Operation, bool) { for method, pathItem := range s.operations { for path, op := range pathItem { if operationID == op.ID { return method, path, op, true } } } return "", "", nil, false } // OperationFor the given method and path func (s *Spec) OperationFor(method, path string) (*spec.Operation, bool) { if mp, ok := s.operations[strings.ToUpper(method)]; ok { op, fn := mp[path] return op, fn } return nil, false } // Operations gathers all the operations specified in the spec document func (s *Spec) Operations() map[string]map[string]*spec.Operation { return s.operations } func (s *Spec) structMapKeys(mp map[string]struct{}) []string { if len(mp) == 0 { return nil } result := make([]string, 0, len(mp)) for k := range mp { result = append(result, k) } return result } // AllPaths returns all the paths in the swagger spec func (s *Spec) AllPaths() map[string]spec.PathItem { if s.spec == nil || s.spec.Paths == nil { return nil } return s.spec.Paths.Paths } // OperationIDs gets all the operation ids based on method an dpath func (s *Spec) OperationIDs() []string { if len(s.operations) == 0 { return nil } result := make([]string, 0, len(s.operations)) for method, v := range s.operations { for p, o := range v { if o.ID != "" { result = append(result, o.ID) } else { result = append(result, fmt.Sprintf("%s %s", strings.ToUpper(method), p)) } } } return result } // OperationMethodPaths gets all the operation ids based on method an dpath func (s *Spec) OperationMethodPaths() []string { if len(s.operations) == 0 { return nil } result := make([]string, 0, len(s.operations)) for method, v := range s.operations { for p := range v { result = append(result, fmt.Sprintf("%s %s", strings.ToUpper(method), p)) } } return result } // RequiredConsumes gets all the distinct consumes that are specified in the specification document func (s *Spec) RequiredConsumes() []string { return s.structMapKeys(s.consumes) } // RequiredProduces gets all the distinct produces that are specified in the specification document func (s *Spec) RequiredProduces() []string { return s.structMapKeys(s.produces) } // RequiredSecuritySchemes gets all the distinct security schemes that are specified in the swagger spec func (s *Spec) RequiredSecuritySchemes() []string { return s.structMapKeys(s.authSchemes) } // SchemaRef is a reference to a schema type SchemaRef struct { Name string Ref spec.Ref Schema *spec.Schema TopLevel bool } // SchemasWithAllOf returns schema references to all schemas that are defined // with an allOf key func (s *Spec) SchemasWithAllOf() (result []SchemaRef) { for _, v := range s.allOfs { result = append(result, v) } return } // AllDefinitions returns schema references for all the definitions that were discovered func (s *Spec) AllDefinitions() (result []SchemaRef) { for _, v := range s.allSchemas { result = append(result, v) } return } // AllDefinitionReferences returns json refs for all the discovered schemas func (s *Spec) AllDefinitionReferences() (result []string) { for _, v := range s.references.schemas { result = append(result, v.String()) } return } // AllParameterReferences returns json refs for all the discovered parameters func (s *Spec) AllParameterReferences() (result []string) { for _, v := range s.references.parameters { result = append(result, v.String()) } return } // AllResponseReferences returns json refs for all the discovered responses func (s *Spec) AllResponseReferences() (result []string) { for _, v := range s.references.responses { result = append(result, v.String()) } return } // AllPathItemReferences returns the references for all the items func (s *Spec) AllPathItemReferences() (result []string) { for _, v := range s.references.pathItems { result = append(result, v.String()) } return } // AllItemsReferences returns the references for all the items in simple schemas (parameters or headers). // // NOTE: since Swagger 2.0 forbids $ref in simple params, this should always yield an empty slice for a valid // Swagger 2.0 spec. func (s *Spec) AllItemsReferences() (result []string) { for _, v := range s.references.items { result = append(result, v.String()) } return } // AllReferences returns all the references found in the document, with possible duplicates func (s *Spec) AllReferences() (result []string) { for _, v := range s.references.allRefs { result = append(result, v.String()) } return } // AllRefs returns all the unique references found in the document func (s *Spec) AllRefs() (result []spec.Ref) { set := make(map[string]struct{}) for _, v := range s.references.allRefs { a := v.String() if a == "" { continue } if _, ok := set[a]; !ok { set[a] = struct{}{} result = append(result, v) } } return } func cloneStringMap(source map[string]string) map[string]string { res := make(map[string]string, len(source)) for k, v := range source { res[k] = v } return res } // ParameterPatterns returns all the patterns found in parameters // the map is cloned to avoid accidental changes func (s *Spec) ParameterPatterns() map[string]string { return cloneStringMap(s.patterns.parameters) } // HeaderPatterns returns all the patterns found in response headers // the map is cloned to avoid accidental changes func (s *Spec) HeaderPatterns() map[string]string { return cloneStringMap(s.patterns.headers) } // ItemsPatterns returns all the patterns found in simple array items // the map is cloned to avoid accidental changes func (s *Spec) ItemsPatterns() map[string]string { return cloneStringMap(s.patterns.items) } // SchemaPatterns returns all the patterns found in schemas // the map is cloned to avoid accidental changes func (s *Spec) SchemaPatterns() map[string]string { return cloneStringMap(s.patterns.schemas) } // AllPatterns returns all the patterns found in the spec // the map is cloned to avoid accidental changes func (s *Spec) AllPatterns() map[string]string { return cloneStringMap(s.patterns.allPatterns) } golang-github-go-openapi-analysis-0.15.0/analyzer_test.go000066400000000000000000000707201332100444700234200ustar00rootroot00000000000000// Copyright 2015 go-swagger maintainers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package analysis import ( "encoding/json" "fmt" "log" "os" "path/filepath" "sort" "strconv" "testing" "github.com/stretchr/testify/require" "github.com/go-openapi/loads/fmts" "github.com/go-openapi/spec" "github.com/go-openapi/swag" "github.com/stretchr/testify/assert" ) func schemeNames(schemes [][]SecurityRequirement) []string { var names []string for _, scheme := range schemes { for _, v := range scheme { names = append(names, v.Name) } } sort.Strings(names) return names } func makeFixturepec(pi, pi2 spec.PathItem, formatParam *spec.Parameter) *spec.Swagger { return &spec.Swagger{ SwaggerProps: spec.SwaggerProps{ Consumes: []string{"application/json"}, Produces: []string{"application/json"}, Security: []map[string][]string{ {"apikey": nil}, }, SecurityDefinitions: map[string]*spec.SecurityScheme{ "basic": spec.BasicAuth(), "apiKey": spec.APIKeyAuth("api_key", "query"), "oauth2": spec.OAuth2AccessToken("http://authorize.com", "http://token.com"), }, Parameters: map[string]spec.Parameter{"format": *formatParam}, Paths: &spec.Paths{ Paths: map[string]spec.PathItem{ "/": pi, "/items": pi2, }, }, }, } } func TestAnalyzer(t *testing.T) { formatParam := spec.QueryParam("format").Typed("string", "") limitParam := spec.QueryParam("limit").Typed("integer", "int32") limitParam.Extensions = spec.Extensions(map[string]interface{}{}) limitParam.Extensions.Add("go-name", "Limit") skipParam := spec.QueryParam("skip").Typed("integer", "int32") pi := spec.PathItem{} pi.Parameters = []spec.Parameter{*limitParam} op := &spec.Operation{} op.Consumes = []string{"application/x-yaml"} op.Produces = []string{"application/x-yaml"} op.Security = []map[string][]string{ {"oauth2": {}}, {"basic": nil}, } op.ID = "someOperation" op.Parameters = []spec.Parameter{*skipParam} pi.Get = op pi2 := spec.PathItem{} pi2.Parameters = []spec.Parameter{*limitParam} op2 := &spec.Operation{} op2.ID = "anotherOperation" op2.Parameters = []spec.Parameter{*skipParam} pi2.Get = op2 spec := makeFixturepec(pi, pi2, formatParam) analyzer := New(spec) assert.Len(t, analyzer.consumes, 2) assert.Len(t, analyzer.produces, 2) assert.Len(t, analyzer.operations, 1) assert.Equal(t, analyzer.operations["GET"]["/"], spec.Paths.Paths["/"].Get) expected := []string{"application/x-yaml"} sort.Strings(expected) consumes := analyzer.ConsumesFor(spec.Paths.Paths["/"].Get) sort.Strings(consumes) assert.Equal(t, expected, consumes) produces := analyzer.ProducesFor(spec.Paths.Paths["/"].Get) sort.Strings(produces) assert.Equal(t, expected, produces) expected = []string{"application/json"} sort.Strings(expected) consumes = analyzer.ConsumesFor(spec.Paths.Paths["/items"].Get) sort.Strings(consumes) assert.Equal(t, expected, consumes) produces = analyzer.ProducesFor(spec.Paths.Paths["/items"].Get) sort.Strings(produces) assert.Equal(t, expected, produces) expectedSchemes := [][]SecurityRequirement{ { {Name: "oauth2", Scopes: []string{}}, {Name: "basic", Scopes: nil}, }, } schemes := analyzer.SecurityRequirementsFor(spec.Paths.Paths["/"].Get) assert.Equal(t, schemeNames(expectedSchemes), schemeNames(schemes)) securityDefinitions := analyzer.SecurityDefinitionsFor(spec.Paths.Paths["/"].Get) assert.Equal(t, *spec.SecurityDefinitions["basic"], securityDefinitions["basic"]) assert.Equal(t, *spec.SecurityDefinitions["oauth2"], securityDefinitions["oauth2"]) parameters := analyzer.ParamsFor("GET", "/") assert.Len(t, parameters, 2) operations := analyzer.OperationIDs() assert.Len(t, operations, 2) producers := analyzer.RequiredProduces() assert.Len(t, producers, 2) consumers := analyzer.RequiredConsumes() assert.Len(t, consumers, 2) authSchemes := analyzer.RequiredSecuritySchemes() assert.Len(t, authSchemes, 3) ops := analyzer.Operations() assert.Len(t, ops, 1) assert.Len(t, ops["GET"], 2) op, ok := analyzer.OperationFor("get", "/") assert.True(t, ok) assert.NotNil(t, op) op, ok = analyzer.OperationFor("delete", "/") assert.False(t, ok) assert.Nil(t, op) // check for duplicates in sec. requirements for operation pi.Get.Security = []map[string][]string{ {"oauth2": {}}, {"basic": nil}, {"basic": nil}, } spec = makeFixturepec(pi, pi2, formatParam) analyzer = New(spec) securityDefinitions = analyzer.SecurityDefinitionsFor(spec.Paths.Paths["/"].Get) assert.Len(t, securityDefinitions, 2) assert.Equal(t, *spec.SecurityDefinitions["basic"], securityDefinitions["basic"]) assert.Equal(t, *spec.SecurityDefinitions["oauth2"], securityDefinitions["oauth2"]) // check for empty (optional) in sec. requirements for operation pi.Get.Security = []map[string][]string{ {"oauth2": {}}, {"": nil}, {"basic": nil}, } spec = makeFixturepec(pi, pi2, formatParam) analyzer = New(spec) securityDefinitions = analyzer.SecurityDefinitionsFor(spec.Paths.Paths["/"].Get) assert.Len(t, securityDefinitions, 2) assert.Equal(t, *spec.SecurityDefinitions["basic"], securityDefinitions["basic"]) assert.Equal(t, *spec.SecurityDefinitions["oauth2"], securityDefinitions["oauth2"]) } func TestDefinitionAnalysis(t *testing.T) { doc, err := loadSpec(filepath.Join("fixtures", "definitions.yml")) if assert.NoError(t, err) { analyzer := New(doc) definitions := analyzer.allSchemas // parameters assertSchemaRefExists(t, definitions, "#/parameters/someParam/schema") assertSchemaRefExists(t, definitions, "#/paths/~1some~1where~1{id}/parameters/1/schema") assertSchemaRefExists(t, definitions, "#/paths/~1some~1where~1{id}/get/parameters/1/schema") // responses assertSchemaRefExists(t, definitions, "#/responses/someResponse/schema") assertSchemaRefExists(t, definitions, "#/paths/~1some~1where~1{id}/get/responses/default/schema") assertSchemaRefExists(t, definitions, "#/paths/~1some~1where~1{id}/get/responses/200/schema") // definitions assertSchemaRefExists(t, definitions, "#/definitions/tag") assertSchemaRefExists(t, definitions, "#/definitions/tag/properties/id") assertSchemaRefExists(t, definitions, "#/definitions/tag/properties/value") assertSchemaRefExists(t, definitions, "#/definitions/tag/definitions/category") assertSchemaRefExists(t, definitions, "#/definitions/tag/definitions/category/properties/id") assertSchemaRefExists(t, definitions, "#/definitions/tag/definitions/category/properties/value") assertSchemaRefExists(t, definitions, "#/definitions/withAdditionalProps") assertSchemaRefExists(t, definitions, "#/definitions/withAdditionalProps/additionalProperties") assertSchemaRefExists(t, definitions, "#/definitions/withAdditionalItems") assertSchemaRefExists(t, definitions, "#/definitions/withAdditionalItems/items/0") assertSchemaRefExists(t, definitions, "#/definitions/withAdditionalItems/items/1") assertSchemaRefExists(t, definitions, "#/definitions/withAdditionalItems/additionalItems") assertSchemaRefExists(t, definitions, "#/definitions/withNot") assertSchemaRefExists(t, definitions, "#/definitions/withNot/not") assertSchemaRefExists(t, definitions, "#/definitions/withAnyOf") assertSchemaRefExists(t, definitions, "#/definitions/withAnyOf/anyOf/0") assertSchemaRefExists(t, definitions, "#/definitions/withAnyOf/anyOf/1") assertSchemaRefExists(t, definitions, "#/definitions/withAllOf") assertSchemaRefExists(t, definitions, "#/definitions/withAllOf/allOf/0") assertSchemaRefExists(t, definitions, "#/definitions/withAllOf/allOf/1") assertSchemaRefExists(t, definitions, "#/definitions/withOneOf/oneOf/0") assertSchemaRefExists(t, definitions, "#/definitions/withOneOf/oneOf/1") allOfs := analyzer.allOfs assert.Len(t, allOfs, 1) assert.Contains(t, allOfs, "#/definitions/withAllOf") } } func loadSpec(path string) (*spec.Swagger, error) { spec.PathLoader = func(path string) (json.RawMessage, error) { ext := filepath.Ext(path) if ext == ".yml" || ext == ".yaml" { return fmts.YAMLDoc(path) } data, err := swag.LoadFromFileOrHTTP(path) if err != nil { return nil, err } return json.RawMessage(data), nil } data, err := fmts.YAMLDoc(path) if err != nil { return nil, err } var sw spec.Swagger if err := json.Unmarshal(data, &sw); err != nil { return nil, err } return &sw, nil } func TestReferenceAnalysis(t *testing.T) { doc, err := loadSpec(filepath.Join("fixtures", "references.yml")) if assert.NoError(t, err) { an := New(doc) definitions := an.references // parameters assertRefExists(t, definitions.parameters, "#/paths/~1some~1where~1{id}/parameters/0") assertRefExists(t, definitions.parameters, "#/paths/~1some~1where~1{id}/get/parameters/0") // path items assertRefExists(t, definitions.pathItems, "#/paths/~1other~1place") // responses assertRefExists(t, definitions.responses, "#/paths/~1some~1where~1{id}/get/responses/404") // definitions assertRefExists(t, definitions.schemas, "#/responses/notFound/schema") assertRefExists(t, definitions.schemas, "#/paths/~1some~1where~1{id}/get/responses/200/schema") assertRefExists(t, definitions.schemas, "#/definitions/tag/properties/audit") // items // Supported non-swagger 2.0 constructs ($ref in simple schema items) assertRefExists(t, definitions.allRefs, "#/paths/~1some~1where~1{id}/get/parameters/1/items") assertRefExists(t, definitions.allRefs, "#/paths/~1some~1where~1{id}/get/parameters/2/items") assertRefExists(t, definitions.allRefs, "#/paths/~1some~1where~1{id}/get/responses/default/headers/x-array-header/items") assert.Lenf(t, an.AllItemsReferences(), 3, "Expected 3 items references in this spec") assertRefExists(t, definitions.parameterItems, "#/paths/~1some~1where~1{id}/get/parameters/1/items") assertRefExists(t, definitions.parameterItems, "#/paths/~1some~1where~1{id}/get/parameters/2/items") assertRefExists(t, definitions.headerItems, "#/paths/~1some~1where~1{id}/get/responses/default/headers/x-array-header/items") } } func assertRefExists(t testing.TB, data map[string]spec.Ref, key string) bool { if _, ok := data[key]; !ok { return assert.Fail(t, fmt.Sprintf("expected %q to exist in the ref bag", key)) } return true } func assertSchemaRefExists(t testing.TB, data map[string]SchemaRef, key string) bool { if _, ok := data[key]; !ok { return assert.Fail(t, fmt.Sprintf("expected %q to exist in schema ref bag", key)) } return true } func TestPatternAnalysis(t *testing.T) { doc, err := loadSpec(filepath.Join("fixtures", "patterns.yml")) if assert.NoError(t, err) { an := New(doc) pt := an.patterns // parameters assertPattern(t, pt.parameters, "#/parameters/idParam", "a[A-Za-Z0-9]+") assertPattern(t, pt.parameters, "#/paths/~1some~1where~1{id}/parameters/1", "b[A-Za-z0-9]+") assertPattern(t, pt.parameters, "#/paths/~1some~1where~1{id}/get/parameters/0", "[abc][0-9]+") // responses assertPattern(t, pt.headers, "#/responses/notFound/headers/ContentLength", "[0-9]+") assertPattern(t, pt.headers, "#/paths/~1some~1where~1{id}/get/responses/200/headers/X-Request-Id", "d[A-Za-z0-9]+") // definitions assertPattern(t, pt.schemas, "#/paths/~1other~1place/post/parameters/0/schema/properties/value", "e[A-Za-z0-9]+") assertPattern(t, pt.schemas, "#/paths/~1other~1place/post/responses/200/schema/properties/data", "[0-9]+[abd]") assertPattern(t, pt.schemas, "#/definitions/named", "f[A-Za-z0-9]+") assertPattern(t, pt.schemas, "#/definitions/tag/properties/value", "g[A-Za-z0-9]+") // items assertPattern(t, pt.items, "#/paths/~1some~1where~1{id}/get/parameters/1/items", "c[A-Za-z0-9]+") assertPattern(t, pt.items, "#/paths/~1other~1place/post/responses/default/headers/Via/items", "[A-Za-z]+") // patternProperties (beyond Swagger 2.0) _, ok := an.spec.Definitions["withPatternProperties"] assert.True(t, ok) _, ok = an.allSchemas["#/definitions/withPatternProperties/patternProperties/^prop[0-9]+$"] assert.True(t, ok) } } func assertPattern(t testing.TB, data map[string]string, key, pattern string) bool { if assert.Contains(t, data, key) { return assert.Equal(t, pattern, data[key]) } return false } func panickerParamsAsMap() { s := prepareTestParamsInvalid("fixture-342.yaml") if s == nil { return } m := make(map[string]spec.Parameter) if pi, ok := s.spec.Paths.Paths["/fixture"]; ok { pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters s.paramsAsMap(pi.Parameters, m, nil) } } func panickerParamsAsMap2() { s := prepareTestParamsInvalid("fixture-342-2.yaml") if s == nil { return } m := make(map[string]spec.Parameter) if pi, ok := s.spec.Paths.Paths["/fixture"]; ok { pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters s.paramsAsMap(pi.Parameters, m, nil) } } func panickerParamsAsMap3() { s := prepareTestParamsInvalid("fixture-342-3.yaml") if s == nil { return } m := make(map[string]spec.Parameter) if pi, ok := s.spec.Paths.Paths["/fixture"]; ok { pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters s.paramsAsMap(pi.Parameters, m, nil) } } func TestAnalyzer_paramsAsMap(Pt *testing.T) { s := prepareTestParamsValid() if assert.NotNil(Pt, s) { m := make(map[string]spec.Parameter) pi, ok := s.spec.Paths.Paths["/items"] if assert.True(Pt, ok) { s.paramsAsMap(pi.Parameters, m, nil) assert.Len(Pt, m, 1) p, ok := m["query#Limit"] assert.True(Pt, ok) assert.Equal(Pt, p.Name, "limit") } } // An invalid spec, but passes this step (errors are figured out at a higher level) s = prepareTestParamsInvalid("fixture-1289-param.yaml") if assert.NotNil(Pt, s) { m := make(map[string]spec.Parameter) pi, ok := s.spec.Paths.Paths["/fixture"] if assert.True(Pt, ok) { pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters s.paramsAsMap(pi.Parameters, m, nil) assert.Len(Pt, m, 1) p, ok := m["body#DespicableMe"] assert.True(Pt, ok) assert.Equal(Pt, p.Name, "despicableMe") } } } func TestAnalyzer_paramsAsMapWithCallback(Pt *testing.T) { s := prepareTestParamsInvalid("fixture-342.yaml") if assert.NotNil(Pt, s) { // No bail out callback m := make(map[string]spec.Parameter) e := []string{} pi, ok := s.spec.Paths.Paths["/fixture"] if assert.True(Pt, ok) { pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters s.paramsAsMap(pi.Parameters, m, func(param spec.Parameter, err error) bool { //Pt.Logf("ERROR on %+v : %v", param, err) e = append(e, err.Error()) return true // Continue }) } assert.Contains(Pt, e, `resolved reference is not a parameter: "#/definitions/sample_info/properties/sid"`) assert.Contains(Pt, e, `invalid reference: "#/definitions/sample_info/properties/sids"`) // bail out callback m = make(map[string]spec.Parameter) e = []string{} pi, ok = s.spec.Paths.Paths["/fixture"] if assert.True(Pt, ok) { pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters s.paramsAsMap(pi.Parameters, m, func(param spec.Parameter, err error) bool { //Pt.Logf("ERROR on %+v : %v", param, err) e = append(e, err.Error()) return false // Bail out }) } // We got one then bail out assert.Len(Pt, e, 1) } // Bail out after ref failure: exercising another path s = prepareTestParamsInvalid("fixture-342-2.yaml") if assert.NotNil(Pt, s) { // bail out callback m := make(map[string]spec.Parameter) e := []string{} pi, ok := s.spec.Paths.Paths["/fixture"] if assert.True(Pt, ok) { pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters s.paramsAsMap(pi.Parameters, m, func(param spec.Parameter, err error) bool { //Pt.Logf("ERROR on %+v : %v", param, err) e = append(e, err.Error()) return false // Bail out }) } // We got one then bail out assert.Len(Pt, e, 1) } // Bail out after ref failure: exercising another path s = prepareTestParamsInvalid("fixture-342-3.yaml") if assert.NotNil(Pt, s) { // bail out callback m := make(map[string]spec.Parameter) e := []string{} pi, ok := s.spec.Paths.Paths["/fixture"] if assert.True(Pt, ok) { pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters s.paramsAsMap(pi.Parameters, m, func(param spec.Parameter, err error) bool { //Pt.Logf("ERROR on %+v : %v", param, err) e = append(e, err.Error()) return false // Bail out }) } // We got one then bail out assert.Len(Pt, e, 1) } } func TestAnalyzer_paramsAsMap_Panic(Pt *testing.T) { assert.Panics(Pt, panickerParamsAsMap) // Specifically on invalid resolved type assert.Panics(Pt, panickerParamsAsMap2) // Specifically on invalid ref assert.Panics(Pt, panickerParamsAsMap3) } func TestAnalyzer_SafeParamsFor(Pt *testing.T) { s := prepareTestParamsInvalid("fixture-342.yaml") if assert.NotNil(Pt, s) { e := []string{} pi, ok := s.spec.Paths.Paths["/fixture"] if assert.True(Pt, ok) { pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters for range s.SafeParamsFor("Get", "/fixture", func(param spec.Parameter, err error) bool { e = append(e, err.Error()) return true // Continue }) { assert.Fail(Pt, "There should be no safe parameter in this testcase") } } assert.Contains(Pt, e, `resolved reference is not a parameter: "#/definitions/sample_info/properties/sid"`) assert.Contains(Pt, e, `invalid reference: "#/definitions/sample_info/properties/sids"`) } } func panickerParamsFor() { s := prepareTestParamsInvalid("fixture-342.yaml") pi, ok := s.spec.Paths.Paths["/fixture"] if ok { pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters s.ParamsFor("Get", "/fixture") } } func TestAnalyzer_ParamsFor(Pt *testing.T) { // Valid example s := prepareTestParamsValid() if assert.NotNil(Pt, s) { params := s.ParamsFor("Get", "/items") assert.True(Pt, len(params) > 0) } // Invalid example assert.Panics(Pt, panickerParamsFor) } func TestAnalyzer_SafeParametersFor(Pt *testing.T) { s := prepareTestParamsInvalid("fixture-342.yaml") if assert.NotNil(Pt, s) { e := []string{} pi, ok := s.spec.Paths.Paths["/fixture"] if assert.True(Pt, ok) { pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters for range s.SafeParametersFor("fixtureOp", func(param spec.Parameter, err error) bool { e = append(e, err.Error()) return true // Continue }) { assert.Fail(Pt, "There should be no safe parameter in this testcase") } } assert.Contains(Pt, e, `resolved reference is not a parameter: "#/definitions/sample_info/properties/sid"`) assert.Contains(Pt, e, `invalid reference: "#/definitions/sample_info/properties/sids"`) } } func panickerParametersFor() { s := prepareTestParamsInvalid("fixture-342.yaml") if s == nil { return } pi, ok := s.spec.Paths.Paths["/fixture"] if ok { pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters //func (s *Spec) ParametersFor(operationID string) []spec.Parameter { s.ParametersFor("fixtureOp") } } func TestAnalyzer_ParametersFor(Pt *testing.T) { // Valid example s := prepareTestParamsValid() params := s.ParamsFor("Get", "/items") assert.True(Pt, len(params) > 0) // Invalid example assert.Panics(Pt, panickerParametersFor) } func prepareTestParamsValid() *Spec { formatParam := spec.QueryParam("format").Typed("string", "") limitParam := spec.QueryParam("limit").Typed("integer", "int32") limitParam.Extensions = spec.Extensions(map[string]interface{}{}) limitParam.Extensions.Add("go-name", "Limit") skipParam := spec.QueryParam("skip").Typed("integer", "int32") pi := spec.PathItem{} pi.Parameters = []spec.Parameter{*limitParam} op := &spec.Operation{} op.Consumes = []string{"application/x-yaml"} op.Produces = []string{"application/x-yaml"} op.Security = []map[string][]string{ {"oauth2": {}}, {"basic": nil}, } op.ID = "someOperation" op.Parameters = []spec.Parameter{*skipParam} pi.Get = op pi2 := spec.PathItem{} pi2.Parameters = []spec.Parameter{*limitParam} op2 := &spec.Operation{} op2.ID = "anotherOperation" op2.Parameters = []spec.Parameter{*skipParam} pi2.Get = op2 spec := makeFixturepec(pi, pi2, formatParam) analyzer := New(spec) return analyzer } func prepareTestParamsInvalid(fixture string) *Spec { cwd, _ := os.Getwd() bp := filepath.Join(cwd, "fixtures", fixture) spec, err := loadSpec(bp) if err != nil { log.Printf("Warning: fixture %s could not be loaded: %v", fixture, err) return nil } analyzer := New(spec) return analyzer } func TestSecurityDefinitionsFor(t *testing.T) { spec := prepareTestParamsAuth() pi1 := spec.spec.Paths.Paths["/"].Get pi2 := spec.spec.Paths.Paths["/items"].Get defs1 := spec.SecurityDefinitionsFor(pi1) require.Contains(t, defs1, "oauth2") require.Contains(t, defs1, "basic") require.NotContains(t, defs1, "apiKey") defs2 := spec.SecurityDefinitionsFor(pi2) require.Contains(t, defs2, "oauth2") require.Contains(t, defs2, "basic") require.Contains(t, defs2, "apiKey") } func TestSecurityRequirements(t *testing.T) { spec := prepareTestParamsAuth() pi1 := spec.spec.Paths.Paths["/"].Get pi2 := spec.spec.Paths.Paths["/items"].Get scopes := []string{"the-scope"} reqs1 := spec.SecurityRequirementsFor(pi1) require.Len(t, reqs1, 2) require.Len(t, reqs1[0], 1) require.Equal(t, reqs1[0][0].Name, "oauth2") require.Equal(t, reqs1[0][0].Scopes, scopes) require.Len(t, reqs1[1], 1) require.Equal(t, reqs1[1][0].Name, "basic") require.Empty(t, reqs1[1][0].Scopes) reqs2 := spec.SecurityRequirementsFor(pi2) require.Len(t, reqs2, 3) require.Len(t, reqs2[0], 1) require.Equal(t, reqs2[0][0].Name, "oauth2") require.Equal(t, reqs2[0][0].Scopes, scopes) require.Len(t, reqs2[1], 1) require.Empty(t, reqs2[1][0].Name) require.Empty(t, reqs2[1][0].Scopes) require.Len(t, reqs2[2], 2) // //require.Equal(t, reqs2[2][0].Name, "basic") require.Contains(t, reqs2[2], SecurityRequirement{Name: "basic", Scopes: []string{}}) require.Empty(t, reqs2[2][0].Scopes) //require.Equal(t, reqs2[2][1].Name, "apiKey") require.Contains(t, reqs2[2], SecurityRequirement{Name: "apiKey", Scopes: []string{}}) require.Empty(t, reqs2[2][1].Scopes) } func TestSecurityRequirementsDefinitions(t *testing.T) { spec := prepareTestParamsAuth() pi1 := spec.spec.Paths.Paths["/"].Get pi2 := spec.spec.Paths.Paths["/items"].Get reqs1 := spec.SecurityRequirementsFor(pi1) defs11 := spec.SecurityDefinitionsForRequirements(reqs1[0]) require.Contains(t, defs11, "oauth2") defs12 := spec.SecurityDefinitionsForRequirements(reqs1[1]) require.Contains(t, defs12, "basic") require.NotContains(t, defs12, "apiKey") reqs2 := spec.SecurityRequirementsFor(pi2) defs21 := spec.SecurityDefinitionsForRequirements(reqs2[0]) require.Len(t, defs21, 1) require.Contains(t, defs21, "oauth2") require.NotContains(t, defs21, "basic") require.NotContains(t, defs21, "apiKey") defs22 := spec.SecurityDefinitionsForRequirements(reqs2[1]) require.NotNil(t, defs22) require.Empty(t, defs22) defs23 := spec.SecurityDefinitionsForRequirements(reqs2[2]) require.Len(t, defs23, 2) require.NotContains(t, defs23, "oauth2") require.Contains(t, defs23, "basic") require.Contains(t, defs23, "apiKey") } func prepareTestParamsAuth() *Spec { formatParam := spec.QueryParam("format").Typed("string", "") limitParam := spec.QueryParam("limit").Typed("integer", "int32") limitParam.Extensions = spec.Extensions(map[string]interface{}{}) limitParam.Extensions.Add("go-name", "Limit") skipParam := spec.QueryParam("skip").Typed("integer", "int32") pi := spec.PathItem{} pi.Parameters = []spec.Parameter{*limitParam} op := &spec.Operation{} op.Consumes = []string{"application/x-yaml"} op.Produces = []string{"application/x-yaml"} op.Security = []map[string][]string{ {"oauth2": {"the-scope"}}, {"basic": nil}, } op.ID = "someOperation" op.Parameters = []spec.Parameter{*skipParam} pi.Get = op pi2 := spec.PathItem{} pi2.Parameters = []spec.Parameter{*limitParam} op2 := &spec.Operation{} op2.ID = "anotherOperation" op2.Security = []map[string][]string{ {"oauth2": {"the-scope"}}, {}, { "basic": {}, "apiKey": {}, }, } op2.Parameters = []spec.Parameter{*skipParam} pi2.Get = op2 oauth := spec.OAuth2AccessToken("http://authorize.com", "http://token.com") oauth.AddScope("the-scope", "the scope gives access to ...") spec := &spec.Swagger{ SwaggerProps: spec.SwaggerProps{ Consumes: []string{"application/json"}, Produces: []string{"application/json"}, Security: []map[string][]string{ {"apikey": nil}, }, SecurityDefinitions: map[string]*spec.SecurityScheme{ "basic": spec.BasicAuth(), "apiKey": spec.APIKeyAuth("api_key", "query"), "oauth2": oauth, }, Parameters: map[string]spec.Parameter{"format": *formatParam}, Paths: &spec.Paths{ Paths: map[string]spec.PathItem{ "/": pi, "/items": pi2, }, }, }, } analyzer := New(spec) return analyzer } func TestMoreParamAnalysis(t *testing.T) { cwd, _ := os.Getwd() bp := filepath.Join(cwd, "fixtures", "parameters", "fixture-parameters.yaml") sp, err := loadSpec(bp) if !assert.NoError(t, err) { t.FailNow() return } an := New(sp) res := an.AllPatterns() assert.Lenf(t, res, 6, "Expected 6 patterns in this spec") res = an.SchemaPatterns() assert.Lenf(t, res, 1, "Expected 1 schema pattern in this spec") res = an.HeaderPatterns() assert.Lenf(t, res, 2, "Expected 2 header pattern in this spec") res = an.ItemsPatterns() assert.Lenf(t, res, 2, "Expected 2 items pattern in this spec") res = an.ParameterPatterns() assert.Lenf(t, res, 1, "Expected 1 simple param pattern in this spec") refs := an.AllRefs() assert.Lenf(t, refs, 10, "Expected 10 reference usage in this spec") references := an.AllReferences() assert.Lenf(t, references, 14, "Expected 14 reference usage in this spec") references = an.AllItemsReferences() assert.Lenf(t, references, 0, "Expected 0 items reference in this spec") references = an.AllPathItemReferences() assert.Lenf(t, references, 1, "Expected 1 pathItem reference in this spec") references = an.AllResponseReferences() assert.Lenf(t, references, 3, "Expected 3 response references in this spec") references = an.AllParameterReferences() assert.Lenf(t, references, 6, "Expected 6 parameter references in this spec") schemaRefs := an.AllDefinitions() assert.Lenf(t, schemaRefs, 14, "Expected 14 schema definitions in this spec") //for _, refs := range schemaRefs { // t.Logf("Schema Ref: %s (%s)", refs.Name, refs.Ref.String()) //} schemaRefs = an.SchemasWithAllOf() assert.Lenf(t, schemaRefs, 1, "Expected 1 schema with AllOf definition in this spec") method, path, op, found := an.OperationForName("postSomeWhere") assert.Equal(t, "POST", method) assert.Equal(t, "/some/where", path) if assert.NotNil(t, op) && assert.True(t, found) { sec := an.SecurityRequirementsFor(op) assert.Nil(t, sec) secScheme := an.SecurityDefinitionsFor(op) assert.Nil(t, secScheme) bag := an.ParametersFor("postSomeWhere") assert.Lenf(t, bag, 6, "Expected 6 parameters for this operation") } method, path, op, found = an.OperationForName("notFound") assert.Equal(t, "", method) assert.Equal(t, "", path) assert.Nil(t, op) assert.False(t, found) // does not take ops under pathItem $ref ops := an.OperationMethodPaths() assert.Lenf(t, ops, 3, "Expected 3 ops") ops = an.OperationIDs() assert.Lenf(t, ops, 3, "Expected 3 ops") assert.Contains(t, ops, "postSomeWhere") assert.Contains(t, ops, "GET /some/where/else") assert.Contains(t, ops, "GET /some/where") } func Test_EdgeCases(t *testing.T) { // check return values are consistent in some nil/empty edge cases sp := Spec{} res1 := sp.AllPaths() assert.Nil(t, res1) res2 := sp.OperationIDs() assert.Nil(t, res2) res3 := sp.OperationMethodPaths() assert.Nil(t, res3) res4 := sp.structMapKeys(nil) assert.Nil(t, res4) res5 := sp.structMapKeys(make(map[string]struct{}, 10)) assert.Nil(t, res5) // check AllRefs() skips empty $refs sp.references.allRefs = make(map[string]spec.Ref, 3) for i := 0; i < 3; i++ { sp.references.allRefs["ref"+strconv.Itoa(i)] = spec.Ref{} } assert.Len(t, sp.references.allRefs, 3) res6 := sp.AllRefs() assert.Len(t, res6, 0) // check AllRefs() skips duplicate $refs sp.references.allRefs["refToOne"] = spec.MustCreateRef("#/ref1") sp.references.allRefs["refToOneAgain"] = spec.MustCreateRef("#/ref1") res7 := sp.AllRefs() assert.NotNil(t, res7) assert.Len(t, res7, 1) } golang-github-go-openapi-analysis-0.15.0/debug.go000066400000000000000000000024741332100444700216230ustar00rootroot00000000000000// Copyright 2015 go-swagger maintainers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package analysis import ( "fmt" "log" "os" "path/filepath" "runtime" ) var ( // Debug is true when the SWAGGER_DEBUG env var is not empty. // It enables a more verbose logging of the spec analyzer. Debug = os.Getenv("SWAGGER_DEBUG") != "" // analysisLogger is a debug logger for this package analysisLogger *log.Logger ) func init() { debugOptions() } func debugOptions() { analysisLogger = log.New(os.Stdout, "analysis:", log.LstdFlags) } func debugLog(msg string, args ...interface{}) { // A private, trivial trace logger, based on go-openapi/spec/expander.go:debugLog() if Debug { _, file1, pos1, _ := runtime.Caller(1) analysisLogger.Printf("%s:%d: %s", filepath.Base(file1), pos1, fmt.Sprintf(msg, args...)) } } golang-github-go-openapi-analysis-0.15.0/debug_test.go000066400000000000000000000024641332100444700226610ustar00rootroot00000000000000// Copyright 2015 go-swagger maintainers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package analysis import ( "io/ioutil" "os" "sync" "testing" "github.com/stretchr/testify/assert" ) var ( logMutex = &sync.Mutex{} ) func TestDebug(t *testing.T) { tmpFile, _ := ioutil.TempFile("", "debug-test") tmpName := tmpFile.Name() defer func() { Debug = false // mutex for -race logMutex.Unlock() os.Remove(tmpName) }() // mutex for -race logMutex.Lock() Debug = true debugOptions() defer func() { analysisLogger.SetOutput(os.Stdout) }() analysisLogger.SetOutput(tmpFile) debugLog("A debug") Debug = false tmpFile.Close() flushed, _ := os.Open(tmpName) buf := make([]byte, 500) _, _ = flushed.Read(buf) analysisLogger.SetOutput(os.Stdout) assert.Contains(t, string(buf), "A debug") } golang-github-go-openapi-analysis-0.15.0/doc.go000066400000000000000000000031061332100444700212730ustar00rootroot00000000000000// Copyright 2015 go-swagger maintainers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package analysis provides methods to work with a Swagger specification document from package go-openapi/spec. Analyzing a specification An analysed specification object (type Spec) provides methods to work with swagger definition. Flattening or expanding a specification Flattening a specification bundles all remote $ref in the main spec document. Depending on flattening options, additional preprocessing may take place: - full flattening: replacing all inline complex constructs by a named entry in #/definitions - expand: replace all $ref's in the document by their expanded content Merging several specifications Mixin several specifications merges all Swagger constructs, and warns about found conflicts. Fixing a specification Unmarshalling a specification with golang json unmarshalling may lead to some unwanted result on present but empty fields. Analyzing a Swagger schema Swagger schemas are analyzed to determine their complexity and qualify their content. */ package analysis golang-github-go-openapi-analysis-0.15.0/doc_test.go000066400000000000000000000031061332100444700223320ustar00rootroot00000000000000// Copyright 2015 go-swagger maintainers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package analysis_test import ( "fmt" "github.com/go-openapi/analysis" // This package "github.com/go-openapi/loads" // Spec loading ) func ExampleSpec() { // Example with spec file in this repo path := "fixtures/flatten.yml" doc, err := loads.Spec(path) // Load spec from file if err == nil { an := analysis.New(doc.Spec()) // Analyze spec paths := an.AllPaths() fmt.Printf("This spec contains %d paths", len(paths)) } // Output: This spec contains 2 paths } func ExampleFlatten() { // Example with spec file in this repo path := "fixtures/flatten.yml" doc, err := loads.Spec(path) // Load spec from file if err == nil { an := analysis.New(doc.Spec()) // Analyze spec // flatten the specification in doc erf := analysis.Flatten(analysis.FlattenOpts{Spec: an, BasePath: path}) if erf == nil { fmt.Printf("Specification doc flattened") } // .. the analyzed spec has been updated and may be now used with the reworked spec } // Output: Specification doc flattened } golang-github-go-openapi-analysis-0.15.0/fixer.go000066400000000000000000000044501332100444700216460ustar00rootroot00000000000000// Copyright 2015 go-swagger maintainers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package analysis import "github.com/go-openapi/spec" // FixEmptyResponseDescriptions replaces empty ("") response // descriptions in the input with "(empty)" to ensure that the // resulting Swagger is stays valid. The problem appears to arise // from reading in valid specs that have a explicit response // description of "" (valid, response.description is required), but // due to zero values being omitted upon re-serializing (omitempty) we // lose them unless we stick some chars in there. func FixEmptyResponseDescriptions(s *spec.Swagger) { if s.Paths != nil { for _, v := range s.Paths.Paths { if v.Get != nil { FixEmptyDescs(v.Get.Responses) } if v.Put != nil { FixEmptyDescs(v.Put.Responses) } if v.Post != nil { FixEmptyDescs(v.Post.Responses) } if v.Delete != nil { FixEmptyDescs(v.Delete.Responses) } if v.Options != nil { FixEmptyDescs(v.Options.Responses) } if v.Head != nil { FixEmptyDescs(v.Head.Responses) } if v.Patch != nil { FixEmptyDescs(v.Patch.Responses) } } } for k, v := range s.Responses { FixEmptyDesc(&v) s.Responses[k] = v } } // FixEmptyDescs adds "(empty)" as the description for any Response in // the given Responses object that doesn't already have one. func FixEmptyDescs(rs *spec.Responses) { FixEmptyDesc(rs.Default) for k, v := range rs.StatusCodeResponses { FixEmptyDesc(&v) rs.StatusCodeResponses[k] = v } } // FixEmptyDesc adds "(empty)" as the description to the given // Response object if it doesn't already have one and isn't a // ref. No-op on nil input. func FixEmptyDesc(rs *spec.Response) { if rs == nil || rs.Description != "" || rs.Ref.Ref.GetURL() != nil { return } rs.Description = "(empty)" } golang-github-go-openapi-analysis-0.15.0/fixer_test.go000066400000000000000000000063041332100444700227050ustar00rootroot00000000000000// Copyright 2015 go-swagger maintainers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package analysis import ( "path/filepath" "strconv" "testing" "github.com/go-openapi/spec" "github.com/stretchr/testify/assert" ) func Test_FixEmptyResponseDescriptions(t *testing.T) { bp := filepath.Join("fixtures", "fixer", "fixer.yaml") sp := loadOrFail(t, bp) FixEmptyResponseDescriptions(sp) for path, pathItem := range sp.Paths.Paths { if !assert.NotNil(t, pathItem, "expected a fixture with all path items provided in: %s", path) { continue } if path == "/noDesc" { // scope for fixed descriptions assertAllVerbs(t, pathItem, true) } else { // scope for unchanged descriptions assertAllVerbs(t, pathItem, false) } } for r, resp := range sp.Responses { assert.Truef(t, assertResponse(t, "/responses/"+r, &resp, true), "expected a fixed empty description in response %s", r) } } func assertAllVerbs(t *testing.T, pathItem spec.PathItem, isEmpty bool) { msg := "expected %s description for %s" var mode string if isEmpty { mode = "a fixed empty" } else { mode = "an unmodified" } assert.Truef(t, assertResponseInOperation(t, pathItem.Get, isEmpty), msg, mode, "GET") assert.Truef(t, assertResponseInOperation(t, pathItem.Put, isEmpty), msg, mode, "PUT") assert.Truef(t, assertResponseInOperation(t, pathItem.Post, isEmpty), msg, mode, "POST") assert.Truef(t, assertResponseInOperation(t, pathItem.Delete, isEmpty), msg, mode, "DELETE") assert.Truef(t, assertResponseInOperation(t, pathItem.Options, isEmpty), msg, mode, "OPTIONS") assert.Truef(t, assertResponseInOperation(t, pathItem.Patch, isEmpty), msg, mode, "PATCH") assert.Truef(t, assertResponseInOperation(t, pathItem.Head, isEmpty), msg, mode, "HEAD") } func assertResponseInOperation(t *testing.T, op *spec.Operation, isEmpty bool) (result bool) { result = true if op == nil { t.Fatalf("expected a fixture with all REST verbs set") } if op.Responses != nil { if op.Responses.Default != nil { assert.Truef(t, assertResponse(t, "default", op.Responses.Default, isEmpty), "unexpected description in response %s for operation", "default") } for code, resp := range op.Responses.StatusCodeResponses { assert.Truef(t, assertResponse(t, strconv.Itoa(code), &resp, isEmpty), "unexpected description in response %d for operation", code) } } return } func assertResponse(t *testing.T, path string, resp *spec.Response, isEmpty bool) bool { var expected string if isEmpty { expected = "(empty)" } else { expected = "my description" } if resp.Ref.String() != "" { expected = "" } if !assert.Equalf(t, expected, resp.Description, "unexpected description for resp. %s", path) { return false } return true } golang-github-go-openapi-analysis-0.15.0/fixtures/000077500000000000000000000000001332100444700220505ustar00rootroot00000000000000golang-github-go-openapi-analysis-0.15.0/fixtures/allOf.yml000066400000000000000000000013561332100444700236350ustar00rootroot00000000000000--- swagger: "2.0" info: version: "0.1.0" title: allOf analysis paths: "/some/where/{id}": parameters: - name: id in: path type: integer format: int32 - name: bodyId in: body schema: type: object get: parameters: - name: limit in: query type: integer format: int32 required: false - name: body in: body schema: type: object responses: default: schema: type: object 200: schema: type: object definitions: tag: type: object properties: id: type: integer format: int64 value: type: string golang-github-go-openapi-analysis-0.15.0/fixtures/bar-crud.yml000066400000000000000000000070501332100444700242740ustar00rootroot00000000000000--- swagger: '2.0' info: title: bar CRUD API version: 4.2.0 schemes: - http basePath: /api consumes: - application/json produces: - application/json paths: /common: get: operationId: commonGet summary: here to test path collisons responses: '200': description: OK schema: $ref: "#/definitions/bar" /bars: post: operationId: create summary: Create a new bar parameters: - name: info in: body schema: $ref: "#/definitions/bar" responses: '201': description: created schema: $ref: "#/definitions/barId" default: description: error schema: $ref: "#/definitions/error" /bars/{barid}: get: operationId: get summary: Get a bar by id parameters: - $ref: "#/parameters/barid" responses: '200': description: OK schema: $ref: "#/definitions/bar" '401': $ref: "#/responses/401" '404': $ref: "#/responses/404" default: description: error schema: $ref: "#/definitions/error" delete: operationId: delete summary: delete a bar by id parameters: - name: barid in: path required: true type: string responses: '200': description: OK '401': description: unauthorized schema: $ref: "#/definitions/error" '404': description: resource not found schema: $ref: "#/definitions/error" default: description: error schema: $ref: "#/definitions/error" post: operationId: update summary: update a bar by id parameters: - name: barid in: path required: true type: string - name: info in: body schema: $ref: "#/definitions/bar" responses: '200': description: OK '401': description: unauthorized schema: $ref: "#/definitions/error" '404': description: resource not found schema: $ref: "#/definitions/error" default: description: error schema: $ref: "#/definitions/error" definitions: common: type: object required: - id properties: id: type: string format: string minLength: 1 bar: type: object required: - name - description properties: id: type: string format: string readOnly: true name: type: string format: string minLength: 1 description: type: string format: string minLength: 1 barId: type: object required: - id properties: id: type: string format: string minLength: 1 error: type: object required: - message properties: code: type: string format: string message: type: string fields: type: string parameters: common: name: common in: query type: string barid: name: barid in: path required: true type: string responses: 401: description: bar unauthorized schema: $ref: "#/definitions/error" 404: description: bar resource not found schema: $ref: "#/definitions/error" golang-github-go-openapi-analysis-0.15.0/fixtures/bugs/000077500000000000000000000000001332100444700230105ustar00rootroot00000000000000golang-github-go-openapi-analysis-0.15.0/fixtures/bugs/1602/000077500000000000000000000000001332100444700234005ustar00rootroot00000000000000golang-github-go-openapi-analysis-0.15.0/fixtures/bugs/1602/fixture-1602-1.yaml000066400000000000000000000036121332100444700265000ustar00rootroot00000000000000--- swagger: "2.0" schemes: - "http" - "https" basePath: "/v1.24" info: title: "derived from Pouch Engine API" version: "1.24" description: A variation on issue go-swagger/go-swagger#1609 paths: /_ping: get: parameters: - $ref: '#/parameters/bodyWithRef' responses: 200: description: "no error" schema: type: "string" example: "OK" 301: description: direct ref schema: $ref: "#/definitions/Error" 401: $ref: "#/responses/401ErrorResponse" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" definitions: Error: type: "object" properties: message: type: string parameters: bodyWithRef: name: bodyWithRefParam in: body schema: $ref: "#/definitions/Error" nobodyWithRef: name: bodyWithRefParam in: query type: integer #required: true anotherBodyWithRef: name: anotherBodyWithRefParam in: body schema: $ref: "#/definitions/Error" funnyParam: name: funnyParam in: body schema: $ref: "#/responses/500ErrorResponse/schema" responses: 401ErrorResponse: description: An unexpected 401 error occurred. schema: $ref: "#/definitions/Error" 404ErrorResponse: description: An unexpected 404 error occurred. schema: $ref: "#/definitions/Error" 500ErrorResponse: description: An unexpected server error occurred. schema: $ref: "#/definitions/Error" funnyResponse: description: An unexpected server error occurred. schema: $ref: "#/responses/401ErrorResponse/schema" foulResponse: description: An unexpected server error occurred. schema: $ref: "#/responses/noSchemaResponse" noSchemaResponse: description: An unexpected server error occurred. golang-github-go-openapi-analysis-0.15.0/fixtures/bugs/1602/fixture-1602-2.yaml000066400000000000000000000037361332100444700265100ustar00rootroot00000000000000--- swagger: "2.0" schemes: - "http" - "https" basePath: "/v1.24" info: title: "derived from Pouch Engine API" version: "1.24" description: A variation on issue go-swagger/go-swagger#1609 paths: /_pong: get: parameters: - $ref: '#/parameters/anotherBodyWithRef' responses: 200: description: "no error" schema: type: "string" example: "OK" 401: $ref: "#/responses/401ErrorResponse" 404: description: direct ref schema: $ref: "#/definitions/Error" 500: $ref: "#/responses/500ErrorResponse" 501: description: faulty ref with wrong object type schema: $ref: "#/responses/500ErrorResponse" definitions: Error: type: "object" properties: message: type: string parameters: bodyWithRef: name: bodyWithRefParam in: body schema: $ref: "#/definitions/Error" nobodyWithRef: name: bodyWithRefParam in: query type: integer #required: true anotherBodyWithRef: name: anotherBodyWithRefParam in: body schema: $ref: "#/definitions/Error" funnyParam: name: funnyParam in: body schema: $ref: "#/responses/500ErrorResponse/schema" responses: 401ErrorResponse: description: An unexpected 401 error occurred. schema: $ref: "#/definitions/Error" 404ErrorResponse: description: An unexpected 404 error occurred. schema: $ref: "#/definitions/Error" 500ErrorResponse: description: An unexpected server error occurred. schema: $ref: "#/definitions/Error" funnyResponse: description: An unexpected server error occurred. schema: $ref: "#/responses/401ErrorResponse/schema" foulResponse: description: An unexpected server error occurred. schema: $ref: "#/responses/noSchemaResponse" noSchemaResponse: description: An unexpected server error occurred. golang-github-go-openapi-analysis-0.15.0/fixtures/bugs/1602/fixture-1602-3.yaml000066400000000000000000000035271332100444700265070ustar00rootroot00000000000000--- swagger: "2.0" schemes: - "http" - "https" basePath: "/v1.24" info: title: "derived from Pouch Engine API" version: "1.24" description: A variation on issue go-swagger/go-swagger#1609 paths: /_pang: get: parameters: - $ref: '#/parameters/funnyParam' responses: 200: description: "no error" schema: type: "string" example: "OK" 401: $ref: "#/responses/401ErrorResponse" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" 501: $ref: "#/responses/funnyResponse" definitions: Error: type: "object" properties: message: type: string parameters: bodyWithRef: name: bodyWithRefParam in: body schema: $ref: "#/definitions/Error" nobodyWithRef: name: bodyWithRefParam in: query type: integer #required: true anotherBodyWithRef: name: anotherBodyWithRefParam in: body schema: $ref: "#/definitions/Error" funnyParam: name: funnyParam in: body schema: $ref: "#/responses/500ErrorResponse/schema" responses: 401ErrorResponse: description: An unexpected 401 error occurred. schema: $ref: "#/definitions/Error" 404ErrorResponse: description: An unexpected 404 error occurred. schema: $ref: "#/definitions/Error" 500ErrorResponse: description: An unexpected server error occurred. schema: $ref: "#/definitions/Error" funnyResponse: description: An unexpected server error occurred. schema: $ref: "#/responses/401ErrorResponse/schema" foulResponse: description: An unexpected server error occurred. schema: $ref: "#/responses/noSchemaResponse" noSchemaResponse: description: An unexpected server error occurred. golang-github-go-openapi-analysis-0.15.0/fixtures/bugs/1602/fixture-1602-4.yaml000066400000000000000000000035551332100444700265110ustar00rootroot00000000000000--- swagger: "2.0" schemes: - "http" - "https" basePath: "/v1.24" info: title: "derived from Pouch Engine API" version: "1.24" description: A variation on issue go-swagger/go-swagger#1609 paths: /_peng: get: parameters: - name: foulBodyRef in: body schema: # <-- foul $ref $ref: '#/parameters/bodyWithRef' responses: 200: description: "no error" schema: type: "string" example: "OK" 401: $ref: "#/responses/401ErrorResponse" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" definitions: Error: type: "object" properties: message: type: string parameters: bodyWithRef: name: bodyWithRefParam in: body schema: $ref: "#/definitions/Error" nobodyWithRef: name: bodyWithRefParam in: query type: integer #required: true anotherBodyWithRef: name: anotherBodyWithRefParam in: body schema: $ref: "#/definitions/Error" funnyParam: name: funnyParam in: body schema: $ref: "#/responses/500ErrorResponse/schema" responses: 401ErrorResponse: description: An unexpected 401 error occurred. schema: $ref: "#/definitions/Error" 404ErrorResponse: description: An unexpected 404 error occurred. schema: $ref: "#/definitions/Error" 500ErrorResponse: description: An unexpected server error occurred. schema: $ref: "#/definitions/Error" funnyResponse: description: An unexpected server error occurred. schema: $ref: "#/responses/401ErrorResponse/schema" foulResponse: description: An unexpected server error occurred. schema: $ref: "#/responses/noSchemaResponse" noSchemaResponse: description: An unexpected server error occurred. golang-github-go-openapi-analysis-0.15.0/fixtures/bugs/1602/fixture-1602-5.yaml000066400000000000000000000036521332100444700265100ustar00rootroot00000000000000--- swagger: "2.0" schemes: - "http" - "https" basePath: "/v1.24" info: title: "derived from Pouch Engine API" version: "1.24" description: A variation on issue go-swagger/go-swagger#1609 paths: /_pung: get: parameters: - name: foulerBodyRef in: body schema: # <-- foul $ref $ref: '#/parameters/nobodyWithRef' responses: 200: description: "no error" schema: type: "string" example: "OK" 401: $ref: "#/responses/401ErrorResponse" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" 501: $ref: "#/responses/foulResponse" definitions: Error: type: "object" properties: message: type: string parameters: bodyWithRef: name: bodyWithRefParam in: body schema: $ref: "#/definitions/Error" nobodyWithRef: name: bodyWithRefParam in: query type: integer #required: true anotherBodyWithRef: name: anotherBodyWithRefParam in: body schema: $ref: "#/definitions/Error" funnyParam: name: funnyParam in: body schema: $ref: "#/responses/500ErrorResponse/schema" responses: 401ErrorResponse: description: An unexpected 401 error occurred. schema: $ref: "#/definitions/Error" 404ErrorResponse: description: An unexpected 404 error occurred. schema: $ref: "#/definitions/Error" 500ErrorResponse: description: An unexpected server error occurred. schema: $ref: "#/definitions/Error" funnyResponse: description: An unexpected server error occurred. schema: $ref: "#/responses/401ErrorResponse/schema" foulResponse: description: An unexpected server error occurred. schema: $ref: "#/responses/noSchemaResponse" noSchemaResponse: description: An unexpected server error occurred. golang-github-go-openapi-analysis-0.15.0/fixtures/bugs/1602/fixture-1602-full.yaml000066400000000000000000003423531332100444700273120ustar00rootroot00000000000000--- swagger: "2.0" schemes: - "http" - "https" produces: - "application/json" - "text/plain" consumes: - "application/json" - "text/plain" basePath: "/v1.24" info: title: "Pouch Engine API" version: "1.24" description: | API is an HTTP API served by Pouch Engine. produces: - "application/json" paths: /_ping: get: summary: "" description: "" responses: 200: description: "no error" schema: type: "string" example: "OK" 500: $ref: "#/responses/500ErrorResponse" /version: get: summary: "Get Pouchd version" description: "" responses: 200: schema: $ref: '#/definitions/SystemVersion' description: "no error" 500: $ref: "#/responses/500ErrorResponse" /info: get: summary: "Get System information" description: "" responses: 200: schema: $ref: '#/definitions/SystemInfo' description: "no error" 500: $ref: "#/responses/500ErrorResponse" /auth: post: summary: "Check auth configuration" description: "Validate credentials for a registry and, if available, get an identity token for accessing the registry without password." consumes: - "application/json" produces: - "application/json" responses: 200: description: "An identity token was generated successfully." schema: $ref: "#/definitions/AuthResponse" 401: description: "Login unauthorized" schema: $ref: "#/responses/401ErrorResponse" 500: description: "Server error" schema: $ref: "#/responses/500ErrorResponse" parameters: - name: "authConfig" in: "body" description: "Authentication to check" schema: $ref: "#/definitions/AuthConfig" /daemon/update: post: summary: "Update daemon's labels and image proxy" consumes: - "application/json" produces: - "application/json" responses: 200: description: "no error" 400: description: "bad parameter" schema: $ref: '#/definitions/Error' 500: $ref: "#/responses/500ErrorResponse" parameters: - name: "DaemonUpdateConfig" in: body description: "Config used to update daemon, only labels and image proxy are allowed." schema: $ref: "#/definitions/DaemonUpdateConfig" /images/create: post: summary: "Create an image by pulling from a registry or importing from an existing source file" consumes: - "text/plain" - "application/octet-stream" produces: - "application/json" responses: 200: description: "no error" 404: schema: $ref: '#/definitions/Error' description: "image not found" 500: $ref: "#/responses/500ErrorResponse" parameters: - name: "fromImage" in: "query" description: "Name of the image to pull. The name may include a tag or digest. This parameter may only be used when pulling an image. The pull is cancelled if the HTTP connection is closed." type: "string" - name: "fromSrc" in: "query" description: "Source to import. The value may be a URL from which the image can be retrieved or `-` to read the image from the request body. This parameter may only be used when importing an image." type: "string" - name: "repo" in: "query" description: "Repository name given to an image when it is imported. The repo may include a tag. This parameter may only be used when importing an image." type: "string" - name: "tag" in: "query" description: "Tag or digest. If empty when pulling an image, this causes all tags for the given image to be pulled." type: "string" - name: "inputImage" in: "body" description: "Image content if the value `-` has been specified in fromSrc query parameter" schema: type: "string" required: false - name: "X-Registry-Auth" in: "header" description: "A base64-encoded auth configuration. [See the authentication section for details.](#section/Authentication)" type: "string" /images/load: post: summary: "Import images" description: | Load a set of images by oci.v1 format tar stream consumes: - application/x-tar responses: 200: description: "no error" 500: description: "server error" schema: $ref: "#/responses/500ErrorResponse" parameters: - name: "imageTarStream" in: "body" description: "tar stream containing images" schema: type: "string" format: "binary" - name: "name" in: "query" description: "set the image name for the tar stream, default unknown/unknown" type: "string" /images/{imageid}/json: get: summary: "Inspect a image" description: "Return the information about image" operationId: "ImageInspect" produces: - "application/json" responses: 200: description: "no error" schema: $ref: "#/definitions/ImageInfo" examples: application/json: CreatedAt: "2017-12-19 15:32:09" Digest: "sha256:e216a057b1cb1efc11f8a268f37ef62083e70b1b38323ba252e25ac88904a7e8" ID: "e216a057b1cb" Name: "ubuntu:12.04" Size: 103579269 Tag: "12.04" 404: description: "no such image" schema: $ref: "#/definitions/Error" examples: application/json: message: "No such image: e216a057b1cb" 500: $ref: "#/responses/500ErrorResponse" parameters: - $ref: "#/parameters/imageid" /images/json: get: summary: "List Images" description: "Return a list of stored images." operationId: "ImageList" produces: - "application/json" responses: 200: description: "Summary image data for the images matching the query" schema: type: "array" items: $ref: "#/definitions/ImageInfo" examples: application/json: - Id: "sha256:e216a057b1cb1efc11f8a268f37ef62083e70b1b38323ba252e25ac88904a7e8" ParentId: "" RepoTags: - "ubuntu:12.04" - "ubuntu:precise" RepoDigests: - "ubuntu@sha256:992069aee4016783df6345315302fa59681aae51a8eeb2f889dea59290f21787" Created: 1474925151 Size: 103579269 VirtualSize: 103579269 SharedSize: 0 Labels: {} Containers: 2 - Id: "sha256:3e314f95dcace0f5e4fd37b10862fe8398e3c60ed36600bc0ca5fda78b087175" ParentId: "" RepoTags: - "ubuntu:12.10" - "ubuntu:quantal" RepoDigests: - "ubuntu@sha256:002fba3e3255af10be97ea26e476692a7ebed0bb074a9ab960b2e7a1526b15d7" - "ubuntu@sha256:68ea0200f0b90df725d99d823905b04cf844f6039ef60c60bf3e019915017bd3" Created: 1403128455 Size: 172064416 VirtualSize: 172064416 SharedSize: 0 Labels: {} Containers: 5 500: $ref: "#/responses/500ErrorResponse" parameters: - name: "all" in: "query" description: "Show all images. Only images from a final layer (no children) are shown by default." type: "boolean" - name: "filters" in: "query" description: | A JSON encoded value of the filters (a `map[string][]string`) to process on the images list. Available filters: - `before`=(`[:]`, `` or ``) - `dangling=true` - `label=key` or `label="key=value"` of an image label - `reference`=(`[:]`) - `since`=(`[:]`, `` or ``) type: "string" - name: "digests" in: "query" description: "Show digest information as a `RepoDigests` field on each image." type: "boolean" /images/search: get: summary: "Search images" produces: - "application/json" responses: 200: description: "No error" schema: type: "array" items: $ref: "#/definitions/SearchResultItem" 500: $ref: "#/responses/500ErrorResponse" /images/{imageid}/tag: post: summary: "Tag an image" description: "Add tag reference to the existing image" parameters: - $ref: "#/parameters/imageid" - name: "repo" in: "query" description: "The repository to tag in. For example, `someuser/someimage`." type: "string" - name: "tag" in: "query" description: "The name of the new tag." type: "string" responses: 201: description: "No error" 400: description: "Bad parameter" schema: $ref: "#/definitions/Error" 404: description: "no such image" schema: $ref: "#/definitions/Error" 500: $ref: "#/responses/500ErrorResponse" /images/{imageid}: delete: summary: "Remove an image" description: "Remove an image by reference." parameters: - $ref: "#/parameters/imageid" - name: "force" in: "query" description: "Remove the image even if it is being used" type: "boolean" default: false responses: 204: description: "No error" 404: description: "no such image" schema: $ref: "#/definitions/Error" examples: application/json: message: "No such image: c2ada9df5af8" 500: $ref: "#/responses/500ErrorResponse" /containers/create: post: summary: "Create a container" consumes: - "application/json" produces: - "application/json" parameters: - name: "name" in: "query" description: "Assign the specified name to the container. Must match `/?[a-zA-Z0-9_-]+`." type: "string" pattern: "/?[a-zA-Z0-9_-]+" - name: "body" in: "body" description: "Container to create" schema: $ref: "#/definitions/ContainerCreateConfig" required: true responses: 201: description: "Container created successfully" schema: $ref: "#/definitions/ContainerCreateResp" examples: application/json: Id: "e90e34656806" Warnings: [] 400: description: "bad parameter" schema: $ref: "#/definitions/Error" 404: description: "no such image" schema: $ref: "#/definitions/Error" examples: application/json: message: "image: xxx:latest: not found" 409: description: "conflict" schema: $ref: "#/definitions/Error" 500: $ref: "#/responses/500ErrorResponse" tags: ["Container"] /containers/{id}/json: get: summary: "Inspect a container" description: "Return low-level information about a container." operationId: "ContainerInspect" produces: - "application/json" responses: 200: description: "no error" schema: $ref: "#/definitions/ContainerJSON" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" parameters: - $ref: "#/parameters/id" - name: "size" in: "query" type: "boolean" description: "Return the size of container as fields `SizeRw` and `SizeRootFs`" tags: ["Container"] /containers/json: get: summary: "List containers" operationId: "ContainerList" produces: ["application/json"] responses: 200: description: "Summary containers that matches the query" schema: type: "array" items: $ref: "#/definitions/Container" 500: $ref: "#/responses/500ErrorResponse" parameters: - name: "all" in: "query" description: "Return all containers. By default, only running containers are shown" type: "boolean" default: false /containers/{id}/rename: post: summary: "Rename a container" operationId: "ContainerRename" parameters: - $ref: "#/parameters/id" - name: "name" in: "query" required: true description: "New name for the container" type: "string" responses: 204: description: "no error" 404: $ref: "#/responses/404ErrorResponse" 409: description: "name already in use" schema: $ref: "#/definitions/Error" 500: $ref: "#/responses/500ErrorResponse" tags: ["Container"] /containers/{id}/restart: post: summary: "Restart a container" operationId: "ContainerRestart" parameters: - $ref: "#/parameters/id" - name: "name" in: "query" required: true description: "New name for the container" type: "string" - name: "t" in: "query" description: "Number of seconds to wait before killing the container" type: "integer" responses: 204: description: "no error" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" tags: ["Container"] /containers/{id}/start: post: summary: "Start a container" operationId: "ContainerStart" parameters: - $ref: "#/parameters/id" - name: "detachKeys" in: "query" description: "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-` where `` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`." type: "string" responses: 204: description: "no error" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" tags: ["Container"] /containers/{id}/stop: post: summary: "Stop a container" operationId: "ContainerStop" parameters: - $ref: "#/parameters/id" - name: "t" in: "query" description: "Number of seconds to wait before killing the container" type: "integer" responses: 204: description: "no error" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" tags: ["Container"] /containers/{id}/pause: post: summary: "Pause a container" operationId: "ContainerPause" parameters: - $ref: "#/parameters/id" responses: 204: description: "no error" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" tags: ["Container"] /containers/{id}/unpause: post: summary: "Unpause a container" operationId: "ContainerUnpause" parameters: - $ref: "#/parameters/id" responses: 204: description: "no error" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" tags: ["Container"] /containers/{id}/top: post: summary: "Display the running processes of a container" operationId: "ContainerTop" parameters: - $ref: "#/parameters/id" - name: "ps_args" in: "query" description: "top arguments" type: "string" responses: 200: description: "no error" schema: $ref: "#/definitions/ContainerProcessList" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" tags: ["Container"] /containers/{id}/wait: post: summary: "Block until a container stops, then returns the exit code." operationId: "ContainerWait" parameters: - $ref: "#/parameters/id" responses: 200: description: "The container has exited." schema: type: "object" required: [StatusCode] properties: StatusCode: description: "Exit code of the container" type: "integer" x-nullable: false Error: description: "The error message of waiting container" type: "string" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" tags: ["Container"] /containers/{id}: delete: summary: "Remove one container" operationId: "ContainerRemove" parameters: - $ref: "#/parameters/id" - name: "force" in: "query" description: "If the container is running, force query is used to kill it and remove it forcefully." type: "boolean" responses: 204: description: "no error" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" tags: ["Container"] /containers/{id}/exec: post: summary: "Create an exec instance" description: "Run a command inside a running container." operationId: "ContainerExec" consumes: - "application/json" produces: - "application/json" responses: 201: description: "no error" schema: $ref: "#/definitions/ExecCreateResp" 404: $ref: "#/responses/404ErrorResponse" 409: description: "container is paused" schema: $ref: "#/definitions/Error" 500: $ref: "#/responses/500ErrorResponse" parameters: - $ref: "#/parameters/id" - name: "body" in: "body" schema: $ref: "#/definitions/ExecCreateConfig" required: true tags: ["Exec"] /containers/{id}/logs: get: summary: "Get container logs" description: | Get `stdout` and `stderr` logs from a container. Note: This endpoint works only for containers with the `json-file` or `journald` logging driver. operationId: "ContainerLogs" responses: 101: description: "logs returned as a stream" schema: type: "string" format: "binary" 200: description: "logs returned as a string in response body" schema: type: "string" 400: description: "bad parameter" schema: $ref: "#/definitions/Error" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "follow" in: "query" description: | Return the logs as a stream. type: "boolean" default: false - name: "stdout" in: "query" description: "Return logs from `stdout`" type: "boolean" default: false - name: "stderr" in: "query" description: "Return logs from `stderr`" type: "boolean" default: false - name: "since" in: "query" description: "Only return logs since this time, as a UNIX timestamp" type: "integer" default: 0 - name: "until" in: "query" description: "Only return logs before this time, as a UNIX timestamp" type: "integer" default: 0 - name: "timestamps" in: "query" description: "Add timestamps to every log line" type: "boolean" default: false - name: "tail" in: "query" description: "Only return this number of log lines from the end of the logs. Specify as an integer or `all` to output all log lines." type: "string" default: "all" tags: ["Container"] /containers/{id}/resize: post: summary: "changes the size of the tty for a container" operationId: "ContainerResize" parameters: - $ref: "#/parameters/id" - name: "height" in: "query" description: "height of the tty" type: "string" - name: "width" in: "query" description: "width of the tty" type: "string" responses: 200: description: "no error" 400: description: "bad parameter" schema: $ref: "#/definitions/Error" tags: ["Container"] /exec/{id}/start: post: summary: "Start an exec instance" description: "Starts a previously set up exec instance. If detach is true, this endpoint returns immediately after starting the command. Otherwise, it sets up an interactive session with the command." operationId: "ExecStart" consumes: - "application/json" produces: - "application/vnd.raw-stream" responses: 200: description: "No error" 404: description: "No such exec instance" schema: $ref: "#/definitions/Error" 409: description: "Container is stopped or paused" schema: $ref: "#/definitions/Error" parameters: - name: "execStartConfig" in: "body" schema: $ref: "#/definitions/ExecStartConfig" - name: "id" in: "path" description: "Exec instance ID" required: true type: "string" tags: ["Exec"] /exec/{id}/json: get: summary: "Inspect an exec instance" description: "Return low-level information about an exec instance." operationId: "ExecInspect" produces: - "application/json" responses: 200: description: "No error" schema: $ref: "#/definitions/ContainerExecInspect" 404: description: "No such exec instance" schema: $ref: "#/responses/404ErrorResponse" 500: description: "Server error" schema: $ref: "#/responses/500ErrorResponse" parameters: - name: "id" in: "path" description: "Exec instance ID" required: true type: "string" tags: ["Exec"] /containers/{id}/attach: post: summary: "Attach to a container" description: | Attach to a container to read its output or send it input. You can attach to the same container multiple times and you can reattach to containers that have been detached. Either the `stream` or `logs` parameter must be `true` for this endpoint to do anything. ### Hijacking This endpoint hijacks the HTTP connection to transport `stdin`, `stdout`, and `stderr` on the same socket. This is the response from the daemon for an attach request: ``` HTTP/1.1 200 OK Content-Type: application/vnd.raw-stream [STREAM] ``` After the headers and two new lines, the TCP connection can now be used for raw, bidirectional communication between the client and server. To hint potential proxies about connection hijacking, the Pouch client can also optionally send connection upgrade headers. For example, the client sends this request to upgrade the connection: ``` POST /containers/16253994b7c4/attach?stream=1&stdout=1 HTTP/1.1 Upgrade: tcp Connection: Upgrade ``` The Pouch daemon will respond with a `101 UPGRADED` response, and will similarly follow with the raw stream: ``` HTTP/1.1 101 UPGRADED Content-Type: application/vnd.raw-stream Connection: Upgrade Upgrade: tcp [STREAM] ``` ### Stream format When the TTY setting is disabled in [`POST /containers/create`](#operation/ContainerCreate), the stream over the hijacked connected is multiplexed to separate out `stdout` and `stderr`. The stream consists of a series of frames, each containing a header and a payload. The header contains the information which the stream writes (`stdout` or `stderr`). It also contains the size of the associated frame encoded in the last four bytes (`uint32`). It is encoded on the first eight bytes like this: ```go header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4} ``` `STREAM_TYPE` can be: - 0: `stdin` (is written on `stdout`) - 1: `stdout` - 2: `stderr` `SIZE1, SIZE2, SIZE3, SIZE4` are the four bytes of the `uint32` size encoded as big endian. Following the header is the payload, which is the specified number of bytes of `STREAM_TYPE`. The simplest way to implement this protocol is the following: 1. Read 8 bytes. 2. Choose `stdout` or `stderr` depending on the first byte. 3. Extract the frame size from the last four bytes. 4. Read the extracted size and output it on the correct output. 5. Goto 1. ### Stream format when using a TTY When the TTY setting is enabled in [`POST /containers/create`](#operation/ContainerCreate), the stream is not multiplexed. The data exchanged over the hijacked connection is simply the raw data from the process PTY and client's `stdin`. operationId: "ContainerAttach" produces: - "application/vnd.raw-stream" responses: 101: description: "no error, hints proxy about hijacking" 200: description: "no error, no upgrade header found" 400: description: "bad parameter" schema: $ref: "#/definitions/Error" 404: description: "no such container" schema: $ref: "#/definitions/Error" examples: application/json: message: "No such container: c2ada9df5af8" 500: description: "server error" schema: $ref: "#/definitions/Error" parameters: - name: "id" in: "path" required: true description: "ID or name of the container" type: "string" - name: "detachKeys" in: "query" description: "Override the key sequence for detaching a container.Format is a single character `[a-Z]` or `ctrl-` where `` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`." type: "string" - name: "logs" in: "query" description: | Replay previous logs from the container. This is useful for attaching to a container that has started and you want to output everything since the container started. If `stream` is also enabled, once all the previous output has been returned, it will seamlessly transition into streaming current output. type: "boolean" default: false - name: "stream" in: "query" description: "Stream attached streams from the time the request was made onwards" type: "boolean" default: false - name: "stdin" in: "query" description: "Attach to `stdin`" type: "boolean" default: false - name: "stdout" in: "query" description: "Attach to `stdout`" type: "boolean" default: false - name: "stderr" in: "query" description: "Attach to `stderr`" type: "boolean" default: false tags: ["Container"] /containers/{id}/update: post: summary: "Update the configurations of a container" operationId: "ContainerUpdate" parameters: - $ref: "#/parameters/id" - name: "updateConfig" in: "body" schema: $ref: "#/definitions/UpdateConfig" responses: 200: description: "no error" 400: description: "bad parameter" schema: $ref: "#/definitions/Error" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" tags: ["Container"] /containers/{id}/upgrade: post: summary: "Upgrade a container with new image and args" operationId: "ContainerUpgrade" parameters: - $ref: "#/parameters/id" - name: "upgradeConfig" in: "body" schema: $ref: "#/definitions/ContainerUpgradeConfig" responses: 200: description: "no error" 400: description: "bad parameter" schema: $ref: "#/definitions/Error" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" tags: ["Container"] /volumes: get: summary: "List volumes" operationId: "VolumeList" produces: ["application/json"] responses: 200: description: "Summary volume data that matches the query" schema: $ref: "#/definitions/VolumeListResp" examples: application/json: Volumes: - CreatedAt: "2017-07-19T12:00:26Z" Name: "tardis" Driver: "local" Mountpoint: "/var/lib/pouch/volumes/tardis" Labels: com.example.some-label: "some-value" com.example.some-other-label: "some-other-value" Scope: "local" Options: device: "tmpfs" o: "opt.size=100m,uid=1000" type: "tmpfs" Warnings: [] 500: $ref: "#/responses/500ErrorResponse" parameters: - name: "filters" in: "query" description: | JSON encoded value of the filters (a `map[string][]string`) to process on the volumes list. Available filters: - `dangling=` When set to `true` (or `1`), returns all volumes that are not in use by a container. When set to `false` (or `0`), only volumes that are in use by one or more containers are returned. - `driver=` Matches volumes based on their driver. - `label=` or `label=:` Matches volumes based on the presence of a `label` alone or a `label` and a value. - `name=` Matches all or part of a volume name. type: "string" format: "json" tags: ["Volume"] /volumes/create: post: summary: "Create a volume" operationId: "VolumeCreate" consumes: ["application/json"] produces: ["application/json"] responses: 201: description: "The volume was created successfully" schema: $ref: "#/definitions/VolumeInfo" 500: $ref: "#/responses/500ErrorResponse" parameters: - name: "body" in: "body" required: true description: "Volume configuration" schema: $ref: "#/definitions/VolumeCreateConfig" tags: ["Volume"] /volumes/{id}: get: summary: "Inspect a volume" operationId: "VolumeInspect" produces: ["application/json"] responses: 200: description: "No error" schema: $ref: "#/definitions/VolumeInfo" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" parameters: - $ref: "#/parameters/id" tags: ["Volume"] delete: summary: "Delete a volume" operationId: "VolumeDelete" responses: 204: description: "No error" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" parameters: - $ref: "#/parameters/id" tags: ["Volume"] /networks/create: post: summary: "Create a network" operationId: "NetworkCreate" consumes: ["application/json"] produces: ["application/json"] responses: 201: description: "The network was created successfully" schema: $ref: "#/definitions/NetworkCreateResp" 400: description: "bad parameter" schema: $ref: "#/definitions/Error" 409: description: "name already in use" schema: $ref: "#/definitions/Error" 500: $ref: "#/responses/500ErrorResponse" parameters: - name: "NetworkCreateConfig" in: "body" required: true description: "Network configuration" schema: $ref: "#/definitions/NetworkCreateConfig" tags: ["Network"] /networks/{id}: get: summary: "Inspect a network" operationId: "NetworkInspect" produces: - "application/json" responses: 200: description: "No error" schema: $ref: "#/definitions/NetworkInspectResp" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" parameters: - $ref: "#/parameters/id" tags: ["Network"] delete: summary: "Remove a network" operationId: "NetworkDelete" responses: 204: description: "No error" 403: description: "operation not supported for pre-defined networks" schema: $ref: "#/definitions/Error" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" parameters: - $ref: "#/parameters/id" tags: ["Network"] /networks: get: summary: "List networks" operationId: "NetworkList" produces: ["application/json"] responses: 200: description: "Summary networks that matches the query" schema: $ref: "#/definitions/NetworkResource" 500: $ref: "#/responses/500ErrorResponse" tags: ["Network"] /networks/{id}/connect: post: summary: "Connect a container to a network" operationId: "NetworkConnect" consumes: - "application/json" responses: 200: description: "No error" 400: description: "bad parameter" schema: $ref: "#/definitions/Error" 404: description: "Network or container not found" schema: $ref: "#/responses/404ErrorResponse" 500: description: "Server error" schema: $ref: "#/responses/500ErrorResponse" parameters: - name: "id" in: "path" description: "Network ID or name" required: true type: "string" - name: "container" in: "body" required: true schema: $ref: "#/definitions/NetworkConnect" tags: ["Network"] /networks/{id}/disconnect: post: summary: "Disconnect a container from a network" operationId: "NetworkDisconnect" consumes: - "application/json" responses: 200: description: "No error" 400: description: "bad parameter" schema: $ref: "#/definitions/Error" 404: description: "Network or container not found" schema: $ref: "#/responses/404ErrorResponse" 500: description: "Server error" schema: $ref: "#/responses/500ErrorResponse" parameters: - name: "id" in: "path" description: "Network ID or name" required: true type: "string" - name: "NetworkDisconnect" in: "body" required: true description: "Network disconnect parameters" schema: $ref: "#/definitions/NetworkDisconnect" tags: ["Network"] definitions: Error: type: "object" properties: message: type: string SystemVersion: type: "object" properties: Version: type: "string" description: "version of Pouch Daemon" example: "0.1.2" ApiVersion: type: "string" description: "Api Version held by daemon" example: "" GitCommit: type: "string" description: "Commit ID held by the latest commit operation" example: "" GoVersion: type: "string" description: "version of Go runtime" example: "1.8.3" Os: type: "string" description: "Operating system type of underlying system" example: "linux" Arch: type: "string" description: "Arch type of underlying hardware" example: "amd64" KernelVersion: type: "string" description: "Operating system kernel version" example: "3.13.0-106-generic" BuildTime: type: "string" description: "The time when this binary of daemon is built" example: "2017-08-29T17:41:57.729792388+00:00" SystemInfo: type: "object" properties: ID: description: | Unique identifier of the daemon.


> **Note**: The format of the ID itself is not part of the API, and > should not be considered stable. type: "string" example: "7TRN:IPZB:QYBB:VPBQ:UMPP:KARE:6ZNR:XE6T:7EWV:PKF4:ZOJD:TPYS" Containers: description: "Total number of containers on the host." type: "integer" example: 14 ContainersRunning: description: | Number of containers with status `"running"`. type: "integer" example: 3 ContainersPaused: description: | Number of containers with status `"paused"`. type: "integer" example: 1 ContainersStopped: description: | Number of containers with status `"stopped"`. type: "integer" example: 10 Images: description: | Total number of images on the host. Both _tagged_ and _untagged_ (dangling) images are counted. type: "integer" example: 508 Driver: description: "Name of the storage driver in use." type: "string" example: "overlay2" DriverStatus: description: | Information specific to the storage driver, provided as "label" / "value" pairs. This information is provided by the storage driver, and formatted in a way consistent with the output of `pouch info` on the command line.


> **Note**: The information returned in this field, including the > formatting of values and labels, should not be considered stable, > and may change without notice. type: "array" items: type: "array" items: type: "string" example: - ["Backing Filesystem", "extfs"] - ["Supports d_type", "true"] - ["Native Overlay Diff", "true"] PouchRootDir: description: | Root directory of persistent Pouch state. Defaults to `/var/lib/pouch` on Linux. type: "string" example: "/var/lib/pouch" Debug: description: "Indicates if the daemon is running in debug-mode / with debug-level logging enabled." type: "boolean" example: true LoggingDriver: description: | The logging driver to use as a default for new containers. type: "string" VolumeDrivers: description: | The list of volume drivers which the pouchd supports type: "array" items: type: "string" example: ["local", "tmpfs"] CgroupDriver: description: | The driver to use for managing cgroups. type: "string" x-nullable: false enum: ["cgroupfs", "systemd"] default: "cgroupfs" example: "cgroupfs" KernelVersion: description: | Kernel version of the host. On Linux, this information obtained from `uname`. type: "string" OperatingSystem: description: | Name of the host's operating system, for example: "Ubuntu 16.04.2 LTS". type: "string" example: "Alpine Linux v3.5" OSType: description: | Generic type of the operating system of the host, as returned by the Go runtime (`GOOS`). Currently returned value is "linux". A full list of possible values can be found in the [Go documentation](https://golang.org/doc/install/source#environment). type: "string" example: "linux" Architecture: description: | Hardware architecture of the host, as returned by the Go runtime (`GOARCH`). A full list of possible values can be found in the [Go documentation](https://golang.org/doc/install/source#environment). type: "string" example: "x86_64" NCPU: description: | The number of logical CPUs usable by the daemon. The number of available CPUs is checked by querying the operating system when the daemon starts. Changes to operating system CPU allocation after the daemon is started are not reflected. type: "integer" example: 4 MemTotal: description: | Total amount of physical memory available on the host, in kilobytes (kB). type: "integer" format: "int64" example: 2095882240 IndexServerAddress: description: | Address / URL of the index server that is used for image search, and as a default for user authentication. type: "string" DefaultRegistry: description: | default registry can be defined by user. type: "string" RegistryConfig: $ref: "#/definitions/RegistryServiceConfig" HttpProxy: description: | HTTP-proxy configured for the daemon. This value is obtained from the [`HTTP_PROXY`](https://www.gnu.org/software/wget/manual/html_node/Proxies.html) environment variable. Containers do not automatically inherit this configuration. type: "string" example: "http://user:pass@proxy.corp.example.com:8080" HttpsProxy: description: | HTTPS-proxy configured for the daemon. This value is obtained from the [`HTTPS_PROXY`](https://www.gnu.org/software/wget/manual/html_node/Proxies.html) environment variable. Containers do not automatically inherit this configuration. type: "string" example: "https://user:pass@proxy.corp.example.com:4443" Name: description: "Hostname of the host." type: "string" example: "node5.corp.example.com" Labels: description: | User-defined labels (key/value metadata) as set on the daemon. type: "array" items: type: "string" example: ["storage=ssd", "production"] ExperimentalBuild: description: | Indicates if experimental features are enabled on the daemon. type: "boolean" example: true ServerVersion: description: | Version string of the daemon. type: "string" example: "17.06.0-ce" Runtimes: description: | List of [OCI compliant](https://github.com/opencontainers/runtime-spec) runtimes configured on the daemon. Keys hold the "name" used to reference the runtime. The Pouch daemon relies on an OCI compliant runtime (invoked via the `containerd` daemon) as its interface to the Linux kernel namespaces, cgroups, and SELinux. The default runtime is `runc`, and automatically configured. Additional runtimes can be configured by the user and will be listed here. type: "object" additionalProperties: $ref: "#/definitions/Runtime" default: runc: path: "pouch-runc" example: runc: path: "pouch-runc" runc-master: path: "/go/bin/runc" custom: path: "/usr/local/bin/my-oci-runtime" runtimeArgs: ["--debug", "--systemd-cgroup=false"] DefaultRuntime: description: | Name of the default OCI runtime that is used when starting containers. The default can be overridden per-container at create time. type: "string" x-nullable: false default: "runc" example: "runc" LiveRestoreEnabled: description: | Indicates if live restore is enabled. If enabled, containers are kept running when the daemon is shutdown or upon daemon start if running containers are detected. type: "boolean" x-nullable: false default: false example: false LxcfsEnabled: description: | Indicates if lxcfs is enabled. type: "boolean" x-nullable: false default: false example: false ContainerdCommit: $ref: "#/definitions/Commit" RuncCommit: $ref: "#/definitions/Commit" SecurityOptions: description: | List of security features that are enabled on the daemon, such as apparmor, seccomp, SELinux, and user-namespaces (userns). Additional configuration options for each security feature may be present, and are included as a comma-separated list of key/value pairs. type: "array" items: type: "string" example: - "name=apparmor" - "name=seccomp,profile=default" - "name=selinux" - "name=userns" ListenAddresses: description: "List of addresses the pouchd listens on" type: "array" items: type: "string" example: - ["unix:///var/run/pouchd.sock", "tcp://0.0.0.0:4243"] DaemonUpdateConfig: type: "object" properties: Labels: description: "Labels indentified the attributes of daemon" type: "array" items: type: "string" example: ["storage=ssd", "zone=hangzhou"] ImageProxy: description: "Image proxy used to pull image." type: "string" RegistryServiceConfig: description: | RegistryServiceConfig stores daemon registry services configuration. type: "object" x-nullable: true properties: AllowNondistributableArtifactsCIDRs: description: | List of IP ranges to which nondistributable artifacts can be pushed, using the CIDR syntax [RFC 4632](https://tools.ietf.org/html/4632). Some images contain artifacts whose distribution is restricted by license. When these images are pushed to a registry, restricted artifacts are not included. This configuration override this behavior, and enables the daemon to push nondistributable artifacts to all registries whose resolved IP address is within the subnet described by the CIDR syntax. This option is useful when pushing images containing nondistributable artifacts to a registry on an air-gapped network so hosts on that network can pull the images without connecting to another server. > **Warning**: Nondistributable artifacts typically have restrictions > on how and where they can be distributed and shared. Only use this > feature to push artifacts to private registries and ensure that you > are in compliance with any terms that cover redistributing > nondistributable artifacts. x-omitempty: true type: "array" items: type: "string" example: ["::1/128", "127.0.0.0/8"] AllowNondistributableArtifactsHostnames: description: | List of registry hostnames to which nondistributable artifacts can be pushed, using the format `[:]` or `[:]`. Some images (for example, Windows base images) contain artifacts whose distribution is restricted by license. When these images are pushed to a registry, restricted artifacts are not included. This configuration override this behavior for the specified registries. This option is useful when pushing images containing nondistributable artifacts to a registry on an air-gapped network so hosts on that network can pull the images without connecting to another server. > **Warning**: Nondistributable artifacts typically have restrictions > on how and where they can be distributed and shared. Only use this > feature to push artifacts to private registries and ensure that you > are in compliance with any terms that cover redistributing > nondistributable artifacts. x-omitempty: true type: "array" items: type: "string" example: ["registry.internal.corp.example.com:3000", "[2001:db8:a0b:12f0::1]:443"] InsecureRegistryCIDRs: description: | List of IP ranges of insecure registries, using the CIDR syntax ([RFC 4632](https://tools.ietf.org/html/4632)). Insecure registries accept un-encrypted (HTTP) and/or untrusted (HTTPS with certificates from unknown CAs) communication. By default, local registries (`127.0.0.0/8`) are configured as insecure. All other registries are secure. Communicating with an insecure registry is not possible if the daemon assumes that registry is secure. This configuration override this behavior, insecure communication with registries whose resolved IP address is within the subnet described by the CIDR syntax. Registries can also be marked insecure by hostname. Those registries are listed under `IndexConfigs` and have their `Secure` field set to `false`. > **Warning**: Using this option can be useful when running a local > registry, but introduces security vulnerabilities. This option > should therefore ONLY be used for testing purposes. For increased > security, users should add their CA to their system's list of trusted > CAs instead of enabling this option. x-omitempty: true type: "array" items: type: "string" example: ["::1/128", "127.0.0.0/8"] IndexConfigs: x-omitempty: true type: "object" additionalProperties: $ref: "#/definitions/IndexInfo" example: "127.0.0.1:5000": "Name": "127.0.0.1:5000" "Mirrors": [] "Secure": false "Official": false "[2001:db8:a0b:12f0::1]:80": "Name": "[2001:db8:a0b:12f0::1]:80" "Mirrors": [] "Secure": false "Official": false "registry.internal.corp.example.com:3000": Name: "registry.internal.corp.example.com:3000" Mirrors: [] Secure: false Official: false Mirrors: description: "List of registry URLs that act as a mirror for the official registry." x-omitempty: true type: "array" items: type: "string" example: - "https://hub-mirror.corp.example.com:5000/" - "https://[2001:db8:a0b:12f0::1]/" IndexInfo: description: IndexInfo contains information about a registry. type: "object" x-nullable: true properties: Name: description: | Name of the registry. type: "string" Mirrors: description: | List of mirrors, expressed as URIs. type: "array" items: type: "string" example: - "https://hub-mirror.corp.example.com:5000/" Secure: description: | Indicates if the the registry is part of the list of insecure registries. If `false`, the registry is insecure. Insecure registries accept un-encrypted (HTTP) and/or untrusted (HTTPS with certificates from unknown CAs) communication. > **Warning**: Insecure registries can be useful when running a local > registry. However, because its use creates security vulnerabilities > it should ONLY be enabled for testing purposes. For increased > security, users should add their CA to their system's list of > trusted CAs instead of enabling this option. type: "boolean" example: true Official: description: | Indicates whether this is an official registry. type: "boolean" example: true Runtime: description: | Runtime describes an [OCI compliant](https://github.com/opencontainers/runtime-spec) runtime. The runtime is invoked by the daemon via the `containerd` daemon. OCI runtimes act as an interface to the Linux kernel namespaces, cgroups, and SELinux. type: "object" properties: path: description: | Name and, optional, path, of the OCI executable binary. If the path is omitted, the daemon searches the host's `$PATH` for the binary and uses the first result. type: "string" example: "/usr/local/bin/my-oci-runtime" runtimeArgs: description: | List of command-line arguments to pass to the runtime when invoked. type: "array" x-nullable: true items: type: "string" example: ["--debug", "--systemd-cgroup=false"] Commit: description: | Commit holds the Git-commit (SHA1) that a binary was built from, as reported in the version-string of external tools, such as `containerd`, or `runC`. type: "object" properties: ID: description: "Actual commit ID of external tool." type: "string" example: "cfb82a876ecc11b5ca0977d1733adbe58599088a" Expected: description: | Commit ID of external tool expected by pouchd as set at build time. type: "string" example: "2d41c047c83e09a6d61d464906feb2a2f3c52aa4" AuthConfig: type: "object" properties: Username: type: "string" Password: type: "string" Auth: type: "string" ServerAddress: type: "string" IdentityToken: type: "string" description: "IdentityToken is used to authenticate the user and get an access token for the registry" RegistryToken: type: "string" description: "RegistryToken is a bearer token to be sent to a registry" AuthResponse: description: "The response returned by login to a registry" type: "object" required: [Status] properties: Status: description: "The status of the authentication" type: "string" x-nullable: false IdentityToken: description: "An opaque token used to authenticate a user after a successful login" type: "string" x-nullable: false ContainerCreateConfig: description: | ContainerCreateConfig is used for API "POST /containers/create". It wraps all kinds of config used in container creation. It can be used to encode client params in client and unmarshal request body in daemon side. allOf: - $ref: "#/definitions/ContainerConfig" - type: "object" properties: HostConfig: $ref: "#/definitions/HostConfig" NetworkingConfig: $ref: "#/definitions/NetworkingConfig" ContainerConfig: type: "object" description: "Configuration for a container that is portable between hosts" required: - Image properties: Hostname: description: "The hostname to use for the container, as a valid RFC 1123 hostname." type: "string" format: hostname minLength: 1 Domainname: description: "The domain name to use for the container." type: "string" User: description: "The user that commands are run as inside the container." type: "string" AttachStdin: description: "Whether to attach to `stdin`." type: "boolean" x-nullable: false AttachStdout: description: "Whether to attach to `stdout`." type: "boolean" x-nullable: false default: true AttachStderr: description: "Whether to attach to `stderr`." type: "boolean" x-nullable: false default: true DisableNetworkFiles: description: "Whether to generate the network files(/etc/hostname, /etc/hosts and /etc/resolv.conf) for container." type: "boolean" x-nullable: false default: false ExposedPorts: description: "An object mapping ports to an empty object in the form:`{/: {}}`" type: "object" additionalProperties: type: "object" enum: - {} default: {} Tty: description: "Attach standard streams to a TTY, including `stdin` if it is not closed." type: "boolean" x-nullable: false OpenStdin: description: "Open `stdin`" type: "boolean" x-nullable: false StdinOnce: description: "Close `stdin` after one attached client disconnects" type: "boolean" x-nullable: false Env: description: | A list of environment variables to set inside the container in the form `["VAR=value", ...]`. A variable without `=` is removed from the environment, rather than to have an empty value. type: "array" items: type: "string" Cmd: description: "Command to run specified an array of strings." type: "array" items: type: "string" ArgsEscaped: description: "Command is already escaped (Windows only)" type: "boolean" x-nullable: false Image: description: "The name of the image to use when creating the container" type: "string" x-nullable: false Volumes: description: "An object mapping mount point paths inside the container to empty objects." type: "object" additionalProperties: type: "object" enum: - {} default: {} WorkingDir: description: "The working directory for commands to run in." type: "string" Entrypoint: description: | The entry point for the container as a string or an array of strings. If the array consists of exactly one empty string (`[""]`) then the entry point is reset to system default. type: "array" items: type: "string" NetworkDisabled: description: "Disable networking for the container." type: "boolean" MacAddress: description: "MAC address of the container." type: "string" OnBuild: description: "`ONBUILD` metadata that were defined." type: "array" items: type: "string" Labels: description: "User-defined key/value metadata." type: "object" additionalProperties: type: "string" StopSignal: description: "Signal to stop a container as a string or unsigned integer." type: "string" default: "SIGTERM" x-nullable: false StopTimeout: description: "Timeout to stop a container in seconds." type: "integer" default: 10 Shell: description: "Shell for when `RUN`, `CMD`, and `ENTRYPOINT` uses a shell." type: "array" items: type: "string" Rich: type: "boolean" description: "Whether to start container in rich container mode. (default false)" x-nullable: false RichMode: type: "string" description: "Choose one rich container mode.(default dumb-init)" enum: - "dumb-init" - "sbin-init" - "systemd" InitScript: type: "string" description: "Initial script executed in container. The script will be executed before entrypoint or command" DiskQuota: type: "object" description: "Set disk quota for container" x-nullable: true additionalProperties: type: "string" SpecAnnotation: description: "annotations send to runtime spec." type: "object" additionalProperties: type: "string" QuotaID: type: "string" description: "set disk quota by specified quota id, if id < 0, it means pouchd alloc a unique quota id" ContainerCreateResp: description: "response returned by daemon when container create successfully" type: "object" required: [Id, Warnings] properties: Id: description: "The ID of the created container" type: "string" x-nullable: false Name: description: "The name of the created container" type: "string" Warnings: description: "Warnings encountered when creating the container" type: "array" x-nullable: false items: type: "string" HostConfig: description: "Container configuration that depends on the host we are running on" type: "object" allOf: - properties: # Applicable to all platforms Binds: type: "array" description: | A list of volume bindings for this container. Each volume binding is a string in one of these forms: - `host-src:container-dest` to bind-mount a host path into the container. Both `host-src`, and `container-dest` must be an _absolute_ path. - `host-src:container-dest:ro` to make the bind mount read-only inside the container. Both `host-src`, and `container-dest` must be an _absolute_ path. - `volume-name:container-dest` to bind-mount a volume managed by a volume driver into the container. `container-dest` must be an _absolute_ path. - `volume-name:container-dest:ro` to mount the volume read-only inside the container. `container-dest` must be an _absolute_ path. items: type: "string" ContainerIDFile: type: "string" description: "Path to a file where the container ID is written" LogConfig: description: "The logging configuration for this container" type: "object" $ref: "#/definitions/LogConfig" RestartPolicy: type: "object" description: "Restart policy to be used to manage the container" $ref: "#/definitions/RestartPolicy" NetworkMode: type: "string" description: "Network mode to use for this container. Supported standard values are: `bridge`, `host`, `none`, and `container:`. Any other value is taken as a custom network's name to which this container should connect to." PortBindings: type: "object" description: "A map of exposed container ports and the host port they should map to." $ref: "#/definitions/PortMap" AutoRemove: type: "boolean" description: "Automatically remove the container when the container's process exits. This has no effect if `RestartPolicy` is set." VolumeDriver: type: "string" description: "Driver that this container uses to mount volumes." VolumesFrom: type: "array" description: "A list of volumes to inherit from another container, specified in the form `[:]`." items: type: "string" CapAdd: type: "array" description: "A list of kernel capabilities to add to the container." items: type: "string" CapDrop: type: "array" description: "A list of kernel capabilities to drop from the container." items: type: "string" Dns: type: "array" description: "A list of DNS servers for the container to use." items: type: "string" DnsOptions: type: "array" description: "A list of DNS options." items: type: "string" DnsSearch: type: "array" description: "A list of DNS search domains." items: type: "string" ExtraHosts: type: "array" description: | A list of hostnames/IP mappings to add to the container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. items: type: "string" GroupAdd: type: "array" description: "A list of additional groups that the container process will run as." items: type: "string" IpcMode: type: "string" description: | IPC sharing mode for the container. Possible values are: - `"none"`: own private IPC namespace, with /dev/shm not mounted - `"private"`: own private IPC namespace - `"shareable"`: own private IPC namespace, with a possibility to share it with other containers - `"container:"`: join another (shareable) container's IPC namespace - `"host"`: use the host system's IPC namespace If not specified, daemon default is used, which can either be `"private"` or `"shareable"`, depending on daemon version and configuration. Cgroup: type: "string" description: "Cgroup to use for the container." Links: type: "array" description: "A list of links for the container in the form `container_name:alias`." items: type: "string" OomScoreAdj: type: "integer" description: | An integer value containing the score given to the container in order to tune OOM killer preferences. The range is in [-1000, 1000]. type: "integer" format: "int" x-nullable: false minimum: -1000 maximum: 1000 PidMode: type: "string" description: | Set the PID (Process) Namespace mode for the container. It can be either: - `"container:"`: joins another container's PID namespace - `"host"`: use the host's PID namespace inside the container Privileged: type: "boolean" description: "Gives the container full access to the host." PublishAllPorts: type: "boolean" description: "Allocates a random host port for all of a container's exposed ports." ReadonlyRootfs: type: "boolean" description: "Mount the container's root filesystem as read only." SecurityOpt: type: "array" description: "A list of string values to customize labels for MLS systems, such as SELinux." items: type: "string" StorageOpt: type: "object" description: | Storage driver options for this container, in the form `{"size": "120G"}`. additionalProperties: type: "string" Tmpfs: type: "object" description: | A map of container directories which should be replaced by tmpfs mounts, and their corresponding mount options. For example: `{ "/run": "rw,noexec,nosuid,size=65536k" }`. additionalProperties: type: "string" UTSMode: type: "string" description: "UTS namespace to use for the container." UsernsMode: type: "string" description: "Sets the usernamespace mode for the container when usernamespace remapping option is enabled." ShmSize: type: "integer" description: "Size of `/dev/shm` in bytes. If omitted, the system uses 64MB." minimum: 0 Sysctls: type: "object" description: | A list of kernel parameters (sysctls) to set in the container. For example: `{"net.ipv4.ip_forward": "1"}` additionalProperties: type: "string" Runtime: type: "string" description: "Runtime to use with this container." # Applicable to Windows ConsoleSize: type: "array" description: "Initial console size, as an `[height, width]` array. (Windows only)" minItems: 2 maxItems: 2 items: type: "integer" minimum: 0 Isolation: type: "string" description: "Isolation technology of the container. (Windows only)" enum: - "default" - "process" - "hyperv" EnableLxcfs: description: "Whether to enable lxcfs." type: "boolean" x-nullable: false Rich: type: "boolean" description: "Whether to start container in rich container mode. (default false)" x-nullable: false RichMode: type: "string" description: "Choose one rich container mode.(default dumb-init)" enum: - "dumb-init" - "sbin-init" - "systemd" InitScript: type: "string" description: "Initial script executed in container. The script will be executed before entrypoint or command" - $ref: "#/definitions/Resources" UpdateConfig: description: "UpdateConfig holds the mutable attributes of a Container. Those attributes can be updated at runtime." allOf: - $ref: "#/definitions/Resources" - properties: RestartPolicy: $ref: "#/definitions/RestartPolicy" Env: description: | A list of environment variables to set inside the container in the form `["VAR=value", ...]`. A variable without `=` is removed from the environment, rather than to have an empty value. type: "array" items: type: "string" Label: description: "List of labels set to container." type: "array" items: type: "string" DiskQuota: type: "object" description: "update disk quota for container" x-nullable: true additionalProperties: type: "string" ContainerUpgradeConfig: description: | ContainerUpgradeConfig is used for API "POST /containers/upgrade". It wraps all kinds of config used in container upgrade. It can be used to encode client params in client and unmarshal request body in daemon side. allOf: - $ref: "#/definitions/ContainerConfig" - type: "object" properties: HostConfig: $ref: "#/definitions/HostConfig" LogConfig: description: "The logging configuration for this container" type: "object" properties: Type: type: "string" x-go-name: "LogDriver" enum: - "json-file" - "syslog" - "journald" - "gelf" - "fluentd" - "awslogs" - "splunk" - "etwlogs" - "none" Config: type: "object" x-go-name: "LogOpts" additionalProperties: type: "string" Resources: description: "A container's resources (cgroups config, ulimits, etc)" type: "object" required: [CPUShares, Memory, CgroupParent, BlkioWeight, CPUPeriod, CPUQuota, CpuRealtimePeriod, CpuRealtimeRuntime, CpusetCpus, CpusetMems, DeviceCgroupRules, KernelMemory, MemoryReservation, MemorySwap, MemorySwappiness, NanoCPUs, OomKillDisable, PidsLimit, CpuCount, CpuPercent, IOMaximumIOps, IOMaximumBandwidth, IntelRdtL3Cbm, ScheLatSwitch, MemoryWmarkRatio, MemoryExtra, MemoryForceEmptyCtl] properties: # Applicable to UNIX platforms CgroupParent: description: "Path to `cgroups` under which the container's `cgroup` is created. If the path is not absolute, the path is considered to be relative to the `cgroups` path of the init process. Cgroups are created if they do not already exist." type: "string" x-nullable: false BlkioWeight: description: "Block IO weight (relative weight), need CFQ IO Scheduler enable." type: "integer" format: "uint16" x-nullable: false minimum: 0 maximum: 1000 BlkioWeightDevice: description: | Block IO weight (relative device weight) in the form `[{"Path": "device_path", "Weight": weight}]`. type: "array" items: $ref: "#/definitions/WeightDevice" BlkioDeviceReadBps: description: | Limit read rate (bytes per second) from a device, in the form `[{"Path": "device_path", "Rate": rate}]`. type: "array" items: $ref: "#/definitions/ThrottleDevice" BlkioDeviceWriteBps: description: | Limit write rate (bytes per second) to a device, in the form `[{"Path": "device_path", "Rate": rate}]`. type: "array" items: $ref: "#/definitions/ThrottleDevice" BlkioDeviceReadIOps: description: | Limit read rate (IO per second) from a device, in the form `[{"Path": "device_path", "Rate": rate}]`. type: "array" items: $ref: "#/definitions/ThrottleDevice" BlkioDeviceWriteIOps: description: | Limit write rate (IO per second) to a device, in the form `[{"Path": "device_path", "Rate": rate}]`. type: "array" items: $ref: "#/definitions/ThrottleDevice" CPUShares: description: "An integer value representing this container's relative CPU weight versus other containers." type: "integer" x-nullable: false CPUPeriod: description: | CPU CFS (Completely Fair Scheduler) period. The length of a CPU period in microseconds. type: "integer" format: "int64" minimum: 1000 maximum: 1000000 x-nullable: false CPUQuota: description: | CPU CFS (Completely Fair Scheduler) quota. Microseconds of CPU time that the container can get in a CPU period." type: "integer" format: "int64" minimum: 1000 x-nullable: false CpuRealtimePeriod: description: "The length of a CPU real-time period in microseconds. Set to 0 to allocate no time allocated to real-time tasks." type: "integer" format: "int64" x-nullable: false CpuRealtimeRuntime: description: "The length of a CPU real-time runtime in microseconds. Set to 0 to allocate no time allocated to real-time tasks." type: "integer" format: "int64" x-nullable: false CpusetCpus: description: "CPUs in which to allow execution (e.g., `0-3`, `0,1`)" type: "string" example: "0-3" x-nullable: false CpusetMems: description: "Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems." type: "string" x-nullable: false Devices: description: "A list of devices to add to the container." type: "array" items: $ref: "#/definitions/DeviceMapping" DeviceCgroupRules: description: "a list of cgroup rules to apply to the container" type: "array" items: type: "string" example: "c 13:* rwm" KernelMemory: description: "Kernel memory limit in bytes." type: "integer" format: "int64" x-nullable: false Memory: description: "Memory limit in bytes." type: "integer" default: 0 x-nullable: false MemoryReservation: description: "Memory soft limit in bytes." type: "integer" format: "int64" x-nullable: false MemorySwap: description: "Total memory limit (memory + swap). Set as `-1` to enable unlimited swap." type: "integer" format: "int64" x-nullable: false MemorySwappiness: description: "Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100." type: "integer" format: "int64" minimum: 0 maximum: 100 NanoCPUs: description: "CPU quota in units of 10-9 CPUs." type: "integer" format: "int64" x-nullable: false OomKillDisable: description: "Disable OOM Killer for the container." type: "boolean" x-nullable: true PidsLimit: description: | Tune a container's pids limit. Set -1 for unlimited. Only on Linux 4.4 does this parameter support. type: "integer" format: "int64" x-nullable: false Ulimits: description: | A list of resource limits to set in the container. For example: `{"Name": "nofile", "Soft": 1024, "Hard": 2048}`" type: "array" items: $ref: "#/definitions/Ulimit" # Applicable to Windows CpuCount: description: | The number of usable CPUs (Windows only). On Windows Server containers, the processor resource controls are mutually exclusive. The order of precedence is `CPUCount` first, then `CPUShares`, and `CPUPercent` last. type: "integer" format: "int64" x-nullable: false CpuPercent: description: | The usable percentage of the available CPUs (Windows only). On Windows Server containers, the processor resource controls are mutually exclusive. The order of precedence is `CPUCount` first, then `CPUShares`, and `CPUPercent` last. type: "integer" format: "int64" x-nullable: false IOMaximumIOps: description: "Maximum IOps for the container system drive (Windows only)" type: "integer" format: "uint64" x-nullable: false IOMaximumBandwidth: description: "Maximum IO in bytes per second for the container system drive (Windows only)" type: "integer" format: "uint64" x-nullable: false IntelRdtL3Cbm: description: "IntelRdtL3Cbm specifies settings for Intel RDT/CAT group that the container is placed into to limit the resources (e.g., L3 cache) the container has available." type: "string" x-nullable: false # applicable to AliKenerl 4.9 ScheLatSwitch: description: "ScheLatSwitch enables scheduler latency count in cpuacct" type: "integer" format: "int64" x-nullable: false minimum: 0 maximum: 1 x-nullable: false MemoryWmarkRatio: description: | MemoryWmarkRatio is an integer value representing this container's memory low water mark percentage. The value of memory low water mark is memory.limit_in_bytes * MemoryWmarkRatio. The range is in [0, 100]. type: "integer" format: "int64" x-nullable: true minimum: 0 maximum: 100 MemoryExtra: description: | MemoryExtra is an integer value representing this container's memory high water mark percentage. The range is in [0, 100]. type: "integer" format: "int64" x-nullable: true minimum: 0 maximum: 100 MemoryForceEmptyCtl: description: "MemoryForceEmptyCtl represents whether to reclaim the page cache when deleting cgroup." type: "integer" format: "int64" x-nullable: false minimum: 0 maximum: 1 ThrottleDevice: type: "object" properties: Path: description: "Device path" type: "string" Rate: description: "Rate" type: "integer" format: "uint64" x-nullable: false minimum: 0 WeightDevice: type: "object" description: "Weight for BlockIO Device" properties: Path: description: "Weight Device" type: "string" Weight: type: "integer" format: "uint16" x-nullable: false minimum: 0 DeviceMapping: type: "object" description: "A device mapping between the host and container" properties: PathOnHost: description: "path on host of the device mapping" type: "string" PathInContainer: description: "path in container of the device mapping" type: "string" CgroupPermissions: description: "cgroup permissions of the device" type: "string" example: PathOnHost: "/dev/deviceName" PathInContainer: "/dev/deviceName" CgroupPermissions: "mrw" Ulimit: type: "object" description: "A list of resource limits" properties: Name: description: "Name of ulimit" type: "string" Soft: description: "Soft limit" type: "integer" Hard: description: "Hard limit" type: "integer" Container: description: | an array of Container contains response of Engine API: GET "/containers/json" type: "object" properties: Id: description: "Container ID" type: "string" Names: type: "array" items: type: "string" example: - "container_1" - "container_2" Image: type: "string" ImageID: type: "string" Command: type: "string" Created: description: "Created time of container in daemon." type: "integer" format: "int64" SizeRw: type: "integer" format: "int64" SizeRootFs: type: "integer" format: "int64" Labels: type: "object" additionalProperties: type: "string" State: type: "string" Status: type: "string" HostConfig: description: | In Moby's API, HostConfig field in Container struct has following type struct { NetworkMode string `json:",omitempty"` } In Pouch, we need to pick runtime field in HostConfig from daemon side to judge runtime type, So Pouch changes this type to be the complete HostConfig. Incompatibility exists, ATTENTION. $ref: "#/definitions/HostConfig" x-nullable: false Mounts: type: "array" description: "Set of mount point in a container." items: $ref: "#/definitions/MountPoint" NetworkSettings: type: "object" properties: Networks: additionalProperties: $ref: "#/definitions/EndpointSettings" x-nullable: true NetworkingConfig: description: "Configuration for a network used to create a container." type: "object" properties: EndpointsConfig: additionalProperties: $ref: "#/definitions/EndpointSettings" x-nullable: true EndpointSettings: description: "Configuration for a network endpoint." type: "object" properties: # Configurations IPAMConfig: $ref: "#/definitions/EndpointIPAMConfig" x-nullable: true Links: type: "array" items: type: "string" example: - "container_1" - "container_2" Aliases: type: "array" items: type: "string" example: - "server_x" - "server_y" # Operational data NetworkID: description: | Unique ID of the network. type: "string" example: "08754567f1f40222263eab4102e1c733ae697e8e354aa9cd6e18d7402835292a" EndpointID: description: | Unique ID for the service endpoint in a Sandbox. type: "string" example: "b88f5b905aabf2893f3cbc4ee42d1ea7980bbc0a92e2c8922b1e1795298afb0b" Gateway: description: | Gateway address for this network. type: "string" example: "172.17.0.1" IPAddress: description: | IPv4 address. type: "string" example: "172.17.0.4" IPPrefixLen: description: | Mask length of the IPv4 address. type: "integer" example: 16 IPv6Gateway: description: | IPv6 gateway address. type: "string" example: "2001:db8:2::100" GlobalIPv6Address: description: | Global IPv6 address. type: "string" example: "2001:db8::5689" GlobalIPv6PrefixLen: description: | Mask length of the global IPv6 address. type: "integer" format: "int64" example: 64 MacAddress: description: | MAC address for the endpoint on this network. type: "string" example: "02:42:ac:11:00:04" DriverOpts: description: | DriverOpts is a mapping of driver options and values. These options are passed directly to the driver and are driver specific. type: "object" x-nullable: true additionalProperties: type: "string" example: com.example.some-label: "some-value" com.example.some-other-label: "some-other-value" EndpointIPAMConfig: description: "IPAM configurations for the endpoint" type: "object" properties: IPv4Address: description: "ipv4 address" type: "string" IPv6Address: description: "ipv6 address" type: "string" LinkLocalIPs: description: "link to the list of local ip" type: "array" x-nullable: false items: type: "string" NetworkDisconnect: description: "Parameters of network disconnect" type: "object" properties: Container: type: "string" description: "The ID or name of the container to disconnect from the network." Force: type: "boolean" description: "Force the container to disconnect from the network." ImageInfo: description: "An object containing all details of an image at API side" type: "object" properties: Id: description: "ID of an image." type: "string" x-nullable: false RepoTags: description: "repository with tag." type: "array" items: type: "string" RepoDigests: description: "repository with digest." type: "array" items: type: "string" CreatedAt: description: "time of image creation." type: "string" x-nullable: false Size: description: "size of image's taking disk space." type: "integer" x-nullable: false Config: $ref: "#/definitions/ContainerConfig" Architecture: description: "the CPU architecture." type: "string" x-nullable: false Os: description: "the name of the operating system." type: "string" x-nullable: false RootFS: description: "the rootfs key references the layer content addresses used by the image." type: "object" required: [Type] properties: Type: description: "type of the rootfs" type: "string" x-nullable: false Layers: description: "an array of layer content hashes" type: "array" items: type: "string" BaseLayer: description: "the base layer content hash." type: "string" SearchResultItem: type: "object" description: "search result item in search results." properties: description: type: "string" description: "description just shows the description of this image" is_official: type: "boolean" description: "is_official shows if this image is marked official." is_automated: type: "boolean" description: "is_automated means whether this image is automated." name: type: "string" description: "name represents the name of this image" star_count: type: "integer" description: "star_count refers to the star count of this image." VolumeInfo: type: "object" description: "Volume represents the configuration of a volume for the container." properties: Name: description: "Name is the name of the volume." type: "string" Driver: description: "Driver is the Driver name used to create the volume." type: "string" Mountpoint: description: "Mountpoint is the location on disk of the volume." type: "string" CreatedAt: type: "string" format: "dateTime" description: "Date/Time the volume was created." Status: description: "Status provides low-level status information about the volume." type: "object" additionalProperties: type: "object" enum: - {} default: {} Labels: description: "Labels is metadata specific to the volume." type: "object" additionalProperties: type: "string" Scope: description: | Scope describes the level at which the volume exists (e.g. `global` for cluster-wide or `local` for machine level) type: "string" VolumeCreateConfig: description: "config used to create a volume" type: "object" properties: Name: description: "The new volume's name. If not specified, Pouch generates a name." type: "string" x-nullable: false Driver: description: "Name of the volume driver to use." type: "string" default: "local" x-nullable: false DriverOpts: description: "A mapping of driver options and values. These options are passed directly to the driver and are driver specific." type: "object" additionalProperties: type: "string" Labels: description: "User-defined key/value metadata." type: "object" additionalProperties: type: "string" example: Name: "tardis" Labels: com.example.some-label: "some-value" com.example.some-other-label: "some-other-value" Driver: "custom" VolumeListResp: type: "object" required: [Volumes, Warnings] properties: Volumes: type: "array" x-nullable: false description: "List of volumes" items: $ref: "#/definitions/VolumeInfo" Warnings: type: "array" x-nullable: false description: "Warnings that occurred when fetching the list of volumes" items: type: "string" ExecCreateConfig: type: "object" description: is a small subset of the Config struct that holds the configuration. properties: User: type: "string" description: "User that will run the command" x-nullable: false Privileged: type: "boolean" description: "Is the container in privileged mode" Tty: type: "boolean" description: "Attach standard streams to a tty" AttachStdin: type: "boolean" description: "Attach the standard input, makes possible user interaction" AttachStderr: type: "boolean" description: "Attach the standard error" AttachStdout: type: "boolean" description: "Attach the standard output" Detach: type: "boolean" description: "Execute in detach mode" DetachKeys: type: "string" description: "Escape keys for detach" Cmd: type: "array" description: "Execution commands and args" minItems: 1 items: type: "string" ContainerProcessList: description: OK Response to ContainerTop operation type: "object" properties: Titles: description: "The ps column titles" type: "array" items: type: "string" Processes: description: "Each process running in the container, where each is process is an array of values corresponding to the titles" type: "array" items: type: "array" items: type: "string" ExecCreateResp: type: "object" description: contains response of Remote API POST "/containers/{name:.*}/exec". properties: Id: type: "string" description: ID is the exec ID ExecStartConfig: type: "object" description: ExecStartConfig is a temp struct used by execStart. properties: Detach: description: ExecStart will first check if it's detached type: "boolean" Tty: description: Check if there's a tty type: "boolean" example: Detach: false Tty: false ContainerExecInspect: type: "object" description: holds information about a running process started. required: [ID, Running, ExitCode, ProcessConfig, OpenStdin, OpenStderr, OpenStdout, CanRemove, ContainerID, DetachKeys] properties: ID: x-nullable: false type: "string" description: "The ID of this exec" Running: x-nullable: false type: "boolean" ExitCode: x-nullable: false type: "integer" description: "The last exit code of this container" ProcessConfig: x-nullable: false $ref: "#/definitions/ProcessConfig" OpenStdin: x-nullable: false type: "boolean" OpenStderr: x-nullable: false type: "boolean" OpenStdout: x-nullable: false type: "boolean" CanRemove: x-nullable: false type: "boolean" ContainerID: x-nullable: false type: "string" description: "The ID of this container" DetachKeys: x-nullable: false type: "string" ProcessConfig: type: "object" description: ExecProcessConfig holds information about the exec process. required: [privileged, user, tty, entrypoint, arguments] properties: privileged: x-nullable: false type: "boolean" user: x-nullable: false type: "string" tty: x-nullable: false type: "boolean" entrypoint: x-nullable: false type: "string" arguments: x-nullable: false type: "array" items: type: "string" ContainerJSON: description: | ContainerJSON contains response of Engine API: GET "/containers/{id}/json" type: "object" properties: Id: description: "The ID of the container" type: "string" Created: description: "The time the container was created" type: "string" Path: description: "The path to the command being run" type: "string" Args: description: "The arguments to the command being run" type: "array" items: type: "string" State: description: "The state of the container." $ref: "#/definitions/ContainerState" Image: description: "The container's image" type: "string" ResolvConfPath: type: "string" HostnamePath: type: "string" HostsPath: type: "string" LogPath: type: "string" Name: type: "string" RestartCount: type: "integer" Driver: type: "string" MountLabel: type: "string" ProcessLabel: type: "string" AppArmorProfile: type: "string" ExecIDs: description: "exec ids of container" type: "array" items: type: "string" HostConfig: $ref: "#/definitions/HostConfig" SizeRw: description: "The size of files that have been created or changed by this container." type: "integer" format: "int64" x-nullable: true SizeRootFs: description: "The total size of all the files in this container." type: "integer" format: "int64" x-nullable: true Config: $ref: "#/definitions/ContainerConfig" Snapshotter: $ref: "#/definitions/SnapshotterData" GraphDriver: $ref: "#/definitions/GraphDriverData" Mounts: type: "array" description: "Set of mount point in a container." items: $ref: "#/definitions/MountPoint" NetworkSettings: description: "NetworkSettings exposes the network settings in the API." $ref: "#/definitions/NetworkSettings" ContainerState: type: "object" required: [StartedAt, FinishedAt] properties: Status: $ref: "#/definitions/Status" Running: description: | Whether this container is running. Note that a running container can be _paused_. The `Running` and `Paused` booleans are not mutually exclusive: When pausing a container (on Linux), the cgroups freezer is used to suspend all processes in the container. Freezing the process requires the process to be running. As a result, paused containers are both `Running` _and_ `Paused`. Use the `Status` field instead to determine if a container's state is "running". type: "boolean" Paused: description: "Whether this container is paused." type: "boolean" Restarting: description: "Whether this container is restarting." type: "boolean" OOMKilled: description: "Whether this container has been killed because it ran out of memory." type: "boolean" Dead: description: "Whether this container is dead." type: "boolean" Pid: description: "The process ID of this container" type: "integer" ExitCode: description: "The last exit code of this container" type: "integer" Error: description: "The error message of this container" type: "string" StartedAt: description: "The time when this container was last started." type: "string" x-nullable: false FinishedAt: description: "The time when this container last exited." type: "string" x-nullable: false ContainerLogsOptions: type: "object" properties: ShowStdout: description: "Return logs from `stdout`" type: "boolean" ShowStderr: description: "Return logs from `stderr`" type: "boolean" Since: description: "Only return logs after this time, as a UNIX timestamp" type: "string" Until: description: "Only reture logs before this time, as a UNIX timestamp" type: "string" Timestamps: description: "Add timestamps to every log line" type: "boolean" Follow: description: "Return logs as a stream" type: "boolean" Tail: description: "Only reture this number of log lines from the end of the logs. Specify as an integer or `all` to output all log lines." type: "string" Details: description: "Show extra details provided to logs" type: "boolean" description: The parameters to filter the log. Status: description: The status of the container. For example, "running" or "exited". type: "string" enum: ["created", "running", "stopped", "paused", "restarting", "removing", "exited", "dead"] SnapshotterData: description: "Information about a container's snapshotter." type: "object" required: [Name, Data] properties: Name: type: "string" x-nullable: false Data: type: "object" x-nullable: false additionalProperties: type: "string" GraphDriverData: description: "Information about a container's graph driver." type: "object" required: [Name, Data] properties: Name: type: "string" x-nullable: false Data: type: "object" x-nullable: false additionalProperties: type: "string" MountPoint: type: "object" description: "A mount point inside a container" x-nullable: false properties: Type: type: "string" ID: type: "string" Name: type: "string" Source: type: "string" Destination: type: "string" Driver: type: "string" Mode: type: "string" RW: type: "boolean" CopyData: type: "boolean" Named: type: "boolean" Replace: type: "string" Propagation: type: "string" NetworkSettings: description: "NetworkSettings exposes the network settings in the API." type: "object" properties: Bridge: description: Name of the network'a bridge (for example, `pouch-br`). type: "string" example: "pouch-br" SandboxID: description: SandboxID uniquely represents a container's network stack. type: "string" example: "9d12daf2c33f5959c8bf90aa513e4f65b561738661003029ec84830cd503a0c3" HairpinMode: description: "Indicates if hairpin NAT should be enabled on the virtual interface" type: "boolean" example: false LinkLocalIPv6Address: description: "IPv6 unicast address using the link-local prefix" type: "string" example: "fe80::42:acff:fe11:1" LinkLocalIPv6PrefixLen: description: Prefix length of the IPv6 unicast address. type: "integer" example: "64" Ports: $ref: "#/definitions/PortMap" SandboxKey: description: SandboxKey identifies the sandbox type: "string" example: "/var/run/pouch/netns/8ab54b426c38" # TODO is SecondaryIPAddresses actually used? SecondaryIPAddresses: description: "" type: "array" items: $ref: "#/definitions/IPAddress" x-nullable: true # TODO is SecondaryIPv6Addresses actually used? SecondaryIPv6Addresses: description: "" type: "array" items: $ref: "#/definitions/IPAddress" x-nullable: true Networks: description: "Information about all networks that the container is connected to" type: "object" additionalProperties: $ref: "#/definitions/EndpointSettings" x-nullable: true IPAddress: description: Address represents an IPv4 or IPv6 IP address. type: "object" properties: Addr: description: IP address. type: "string" PrefixLen: description: Mask length of the IP address. type: "integer" PortMap: description: | PortMap describes the mapping of container ports to host ports, using the container's port-number and protocol as key in the format `/`, for example, `80/udp`. If a container's port is mapped for both `tcp` and `udp`, two separate entries are added to the mapping table. type: "object" additionalProperties: type: "array" items: $ref: "#/definitions/PortBinding" example: "443/tcp": - HostIp: "127.0.0.1" HostPort: "4443" "80/tcp": - HostIp: "0.0.0.0" HostPort: "80" - HostIp: "0.0.0.0" HostPort: "8080" "80/udp": - HostIp: "0.0.0.0" HostPort: "80" "2377/tcp": null PortBinding: description: "PortBinding represents a binding between a host IP address and a host port" type: "object" x-nullable: true properties: HostIp: description: "Host IP address that the container's port is mapped to." type: "string" example: "127.0.0.1" HostPort: description: "Host port number that the container's port is mapped to." type: "string" example: "4443" RestartPolicy: description: "Define container's restart policy" type: "object" properties: Name: type: "string" MaximumRetryCount: type: "integer" NetworkConnect: type: "object" description: "contains the request for the remote API: POST /networks/{id:.*}/connect" properties: Container: type: "string" description: "The ID or name of the container to connect to the network." EndpointConfig: $ref: "#/definitions/EndpointSettings" NetworkCreateConfig: type: "object" description: "contains the request for the remote API: POST /networks/create" allOf: - properties: Name: description: "Name is the name of the network." type: "string" - $ref: "#/definitions/NetworkCreate" NetworkCreateResp: type: "object" description: "contains the response for the remote API: POST /networks/create" properties: Id: description: "ID is the id of the network." type: "string" Warning: description: "Warning means the message of create network result." type: "string" NetworkCreate: type: "object" description: "is the expected body of the \"create network\" http request message" properties: CheckDuplicate: type: "boolean" description: "CheckDuplicate is used to check the network is duplicate or not." Driver: type: "string" description: "Driver means the network's driver." EnableIPv6: type: "boolean" IPAM: type: "object" $ref: "#/definitions/IPAM" Internal: type: "boolean" description: "Internal checks the network is internal network or not." Options: type: "object" additionalProperties: type: "string" Labels: type: "object" additionalProperties: type: "string" NetworkInspectResp: type: "object" description: "is the expected body of the 'GET networks/{id}'' http request message" properties: Name: type: "string" description: "Name is the requested name of the network" Id: type: "string" description: "ID uniquely identifies a network on a single machine" Scope: type: "string" description: "Scope describes the level at which the network exists." Driver: type: "string" description: "Driver means the network's driver." EnableIPv6: type: "boolean" description: "EnableIPv6 represents whether to enable IPv6." IPAM: type: "object" description: "IPAM is the network's IP Address Management." $ref: "#/definitions/IPAM" Internal: type: "boolean" description: "Internal checks the network is internal network or not." Options: type: "object" description: "Options holds the network specific options to use for when creating the network." additionalProperties: type: "string" Labels: type: "object" description: "Labels holds metadata specific to the network being created." additionalProperties: type: "string" NetworkResource: type: "object" description: "NetworkResource is the body of the \"get network\" http response message" properties: Name: description: "Name is the requested name of the network" type: "string" Id: description: "ID uniquely identifies a network on a single machine" type: "string" Scope: description: "Scope describes the level at which the network exists (e.g. `global` for cluster-wide or `local` for machine level)" type: "string" Driver: description: "Driver is the Driver name used to create the network (e.g. `bridge`, `overlay`)" type: "string" EnableIPv6: description: "EnableIPv6 represents whether to enable IPv6" type: "boolean" IPAM: description: "" type: "object" $ref: "#/definitions/IPAM" Internal: description: "Internal represents if the network is used internal only" type: "boolean" Containers: description: "Containers contains endpoints belonging to the network" type: "object" IndexConfigs: type: "object" additionalProperties: $ref: "#/definitions/EndpointResource" Options: description: "Options holds the network specific options to use for when creating the network" type: "object" x-nullable: true additionalProperties: type: "string" example: com.example.some-options: "some-option" com.example.some-other-options: "some-other-option" Labels: description: "Labels holds metadata specific to the network being created" type: "object" x-nullable: true additionalProperties: type: "string" example: com.example.some-label: "some-label" com.example.some-other-label: "some-other-label" EndpointResource: type: "object" description: "NetworkResource is the body of the \"get network\" http response message" properties: Name: description: "Name is the requested name of the network" type: "string" EndpointID: description: "EndpointID represents the endpoint's id" type: "string" MacAddress: description: "MacAddress represents the enpoint's mac address" type: "string" IPv4Address: description: "IPv4Address represents the enpoint's ipv4 address" type: "string" IPv6Address: description: "IPv4Address represents the enpoint's ipv6 address" type: "string" IPAM: type: "object" description: "represents IP Address Management" properties: Driver: type: "string" Options: type: "object" additionalProperties: type: "string" Config: type: "array" items: $ref: '#/definitions/IPAMConfig' IPAMConfig: description: "represents IPAM configurations" type: "object" x-nullable: false properties: Subnet: description: "subnet address for network" type: "string" IPRange: description: "sub ip range in sub-network" type: "string" Gateway: description: "gateway for sub-network" type: "string" AuxAddress: description: "aux address in sub-network" type: "object" additionalProperties: type: "string" ResizeOptions: description: "options of resizing container tty size" type: "object" properties: Height: type: "integer" Width: type: "integer" ContainerRemoveOptions: description: "options of remove container" type: "object" properties: Force: type: "boolean" Volumes: type: "boolean" Link: type: "boolean" ContainerListOptions: description: | options of list container, filters (a `map[string][]string`) to process on the container list. Available filters: - `id=container-id` - `name=container-name` - `status=running` - `label=key` or `label=key=value` - `network=container-network` - `volume=volume-id` type: "object" properties: All: type: "boolean" Since: type: "string" Before: type: "string" Limit: type: "integer" Filter: type: "object" additionalProperties: type: "array" items: type: "string" parameters: id: name: id in: path required: true description: ID or name of the container type: string imageid: name: imageid in: path required: true description: Image name or id type: string responses: 401ErrorResponse: description: An unexpected 401 error occurred. schema: $ref: "#/definitions/Error" 404ErrorResponse: description: An unexpected 404 error occurred. schema: $ref: "#/definitions/Error" 500ErrorResponse: description: An unexpected server error occurred. schema: $ref: "#/definitions/Error" golang-github-go-openapi-analysis-0.15.0/fixtures/bugs/1602/other-invalid-pointers.yaml000066400000000000000000000034471332100444700307020ustar00rootroot00000000000000--- swagger: "2.0" schemes: - "http" - "https" basePath: "/v1.24" info: title: Edge cases with JSON pointers version: "1.24" description: | Explore edge cases with invalid pointers paths: /invalid: get: responses: 401: $ref: "#/responses/401ErrorResponse" 404: $ref: "#/responses/404ErrorResponse" 500: $ref: "#/responses/500ErrorResponse" definitions: invalidRefInObject: type: object properties: prop1: type: integer prop2: $ref: "#/definitions/noWhere" invalidRefInMap: type: object additionalProperties: $ref: "#/definitions/noWhere" invalidRefInArray: type: array items: $ref: "#/definitions/noWhere" indirectToInvalidRef: $ref: "#/definitions/invalidRefInArray" invalidRefInTuple: type: array items: - type: integer - $ref: "#/definitions/noWhere" parameters: bodyWithRef: name: bodyWithRefParam in: body required: true schema: $ref: "#/definitions/Error" anotherBodyWithRef: name: anotherBodyWithRefParam in: body required: true schema: $ref: "#/definitions/Error" funnyParam: name: funnyParam in: body required: true schema: $ref: "#/responses/500ErrorResponse/schema" responses: 401ErrorResponse: description: An unexpected 401 error occurred. schema: $ref: "#/definitions/Error" 404ErrorResponse: description: An unexpected 404 error occurred. schema: $ref: "#/definitions/Error" 500ErrorResponse: description: An unexpected server error occurred. schema: $ref: "#/definitions/Error" funnyResponse: description: An unexpected server error occurred. schema: $ref: "#/responses/401ErrorResponse/schema" golang-github-go-openapi-analysis-0.15.0/fixtures/bugs/bitbucket.json000066400000000000000000007422251332100444700256730ustar00rootroot00000000000000{ "info": { "termsOfService": "https://www.atlassian.com/end-user-agreement", "version": "2.0", "contact": { "url": "https://bitbucket.org/support", "name": "Bitbucket Support", "email": "support@bitbucket.org" }, "description": "Code against the Bitbucket API to automate simple tasks, embed Bitbucket data into your own site, build mobile or desktop apps, or even add custom UI add-ons into Bitbucket itself using the Connect framework.", "title": "Bitbucket API" }, "paths": { "/repositories/{username}/{repo_slug}/refs/branches/{name}": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "name", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "refs" ] } }, "/teams/{username}/following": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" } ], "get": { "security": [ { "oauth2": [ "account" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the list of accounts this team is following.", "responses": { "200": { "description": "A paginated list of user objects.", "schema": { "$ref": "#/definitions/paginated_users" } }, "404": { "description": "If no team exists for the specified name, or if the specified account is a personal account, not a team account.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The team's username", "name": "username", "in": "path" } ], "tags": [ "teams" ] } }, "/repositories/{username}/{repo_slug}/commit/{sha}/comments/{comment_id}": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "sha", "in": "path" }, { "required": true, "type": "string", "name": "comment_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the specified commit comment.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "commits" ] } }, "/repositories/{username}/{repo_slug}/hooks": { "post": { "security": [ { "oauth2": [ "webhook" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Creates a new webhook on the specified repository.", "responses": { "201": { "description": "If the webhook was registered successfully.", "schema": { "$ref": "#/definitions/webhook_subscription" } }, "403": { "description": "If the authenticated user does not have permission to install webhooks on the specified repository.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the repository does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "webhooks" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "webhook" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns a paginated list of webhooks installed on this repository.", "responses": { "200": { "description": "The paginated list of installed webhooks.", "schema": { "$ref": "#/definitions/paginated_webhook_subscriptions" } }, "403": { "description": "If the authenticated user does not have permission to access the webhooks.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the repository does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "webhooks" ] } }, "/repositories/{username}/{repo_slug}/issues/{issue_id}/attachments/{path}": { "delete": { "security": [ { "oauth2": [ "issue:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Deletes an attachment.", "responses": { "401": { "description": "If the issue tracker is private and the request was not authenticated." }, "404": { "description": "If the specified repository, issue, or attachment does not exist, or if the repository doesn't have the issue tracker enabled.", "schema": { "$ref": "#/definitions/error" } }, "204": { "description": "Indicates that the deletion was successful" } }, "parameters": [], "tags": [ "issue_tracker" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "issue_id", "in": "path" }, { "required": true, "type": "string", "name": "path", "in": "path" } ], "get": { "security": [ { "oauth2": [ "issue" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the contents of the specified file attachment.\n\nNote that this endpoint does not return a JSON response, but instead\nreturns a redirect pointing to the actual file that in turn will return\nthe raw contents.\n\nThe redirect URL contains a one-time token that has a limited lifetime.\nAs a result, the link should not be persisted, stored, or shared.", "responses": { "401": { "description": "If the issue tracker is private and the request was not authenticated." }, "404": { "description": "If the specified repository or issue does not exist, or if the repository doesn't have the issue tracker enabled.", "schema": { "$ref": "#/definitions/error" } }, "302": { "headers": { "Location": { "type": "string" } }, "description": "A redirect to the file's contents" } }, "parameters": [], "tags": [ "issue_tracker" ] } }, "/repositories/{username}/{repo_slug}/versions/{version_id}": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "version_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "issue" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the specified issue tracker version object.", "responses": { "200": { "description": "The specified version object.", "schema": { "$ref": "#/definitions/version" } }, "404": { "description": "If the specified repository or version does not exist, or if the repository doesn't have the issue tracker enabled.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "integer", "description": "The version's id", "name": "version_id", "in": "path" } ], "tags": [ "issue_tracker" ] } }, "/repositories/{username}/{repo_slug}/issues/{issue_id}/vote": { "put": { "security": [ { "oauth2": [ "issue", "account:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Vote for this issue.\n\nTo cast your vote, do an empty PUT. The 204 status code indicates that\nthe operation was successful.", "responses": { "401": { "description": "When the request wasn't authenticated.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "When the issue does not exist, the repo does not exist, or when the repos does not have an issue tracker.", "schema": { "$ref": "#/definitions/error" } }, "204": { "description": "Indicating the authenticated user has cast their vote successfully.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "integer", "description": "The issue's id", "name": "issue_id", "in": "path" } ], "tags": [ "issue_tracker" ] }, "delete": { "security": [ { "oauth2": [ "issue:write", "account:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Retract your vote.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "integer", "description": "The issue's id", "name": "issue_id", "in": "path" } ], "tags": [ "issue_tracker" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "issue_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "issue", "account" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Check whether the authenticated user has voted for this issue.\nA 204 status code indicates that the user has voted, while a 404\nimplies they haven't.", "responses": { "401": { "description": "When the request wasn't authenticated.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the authenticated user has not voted for this issue, or when the repo does not exist, or does not have an issue tracker.", "schema": { "$ref": "#/definitions/error" } }, "204": { "description": "If the authenticated user has not voted for this issue.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "integer", "description": "The issue's id", "name": "issue_id", "in": "path" } ], "tags": [ "issue_tracker" ] } }, "/repositories/{username}/{repo_slug}/milestones": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "issue" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the milestones that have been defined in the issue tracker.\n\nThis resource is only available on repositories that have the issue\ntracker enabled.", "responses": { "200": { "description": "The milestones that have been defined in the issue tracker.", "schema": { "$ref": "#/definitions/paginated_milestones" } }, "404": { "description": "If the specified repository does not exist, or if the repository doesn't have the issue tracker enabled.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "issue_tracker" ] } }, "/repositories/{username}/{repo_slug}/components": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "issue" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the components that have been defined in the issue tracker.\n\nThis resource is only available on repositories that have the issue\ntracker enabled.", "responses": { "200": { "description": "The components that have been defined in the issue tracker.", "schema": { "$ref": "#/definitions/paginated_components" } }, "404": { "description": "If the specified repository does not exist, or if the repository doesn't have the issue tracker enabled.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "issue_tracker" ] } }, "/account/{username}/addons/{encoded_context_id}/refresh": { "put": { "security": [ { "oauth2": [ "account:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "encoded_context_id", "in": "path" } ] }, "/addon/linkers/{linker_key}/values": { "put": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] }, "post": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] }, "delete": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] }, "parameters": [ { "required": true, "type": "string", "name": "linker_key", "in": "path" } ], "get": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] } }, "/repositories/{username}/{repo_slug}/refs/branches": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "refs" ] } }, "/hook_events/{subject_type}": { "parameters": [ { "required": true, "type": "string", "name": "subject_type", "in": "path" } ], "get": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns a paginated list of all valid webhook events for the\nspecified entity.\n\nThis is public data that does not require any scopes or authentication.", "responses": { "200": { "description": "A paginated list of webhook types available to subscribe on.", "schema": { "$ref": "#/definitions/paginated_hook_events" } }, "404": { "description": "If an invalid `{subject_type}` value was specified.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "enum": [ "user", "repository", "team" ], "name": "subject_type", "required": true, "in": "path", "type": "string", "description": "A resource or subject type." } ], "tags": [ "webhooks" ] } }, "/users/{username}/followers": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" } ], "get": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the list of accounts that are following this team.", "responses": { "200": { "description": "A paginated list of user objects.", "schema": { "$ref": "#/definitions/paginated_users" } }, "404": { "description": "If no account exists for the specified name, or if the specified account is a team account, not a personal account.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The account's username", "name": "username", "in": "path" } ], "tags": [ "users" ] } }, "/repositories/{username}/{repo_slug}/default-reviewers": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository:admin" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the repository's default reviewers.\n\nThese are the users that are automatically added as reviewers on every\nnew pull request that is created.", "responses": { "200": { "description": "The paginated list of default reviewers" } }, "parameters": [], "tags": [ "pullrequests" ] } }, "/repositories/{username}/{repo_slug}/downloads/{filename}": { "delete": { "security": [ { "oauth2": [ "repository:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Deletes the specified download artifact from the repository.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "downloads" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "filename", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Return a redirect to the contents of a download artifact.\n\nThis endpoint returns the actual file contents and not the artifact's\nmetadata.\n\n $ curl -s -L https://api.bitbucket.org/2.0/repositories/evzijst/git-tests/downloads/hello.txt\n Hello World", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "downloads" ] } }, "/repositories/{username}/{repo_slug}/commit/{node}/statuses/build/{key}": { "put": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "node", "in": "path" }, { "required": true, "type": "string", "name": "key", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [] } }, "/repositories/{username}/{repo_slug}/watchers": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns a paginated list of all the watchers on the specified\nrepository.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "repositories" ] } }, "/snippets/{username}/{encoded_id}/commits": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "encoded_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the changes (commits) made on this snippet.", "responses": { "200": { "description": "The paginated list of snippet commits.", "schema": { "$ref": "#/definitions/paginated_snippet_commit" } }, "403": { "description": "If the authenticated user does not have access to the snippet.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the snippet does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "snippets" ] } }, "/users/{username}/repositories": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "All repositories owned by a user/team. This includes private\nrepositories, but filtered down to the ones that the calling user has\naccess to.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "users", "teams" ] } }, "/repositories/{username}/{repo_slug}/pullrequests/activity": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "pullrequest" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the pull request's activity log. This includes comments that\nwere made by the reviewers, updates and approvals.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "pullrequests" ] } }, "/snippets/{username}/{encoded_id}/comments/{comment_id}": { "put": { "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Updates a comment.\n\nComments can only be updated by their author.", "responses": { "200": { "description": "The updated comment object." }, "403": { "description": "If the authenticated user does not have access to the snippet.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the comment or snippet does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "snippets" ] }, "delete": { "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Deletes a snippet comment.\n\nComments can only be removed by their author.", "responses": { "204": { "description": "Indicates the comment was deleted successfully." }, "403": { "description": "If the authenticated user is not the author of the comment.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the comment or the snippet does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "snippets" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "encoded_id", "in": "path" }, { "required": true, "type": "string", "name": "comment_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the specific snippet comment.", "responses": { "200": { "description": "The specified comment.", "schema": { "$ref": "#/definitions/snippet_comment" } }, "403": { "description": "If the authenticated user does not have access to the snippet.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the comment or snippet does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "snippets" ] } }, "/repositories/{username}/{repo_slug}/diff/{spec}": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "spec", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "commits" ] } }, "/repositories/{username}/{repo_slug}/branch-restrictions": { "post": { "security": [ { "oauth2": [ "repository:admin" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "branch_restrictions" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository:admin" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "branch_restrictions" ] } }, "/repositories/{username}/{repo_slug}/issues/{issue_id}/comments/{comment_id}": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "issue_id", "in": "path" }, { "required": true, "type": "string", "name": "comment_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "issue" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the specified issue comment object.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "issue_tracker" ] } }, "/repositories/{username}/{repo_slug}/hooks/{uid}": { "put": { "security": [ { "oauth2": [ "webhook" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Updates the specified webhook subscription.\n\nThe following properties can be mutated:\n\n* `description`\n* `url`\n* `active`\n* `events`", "responses": { "200": { "description": "The webhook subscription object.", "schema": { "$ref": "#/definitions/webhook_subscription" } }, "403": { "description": "If the authenticated user does not have permission to update the webhook.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the webhook or repository does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The installed webhook's id", "name": "uid", "in": "path" } ], "tags": [ "webhooks" ] }, "delete": { "security": [ { "oauth2": [ "webhook" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Deletes the specified webhook subscription.", "responses": { "204": { "description": "When the webhook was deleted successfully", "schema": { "$ref": "#/definitions/webhook_subscription" } }, "403": { "description": "If the authenticated user does not have permission to delete the webhook.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the webhook or repository does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The installed webhook's id", "name": "uid", "in": "path" } ], "tags": [ "webhooks" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "uid", "in": "path" } ], "get": { "security": [ { "oauth2": [ "webhook" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the webhook installed on the specified repository.", "responses": { "200": { "description": "The webhook subscription object.", "schema": { "$ref": "#/definitions/webhook_subscription" } }, "404": { "description": "If the webhook or repository does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The installed webhook's id.", "name": "uid", "in": "path" } ], "tags": [ "webhooks" ] } }, "/users/{username}": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" } ], "get": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "Gets the public information associated with a user account.\n\nIf the user's profile is private, `location`, `website` and\n`created_on` elements are omitted.", "responses": { "200": { "description": "The user object", "schema": { "$ref": "#/definitions/team" } }, "404": { "description": "If no user exists for the specified name or UUID, or if the specified account is a team account, not a personal account.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The account's username or UUID.", "name": "username", "in": "path" } ], "tags": [ "users" ] } }, "/snippets/{username}/{encoded_id}": { "put": { "responses": { "200": { "description": "The updated snippet object.", "schema": { "$ref": "#/definitions/snippet" } }, "401": { "description": "If the snippet is private and the request was not authenticated.", "schema": { "$ref": "#/definitions/error" } }, "403": { "description": "If authenticated user does not have permission to update the private snippet.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the snippet does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The snippet's id.", "name": "encoded_id", "in": "path" } ], "tags": [ "snippets" ], "produces": [ "application/json", "multipart/related", "multipart/form-data" ], "security": [ { "oauth2": [ "snippet:write" ] }, { "basic": [] }, { "api_key": [] } ], "consumes": [ "application/json", "multipart/related", "multipart/form-data" ], "description": "Used to update a snippet. Use this to add and delete files and to\nchange a snippet's title.\n\nTo update a snippet, one can either PUT a full snapshot, or only the\nparts that need to be changed.\n\nThe contract for PUT on this API is that properties missing from the\nrequest remain untouched so that snippets can be efficiently\nmanipulated with differential payloads.\n\nTo delete a property (e.g. the title, or a file), include its name in\nthe request, but omit its value (use `null`).\n\nAs in Git, explicit renaming of files is not supported. Instead, to\nrename a file, delete it and add it again under another name. This can\nbe done atomically in a single request. Rename detection is left to\nthe SCM.\n\nPUT supports three different content types for both request and\nresponse bodies:\n\n* `application/json`\n* `multipart/related`\n* `multipart/form-data`\n\nThe content type used for the request body can be different than that\nused for the response. Content types are specified using standard HTTP\nheaders.\n\nUse the `Content-Type` and `Accept` headers to select the desired\nrequest and response format.\n\n\napplication/json\n----------------\n\nAs with creation and retrieval, the content type determines what\nproperties can be manipulated. `application/json` does not support\nfile contents and is therefore limited to a snippet's meta data.\n\nTo update the title, without changing any of its files:\n\n $ curl -X POST -H \"Content-Type: application/json\" https://api.bitbucket.org/2.0/snippets/evzijst/kypj -d '{\"title\": \"Updated title\"}'\n\n\nTo delete the title:\n\n $ curl -X POST -H \"Content-Type: application/json\" https://api.bitbucket.org/2.0/snippets/evzijst/kypj -d '{\"title\": null}'\n\nNot all parts of a snippet can be manipulated. The owner and creator\nfor instance are immutable.\n\n\nmultipart/related\n-----------------\n\n`multipart/related` can be used to manipulate all of a snippet's\nproperties. The body is identical to a POST. properties omitted from\nthe request are left unchanged. Since the `start` part contains JSON,\nthe mechanism for manipulating the snippet's meta data is identical\nto `application/json` requests.\n\nTo update one of a snippet's file contents, while also changing its\ntitle:\n\n PUT /2.0/snippets/evzijst/kypj HTTP/1.1\n Content-Length: 288\n Content-Type: multipart/related; start=\"snippet\"; boundary=\"===============1438169132528273974==\"\n MIME-Version: 1.0\n\n --===============1438169132528273974==\n Content-Type: application/json; charset=\"utf-8\"\n MIME-Version: 1.0\n Content-ID: snippet\n\n {\n \"title\": \"My updated snippet\",\n \"files\": {\n \"foo.txt\": {}\n }\n }\n\n --===============1438169132528273974==\n Content-Type: text/plain; charset=\"us-ascii\"\n MIME-Version: 1.0\n Content-Transfer-Encoding: 7bit\n Content-ID: \"foo.txt\"\n Content-Disposition: attachment; filename=\"foo.txt\"\n\n Updated file contents.\n\n --===============1438169132528273974==--\n\nHere only the parts that are changed are included in the body. The\nother files remain untouched.\n\nNote the use of the `files` list in the JSON part. This list contains\nthe files that are being manipulated. This list should have\ncorresponding multiparts in the request that contain the new contents\nof these files.\n\nIf a filename in the `files` list does not have a corresponding part,\nit will be deleted from the snippet, as shown below:\n\n PUT /2.0/snippets/evzijst/kypj HTTP/1.1\n Content-Length: 188\n Content-Type: multipart/related; start=\"snippet\"; boundary=\"===============1438169132528273974==\"\n MIME-Version: 1.0\n\n --===============1438169132528273974==\n Content-Type: application/json; charset=\"utf-8\"\n MIME-Version: 1.0\n Content-ID: snippet\n\n {\n \"files\": {\n \"image.png\": {}\n }\n }\n\n --===============1438169132528273974==--\n\nTo simulate a rename, delete a file and add the same file under\nanother name:\n\n PUT /2.0/snippets/evzijst/kypj HTTP/1.1\n Content-Length: 212\n Content-Type: multipart/related; start=\"snippet\"; boundary=\"===============1438169132528273974==\"\n MIME-Version: 1.0\n\n --===============1438169132528273974==\n Content-Type: application/json; charset=\"utf-8\"\n MIME-Version: 1.0\n Content-ID: snippet\n\n {\n \"files\": {\n \"foo.txt\": {},\n \"bar.txt\": {}\n }\n }\n\n --===============1438169132528273974==\n Content-Type: text/plain; charset=\"us-ascii\"\n MIME-Version: 1.0\n Content-Transfer-Encoding: 7bit\n Content-ID: \"bar.txt\"\n Content-Disposition: attachment; filename=\"bar.txt\"\n\n foo\n\n --===============1438169132528273974==--\n\n\nmultipart/form-data\n-----------------\n\nAgain, one can also use `multipart/form-data` to manipulate file\ncontents and meta data atomically.\n\n $ curl -X PUT http://localhost:12345/2.0/snippets/evzijst/kypj -F title=\"My updated snippet\" -F file=@foo.txt\n\n PUT /2.0/snippets/evzijst/kypj HTTP/1.1\n Content-Length: 351\n Content-Type: multipart/form-data; boundary=----------------------------63a4b224c59f\n\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"file\"; filename=\"foo.txt\"\n Content-Type: text/plain\n\n foo\n\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"title\"\n\n My updated snippet\n ------------------------------63a4b224c59f\n\nTo delete a file, omit its contents while including its name in the\n`files` field:\n\n $ curl -X PUT https://api.bitbucket.org/2.0/snippets/evzijst/kypj -F files=image.png\n\n PUT /2.0/snippets/evzijst/kypj HTTP/1.1\n Content-Length: 149\n Content-Type: multipart/form-data; boundary=----------------------------ef8871065a86\n\n ------------------------------ef8871065a86\n Content-Disposition: form-data; name=\"files\"\n\n image.png\n ------------------------------ef8871065a86--\n\nThe explicit use of the `files` element in `multipart/related` and\n`multipart/form-data` is only required when deleting files.\nThe default mode of operation is for file parts to be processed,\nregardless of whether or not they are listed in `files`, as a\nconvenience to the client." }, "delete": { "security": [ { "oauth2": [ "snippet:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Deletes a snippet and returns an empty response.", "responses": { "401": { "description": "If the snippet is private and the request was not authenticated.", "schema": { "$ref": "#/definitions/error" } }, "403": { "description": "If authenticated user does not have permission to delete the private snippet.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the snippet does not exist.", "schema": { "$ref": "#/definitions/error" } }, "204": { "description": "If the snippet was deleted successfully." } }, "parameters": [ { "required": true, "type": "string", "description": "The snippet's id.", "name": "encoded_id", "in": "path" } ], "tags": [ "snippets" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "encoded_id", "in": "path" } ], "get": { "responses": { "200": { "description": "The snippet object.", "schema": { "$ref": "#/definitions/snippet" } }, "401": { "description": "If the snippet is private and the request was not authenticated.", "schema": { "$ref": "#/definitions/error" } }, "403": { "description": "If authenticated user does not have access to the private snippet.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the snippet does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The snippet's id.", "name": "encoded_id", "in": "path" } ], "produces": [ "application/json", "multipart/related", "multipart/form-data" ], "tags": [ "snippets" ], "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Retrieves a single snippet.\n\nSnippets support multiple content types:\n\n* application/json\n* multipart/related\n* multipart/form-data\n\n\napplication/json\n----------------\n\nThe default content type of the response is `application/json`.\nSince JSON is always `utf-8`, it cannot reliably contain file contents\nfor files that are not text. Therefore, JSON snippet documents only\ncontain the filename and links to the file contents.\n\nThis means that in order to retrieve all parts of a snippet, N+1\nrequests need to be made (where N is the number of files in the\nsnippet).\n\n\nmultipart/related\n-----------------\n\nTo retrieve an entire snippet in a single response, use the\n`Accept: multipart/related` HTTP request header.\n\n $ curl -H \"Accept: multipart/related\" https://api.bitbucket.org/2.0/snippets/evzijst/1\n\nResponse:\n\n HTTP/1.1 200 OK\n Content-Length: 2214\n Content-Type: multipart/related; start=\"snippet\"; boundary=\"===============1438169132528273974==\"\n MIME-Version: 1.0\n\n --===============1438169132528273974==\n Content-Type: application/json; charset=\"utf-8\"\n MIME-Version: 1.0\n Content-ID: snippet\n\n {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/snippets/evzijst/kypj\"\n },\n \"comments\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj/comments\"\n },\n \"watchers\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj/watchers\"\n },\n \"commits\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj/commits\"\n }\n },\n \"id\": kypj,\n \"title\": \"My snippet\",\n \"created_on\": \"2014-12-29T22:22:04.790331+00:00\",\n \"updated_on\": \"2014-12-29T22:22:04.790331+00:00\",\n \"is_private\": false,\n \"files\": {\n \"foo.txt\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj/files/367ab19/foo.txt\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/snippets/evzijst/kypj#foo.txt\"\n }\n }\n },\n \"image.png\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj/files/367ab19/image.png\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/snippets/evzijst/kypj#image.png\"\n }\n }\n }\n ],\n \"owner\": {\n \"username\": \"evzijst\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/evzijst\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/evzijst\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket-staging-assetroot.s3.amazonaws.com/c/photos/2013/Jul/31/erik-avatar-725122544-0_avatar.png\"\n }\n }\n },\n \"creator\": {\n \"username\": \"evzijst\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/evzijst\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/evzijst\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket-staging-assetroot.s3.amazonaws.com/c/photos/2013/Jul/31/erik-avatar-725122544-0_avatar.png\"\n }\n }\n }\n }\n\n --===============1438169132528273974==\n Content-Type: text/plain; charset=\"us-ascii\"\n MIME-Version: 1.0\n Content-Transfer-Encoding: 7bit\n Content-ID: \"foo.txt\"\n Content-Disposition: attachment; filename=\"foo.txt\"\n\n foo\n\n --===============1438169132528273974==\n Content-Type: image/png\n MIME-Version: 1.0\n Content-Transfer-Encoding: base64\n Content-ID: \"image.png\"\n Content-Disposition: attachment; filename=\"image.png\"\n\n iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAYAAAD+MdrbAAABD0lEQVR4Ae3VMUoDQRTG8ccUaW2m\n TKONFxArJYJamCvkCnZTaa+VnQdJSBFl2SMsLFrEWNjZBZs0JgiL/+KrhhVmJRbCLPx4O+/DT2TB\n cbblJxf+UWFVVRNsEGAtgvJxnLm2H+A5RQ93uIl+3632PZyl/skjfOn9Gvdwmlcw5aPUwimG+NT5\n EnNN036IaZePUuIcK533NVfal7/5yjWeot2z9ta1cAczHEf7I+3J0ws9Cgx0fsOFpmlfwKcWPuBQ\n 73Oc4FHzBaZ8llq4q1mr5B2mOUCt815qYR8eB1hG2VJ7j35q4RofaH7IG+Xrf/PfJhfmwtfFYoIN\n AqxFUD6OMxcvkO+UfKfkOyXfKdsv/AYCHMLVkHAFWgAAAABJRU5ErkJggg==\n --===============1438169132528273974==--\n\nmultipart/form-data\n-------------------\n\nAs with creating new snippets, `multipart/form-data` can be used as an\nalternative to `multipart/related`. However, the inherently flat\nstructure of form-data means that only basic, root-level properties\ncan be returned, while nested elements like `links` are omitted:\n\n $ curl -H \"Accept: multipart/form-data\" https://api.bitbucket.org/2.0/snippets/evzijst/kypj\n\nResponse:\n\n HTTP/1.1 200 OK\n Content-Length: 951\n Content-Type: multipart/form-data; boundary=----------------------------63a4b224c59f\n\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"title\"\n Content-Type: text/plain; charset=\"utf-8\"\n\n My snippet\n ------------------------------63a4b224c59f--\n Content-Disposition: attachment; name=\"file\"; filename=\"foo.txt\"\n Content-Type: text/plain\n\n foo\n\n ------------------------------63a4b224c59f\n Content-Disposition: attachment; name=\"file\"; filename=\"image.png\"\n Content-Transfer-Encoding: base64\n Content-Type: application/octet-stream\n\n iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAYAAAD+MdrbAAABD0lEQVR4Ae3VMUoDQRTG8ccUaW2m\n TKONFxArJYJamCvkCnZTaa+VnQdJSBFl2SMsLFrEWNjZBZs0JgiL/+KrhhVmJRbCLPx4O+/DT2TB\n cbblJxf+UWFVVRNsEGAtgvJxnLm2H+A5RQ93uIl+3632PZyl/skjfOn9Gvdwmlcw5aPUwimG+NT5\n EnNN036IaZePUuIcK533NVfal7/5yjWeot2z9ta1cAczHEf7I+3J0ws9Cgx0fsOFpmlfwKcWPuBQ\n 73Oc4FHzBaZ8llq4q1mr5B2mOUCt815qYR8eB1hG2VJ7j35q4RofaH7IG+Xrf/PfJhfmwtfFYoIN\n AqxFUD6OMxcvkO+UfKfkOyXfKdsv/AYCHMLVkHAFWgAAAABJRU5ErkJggg==\n ------------------------------5957323a6b76--" } }, "/addon/linkers": { "parameters": [], "get": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] } }, "/repositories/{username}/{repo_slug}/pullrequests/{pull_request_id}/comments/{comment_id}": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "pull_request_id", "in": "path" }, { "required": true, "type": "string", "name": "comment_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "pullrequest" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns a specific pull request comment.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "pullrequests" ] } }, "/repositories/{username}/{repo_slug}/components/{component_id}": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "component_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "issue" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the specified issue tracker component object.", "responses": { "200": { "description": "The specified component object.", "schema": { "$ref": "#/definitions/component" } }, "404": { "description": "If the specified repository or component does not exist, or if the repository doesn't have the issue tracker enabled.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "integer", "description": "The component's id", "name": "component_id", "in": "path" } ], "tags": [ "issue_tracker" ] } }, "/addon": { "put": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] }, "parameters": [], "delete": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] } }, "/users/{username}/following": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" } ], "get": { "security": [ { "oauth2": [ "account" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the list of accounts this user is following.", "responses": { "200": { "description": "A paginated list of user objects.", "schema": { "$ref": "#/definitions/paginated_users" } }, "404": { "description": "If no user exists for the specified name, or if the specified account is a team account, not a personal account.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The user's username", "name": "username", "in": "path" } ], "tags": [ "users" ] } }, "/repositories/{username}/{repo_slug}/issues/{issue_id}": { "delete": { "security": [ { "oauth2": [ "issue:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Deletes the specified issue. This requires write access to the\nrepository.", "responses": { "200": { "description": "The issue object.", "schema": { "$ref": "#/definitions/issue" } }, "403": { "description": "When the authenticated user lacks isn't authorized to delete the issue tracker.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the specified repository or issue does not exist, or if the repository doesn't have the issue tracker enabled.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "issue_tracker" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "issue_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "issue" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the specified issue.", "responses": { "200": { "description": "The issue object.", "schema": { "$ref": "#/definitions/issue" } }, "403": { "description": "When the authenticated user lacks isn't authorized to access the issue tracker.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the specified repository or issue does not exist, or if the repository doesn't have the issue tracker enabled.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "issue_tracker" ] } }, "/teams/{username}/repositories": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "All repositories owned by a user/team. This includes private\nrepositories, but filtered down to the ones that the calling user has\naccess to.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "users", "teams" ] } }, "/repositories/{username}/{repo_slug}/downloads": { "post": { "security": [ { "oauth2": [ "repository:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Upload new download artifacts.\n\nTo upload files, perform a `multipart/form-data` POST containing one\nor more `files` fields:\n\n $ echo Hello World > hello.txt\n $ curl -s -u evzijst -X POST https://api.bitbucket.org/2.0/repositories/evzijst/git-tests/downloads -F files=@hello.txt\n\nWhen a file is uploaded with the same name as an existing artifact,\nthen the existing file will be replaced.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "downloads" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns a list of download links associated with the repository.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "downloads" ] } }, "/repositories/{username}/{repo_slug}/refs": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "refs" ] } }, "/hook_events": { "parameters": [], "get": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the webhook resource or subject types on which webhooks can\nbe registered.\n\nEach resource/subject type contains an `event` link that returns the\npaginated list of specific events each individual subject type can\nemit.\n\nThis endpoint is publicly accessible and does not require\nauthentication or scopes.", "responses": { "200": { "description": "A mapping of resource/subject types pointing to their individual event types.", "schema": { "$ref": "#/definitions/subject_types" } } }, "parameters": [], "tags": [ "webhooks" ] } }, "/teams/{username}": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" } ], "get": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "Gets the public information associated with a team.\n\nIf the team's profile is private, `location`, `website` and\n`created_on` elements are omitted.", "responses": { "200": { "description": "The team object", "schema": { "$ref": "#/definitions/team" } }, "404": { "description": "If no team exists for the specified name or UUID, or if the specified account is a personal account, not a team account.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The team's username or UUID.", "name": "username", "in": "path" } ], "tags": [ "teams" ] } }, "/user/emails/{email}": { "parameters": [ { "required": true, "type": "string", "name": "email", "in": "path" } ], "get": { "security": [ { "oauth2": [ "email" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns details about a specific one of the authenticated user's\nemail addresses.\n\nDetails describe whether the address has been confirmed by the user and\nwhether it is the user's primary address or not.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "users" ] } }, "/repositories/{username}/{repo_slug}/pullrequests/{pull_request_id}/approve": { "post": { "security": [ { "oauth2": [ "pullrequest:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Approve the specified pull request as the authenticated user.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "pullrequests" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "pull_request_id", "in": "path" } ], "delete": { "security": [ { "oauth2": [ "pullrequest:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Redact the authenticated user's approval of the specified pull\nrequest.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "pullrequests" ] } }, "/repositories/{username}/{repo_slug}/patch/{spec}": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "spec", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "commits" ] } }, "/snippets/{username}/{encoded_id}/{node_id}/diff": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "encoded_id", "in": "path" }, { "required": true, "type": "string", "name": "node_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the diff of the specified commit against its first parent.\n\nNote that this resource is different in functionality from the `patch`\nresource.\n\nThe differences between a diff and a patch are:\n\n* patches have a commit header with the username, message, etc\n* diffs support the optional `path=foo/bar.py` query param to filter the\n diff to just that one file diff (not supported for patches)\n* for a merge, the diff will show the diff between the merge commit and\n its first parent (identical to how PRs work), while patch returns a\n response containing separate patches for each commit on the second\n parent's ancestry, up to the oldest common ancestor (identical to\n its reachability).\n\nNote that the character encoding of the contents of the diff is\nunspecified as Git and Mercurial do not track this, making it hard for\nBitbucket to reliably determine this.", "responses": { "200": { "description": "The raw diff contents." }, "403": { "description": "If the authenticated user does not have access to the snippet.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the snippet does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "type": "string", "description": "When used, only one the diff of the specified file will be returned.", "name": "path", "in": "query" }, { "required": true, "type": "string", "description": "The snippet id.", "name": "encoded_id", "in": "path" } ], "tags": [ "snippets" ] } }, "/repositories/{username}/{repo_slug}/forks": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns a paginated list of all the forks of the specified\nrepository.", "responses": { "200": { "description": "All forks.", "schema": { "$ref": "#/definitions/paginated_repositories" } } }, "parameters": [], "tags": [ "repositories" ] } }, "/repositories/{username}/{repo_slug}/issues": { "post": { "security": [ { "oauth2": [ "issue:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Creates a new issue.\n\nThis call requires authentication. Private repositories or private\nissue trackers require the caller to authenticate with an account that\nhas appropriate authorisation.\n\nThe authenticated user is used for the issue's `reporter` field.", "responses": { "201": { "headers": { "Location": { "type": "string", "description": "The (absolute) URL of the newly created issue." } }, "description": "The newly created issue.", "schema": { "$ref": "#/definitions/issue" } }, "403": { "description": "When the authenticated user lacks the privilege to create issues in the issue tracker.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the specified repository or version does not exist, or if the repository doesn't have the issue tracker enabled.", "schema": { "$ref": "#/definitions/error" } }, "401": { "description": "When the request wasn't authenticated.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "schema": { "$ref": "#/definitions/issue" }, "required": true, "description": "The new issue. Note that the only required element is `title`. All other elements can be omitted from the body.", "name": "_body", "in": "body" } ], "tags": [ "issue_tracker" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "issue" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the issues in the issue tracker.", "responses": { "200": { "description": "A paginated list of the issues matching any filter criteria that were provided.", "schema": { "$ref": "#/definitions/paginated_issues" } }, "404": { "description": "If the specified repository or version does not exist, or if the repository doesn't have the issue tracker enabled.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "issue_tracker" ] } }, "/repositories/{username}/{repo_slug}/issues/{issue_id}/attachments": { "post": { "security": [ { "oauth2": [ "issue:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Upload new issue attachments.\n\nTo upload files, perform a `multipart/form-data` POST containing one\nor more file fields.\n\nWhen a file is uploaded with the same name as an existing attachment,\nthen the existing file will be replaced.", "responses": { "400": { "description": "If no files were uploaded, or if the wrong `Content-Type` was used." }, "201": { "headers": { "Location": { "type": "string", "description": "The URL to the issue's collection of attachments." } }, "description": "An empty response document." }, "404": { "description": "If the specified repository or issue does not exist, or if the repository doesn't have the issue tracker enabled.", "schema": { "$ref": "#/definitions/error" } }, "401": { "description": "If the issue tracker is private and the request was not authenticated." } }, "parameters": [ { "required": true, "type": "integer", "description": "The issue's id", "name": "issue_id", "in": "path" } ], "tags": [ "issue_tracker" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "issue_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "issue" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns all attachments for this issue.\n\nThis returns the files' meta data. This does not return the files'\nactual contents.\n\nThe files are always ordered by their upload date.", "responses": { "200": { "description": "A paginated list of all attachments for this issue.", "schema": { "$ref": "#/definitions/paginated_issue_attachments" } }, "401": { "description": "If the issue tracker is private and the request was not authenticated." }, "404": { "description": "If the specified repository or version does not exist, or if the repository doesn't have the issue tracker enabled.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "integer", "description": "The issue's id", "name": "issue_id", "in": "path" } ], "tags": [ "issue_tracker" ] } }, "/repositories/{username}/{repo_slug}/versions": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "issue" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the versions that have been defined in the issue tracker.\n\nThis resource is only available on repositories that have the issue\ntracker enabled.", "responses": { "200": { "description": "The versions that have been defined in the issue tracker.", "schema": { "$ref": "#/definitions/paginated_versions" } }, "404": { "description": "If the specified repository does not exist, or if the repository doesn't have the issue tracker enabled.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "issue_tracker" ] } }, "/repositories/{username}/{repo_slug}/refs/tags/{name}": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "name", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "refs" ] } }, "/snippets/{username}/{encoded_id}/watch": { "put": { "security": [ { "oauth2": [ "snippet:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Used to start watching a specific snippet. Returns 204 (No Content).", "responses": { "401": { "description": "If the request was not authenticated.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the snippet does not exist.", "schema": { "$ref": "#/definitions/error" } }, "204": { "description": "Indicates the authenticated user is now watching the snippet.", "schema": { "$ref": "#/definitions/paginated_users" } } }, "parameters": [ { "required": true, "type": "string", "description": "The snippet id.", "name": "encoded_id", "in": "path" } ], "tags": [ "snippets" ] }, "delete": { "security": [ { "oauth2": [ "snippet:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Used to stop watching a specific snippet. Returns 204 (No Content)\nto indicate success.", "responses": { "401": { "description": "If the request was not authenticated.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the snippet does not exist.", "schema": { "$ref": "#/definitions/error" } }, "204": { "description": "Indicates the user stopped watching the snippet successfully.", "schema": { "$ref": "#/definitions/paginated_users" } } }, "parameters": [ { "required": true, "type": "string", "description": "The snippet id.", "name": "encoded_id", "in": "path" } ], "tags": [ "snippets" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "encoded_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Used to check if the current user is watching a specific snippet.\n\nReturns 204 (No Content) if the user is watching the snippet and 404 if\nnot.\n\nHitting this endpoint anonymously always returns a 404.", "responses": { "204": { "description": "If the authenticated user is watching the snippet.", "schema": { "$ref": "#/definitions/paginated_users" } }, "404": { "description": "If the snippet does not exist, or if the authenticated user is not watching the snippet.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The snippet id.", "name": "encoded_id", "in": "path" } ], "tags": [ "snippets" ] } }, "/repositories/{username}/{repo_slug}/pullrequests/{pull_request_id}/diff": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "pull_request_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "pullrequest" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "pullrequests" ] } }, "/snippets": { "post": { "security": [ { "oauth2": [ "snippet:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Creates a new snippet under the authenticated user's account.\n\nSnippets can contain multiple files. Both text and binary files are\nsupported.\n\nThe simplest way to create a new snippet from a local file:\n\n $ curl -u username:password -X POST https://api.bitbucket.org/2.0/snippets -F file=@image.png\n\nCreating snippets through curl has a few limitations and so let's look\nat a more complicated scenario.\n\nSnippets are created with a multipart POST. Both `multipart/form-data`\nand `multipart/related` are supported. Both allow the creation of\nsnippets with both meta data (title, etc), as well as multiple text\nand binary files.\n\nThe main difference is that `multipart/related` can use rich encoding\nfor the meta data (currently JSON).\n\n\nmultipart/related (RFC-2387)\n----------------------------\n\nThis is the most advanced and efficient way to create a paste.\n\n POST /2.0/snippets/evzijst HTTP/1.1\n Content-Length: 1188\n Content-Type: multipart/related; start=\"snippet\"; boundary=\"===============1438169132528273974==\"\n MIME-Version: 1.0\n\n --===============1438169132528273974==\n Content-Type: application/json; charset=\"utf-8\"\n MIME-Version: 1.0\n Content-ID: snippet\n\n {\n \"title\": \"My snippet\",\n \"is_private\": true,\n \"scm\": \"hg\",\n \"files\": {\n \"foo.txt\": {},\n \"image.png\": {}\n }\n }\n\n --===============1438169132528273974==\n Content-Type: text/plain; charset=\"us-ascii\"\n MIME-Version: 1.0\n Content-Transfer-Encoding: 7bit\n Content-ID: \"foo.txt\"\n Content-Disposition: attachment; filename=\"foo.txt\"\n\n foo\n\n --===============1438169132528273974==\n Content-Type: image/png\n MIME-Version: 1.0\n Content-Transfer-Encoding: base64\n Content-ID: \"image.png\"\n Content-Disposition: attachment; filename=\"image.png\"\n\n iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAYAAAD+MdrbAAABD0lEQVR4Ae3VMUoDQRTG8ccUaW2m\n TKONFxArJYJamCvkCnZTaa+VnQdJSBFl2SMsLFrEWNjZBZs0JgiL/+KrhhVmJRbCLPx4O+/DT2TB\n cbblJxf+UWFVVRNsEGAtgvJxnLm2H+A5RQ93uIl+3632PZyl/skjfOn9Gvdwmlcw5aPUwimG+NT5\n EnNN036IaZePUuIcK533NVfal7/5yjWeot2z9ta1cAczHEf7I+3J0ws9Cgx0fsOFpmlfwKcWPuBQ\n 73Oc4FHzBaZ8llq4q1mr5B2mOUCt815qYR8eB1hG2VJ7j35q4RofaH7IG+Xrf/PfJhfmwtfFYoIN\n AqxFUD6OMxcvkO+UfKfkOyXfKdsv/AYCHMLVkHAFWgAAAABJRU5ErkJggg==\n --===============1438169132528273974==--\n\nThe request contains multiple parts and is structured as follows.\n\nThe first part is the JSON document that describes the snippet's\nproperties or meta data. It either has to be the first part, or the\nrequest's `Content-Type` header must contain the `start` parameter to\npoint to it.\n\nThe remaining parts are the files of which there can be zero or more.\nEach file part should contain the `Content-ID` MIME header through\nwhich the JSON meta data's `files` element addresses it. The value\nshould be the name of the file.\n\n`Content-Disposition` is an optional MIME header. The header's\noptional `filename` parameter can be used to specify the file name\nthat Bitbucket should use when writing the file to disk. When present,\n`filename` takes precedence over the value of `Content-ID`.\n\nWhen the JSON body omits the `files` element, the remaining parts are\nnot ignored. Instead, each file is added to the new snippet as if its\nname was explicitly linked (the use of the `files` elements is\nmandatory for some operations like deleting or renaming files).\n\n\nmultipart/form-data\n-------------------\n\nThe use of JSON for the snippet's meta data is optional. Meta data can\nalso be supplied as regular form fields in a more conventional\n`multipart/form-data` request:\n\n $ curl -X POST -u credentials https://api.bitbucket.org/2.0/snippets -F title=\"My snippet\" -F file=@foo.txt -F file=@image.png\n\n POST /2.0/snippets HTTP/1.1\n Content-Length: 951\n Content-Type: multipart/form-data; boundary=----------------------------63a4b224c59f\n\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"file\"; filename=\"foo.txt\"\n Content-Type: text/plain\n\n foo\n\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"file\"; filename=\"image.png\"\n Content-Type: application/octet-stream\n\n ?PNG\n\n IHDR?1??I.....\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"title\"\n\n My snippet\n ------------------------------63a4b224c59f--\n\nHere the meta data properties are included as flat, top-level form\nfields. The file attachments use the `file` field name. To attach\nmultiple files, simply repeat the field.\n\nThe advantage of `multipart/form-data` over `multipart/related` is\nthat it can be easier to build clients.\n\nEssentially all properties are optional, `title` and `files` included.\n\n\nSharing and Visibility\n----------------------\n\nSnippets can be either public (visible to anyone on Bitbucket, as well\nas anonymous users), or private (visible only to the owner, creator\nand members of the team in case the snippet is owned by a team). This\nis controlled through the snippet's `is_private` element:\n\n* **is_private=false** -- everyone, including anonymous users can view\n the snippet\n* **is_private=true** -- only the owner and team members (for team\n snippets) can view it\n\nTo create the snippet under a team account, just append the team name\nto the URL (see `/2.0/snippets/{username}`).", "responses": { "201": { "headers": { "Location": { "type": "string", "description": "The URL of the newly created snippet." } }, "description": "The newly created snippet object.", "schema": { "$ref": "#/definitions/snippet" } }, "401": { "description": "If the request was not authenticated", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "schema": { "$ref": "#/definitions/snippet" }, "required": true, "description": "The new snippet object.", "name": "_body", "in": "body" } ], "tags": [ "snippets" ] }, "parameters": [], "get": { "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns all snippets. Like pull requests, repositories and teams, the\nfull set of snippets is defined by what the current user has access to.\n\nThis includes all snippets owned by the current user, but also all snippets\nowned by any of the teams the user is a member of, or snippets by other\nusers that the current user is either watching or has collaborated on (for\ninstance by commenting on it).\n\nTo limit the set of returned snippets, apply the\n`?role=[owner|contributor|member]` query parameter where the roles are\ndefined as follows:\n\n* `owner`: all snippets owned by the current user\n* `contributor`: all snippets owned by, or watched by the current user\n* `member`: owned by the user, their teams, or watched by the current user\n\nWhen no role is specified, all public snippets are returned, as well as all\nprivately owned snippets watched or commented on.\n\nThe returned response is a normal paginated JSON list. This endpoint\nonly supports `application/json` responses and no\n`multipart/form-data` or `multipart/related`. As a result, it is not\npossible to include the file contents.", "responses": { "200": { "description": "A paginated list of snippets.", "schema": { "$ref": "#/definitions/paginated_snippets" } }, "404": { "description": "If the snippet does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "enum": [ "owner", "contributor", "member" ], "name": "role", "required": false, "in": "query", "type": "string", "description": "Filter down the result based on the authenticated user's role (`owner`, `contributor`, or `member`)." } ], "tags": [ "snippets" ] } }, "/repositories/{username}/{repo_slug}/pullrequests/{pull_request_id}": { "put": { "security": [ { "oauth2": [ "pullrequest:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Mutates the specified pull request.\n\nThis can be used to change the pull request's branches or description.\n\nOnly open pull requests can be mutated.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "pullrequests" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "pull_request_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "pullrequest" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the specified pull request.", "responses": { "200": { "description": "The pull request object", "schema": { "$ref": "#/definitions/pullrequest" } } }, "parameters": [], "tags": [ "pullrequests" ] } }, "/repositories/{username}/{repo_slug}/pullrequests/{pull_request_id}/comments": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "pull_request_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "pullrequest" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns all the pull request's comments.\n\nThis includes both global, inline comments and replies.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "pullrequests" ] } }, "/account/{username}/addons/{encoded_context_id}": { "put": { "security": [ { "oauth2": [ "account:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] }, "delete": { "security": [ { "oauth2": [ "account:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "encoded_context_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "account:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] } }, "/repositories/{username}/{repo_slug}/pullrequests/{pull_request_id}/decline": { "post": { "security": [ { "oauth2": [ "pullrequest:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "pullrequests" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "pull_request_id", "in": "path" } ] }, "/user/emails": { "parameters": [], "get": { "security": [ { "oauth2": [ "email" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns all the authenticated user's email addresses. Both\nconfirmed and unconfirmed.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "users" ] } }, "/repositories/{username}/{repo_slug}/pullrequests": { "post": { "security": [ { "oauth2": [ "pullrequest:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Creates a new pull request.", "responses": { "201": { "headers": { "Location": { "type": "string", "description": "The URL of new newly created pull request." } }, "description": "The newly created pull request.", "schema": { "$ref": "#/definitions/pullrequest" } } }, "parameters": [ { "schema": { "$ref": "#/definitions/pullrequest" }, "required": false, "description": "The new pull request.\n\nThe request URL you POST to becomes the destination repository URL. For this reason, you must specify an explicit source repository in the request object if you want to pull from a different repository (fork).\n\nSince not all elements are required or even mutable, you only need to include the elements you want to initialize, such as the source branch and the title.", "name": "_body", "in": "body" } ], "tags": [ "pullrequests" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "pullrequest" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns all pull requests on the specified repository.\n\nBy default only open pull requests are returned. This can be controlled\nusing the `state` query parameter. To retrieve pull requests that are\nin one of multiple states, repeat the `state` parameter for each\nindividual state.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "enum": [ "MERGED", "SUPERSEDED", "OPEN", "DECLINED" ], "type": "string", "description": "Only return pull requests that in this state. This parameter can be repeated.", "name": "state", "in": "query" } ], "tags": [ "pullrequests" ] } }, "/repositories/{username}/{repo_slug}/commits": { "post": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Identical to `GET /repositories/{username}/{repo_slug}/commits`,\nexcept that POST allows clients to place the include and exclude\nparameters in the request body to avoid URL length issues.\n\n**Note that this resource does NOT support new commit creation.**", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "commits" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "These are the repository's commits. They are paginated and returned\nin reverse chronological order, similar to the output of `git log` and\n`hg log`. Like these tools, the DAG can be filtered.\n\n## GET /repositories/{username}/{repo_slug}/commits/\n\nReturns all commits in the repo in topological order (newest commit\nfirst). All branches and tags are included (similar to\n`git log --all` and `hg log`).\n\n## GET /repositories/{username}/{repo_slug}/commits/master\n\nReturns all commits on rev `master` (similar to `git log master`,\n`hg log master`).\n\n## GET /repositories/{username}/{repo_slug}/commits/dev?exclude=master\n\nReturns all commits on ref `dev`, except those that are reachable on\n`master` (similar to `git log dev ^master`).\n\n## GET /repositories/{username}/{repo_slug}/commits/?exclude=master\n\nReturns all commits in the repo that are not on master\n(similar to `git log --all ^master`).\n\n## GET /repositories/{username}/{repo_slug}/commits/?include=foo&include=bar&exclude=fu&exclude=fubar\n\nReturns all commits that are on refs `foo` or `bar`, but not on `fu` or\n`fubar` (similar to `git log foo bar ^fu ^fubar`).\n\nBecause the response could include a very large number of commits, it\nis paginated. Follow the 'next' link in the response to navigate to the\nnext page of commits. As with other paginated resources, do not\nconstruct your own links.\n\nWhen the include and exclude parameters are more than ca fit in a\nquery string, clients can use a `x-www-form-urlencoded` POST instead.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "commits" ] } }, "/repositories/{username}/{repo_slug}/commit/{sha}/comments": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "sha", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the commit's comments.\n\nThis includes both global and inline comments.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "commits" ] } }, "/repositories/{username}/{repo_slug}/commit/{revision}": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "revision", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the specified commit.", "responses": { "200": { "description": "The commit object", "schema": { "$ref": "#/definitions/commit" } }, "404": { "description": "If the specified commit or repository does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The commit's SHA1.", "name": "revision", "in": "path" } ], "tags": [ "commits" ] } }, "/snippets/{username}": { "post": { "security": [ { "oauth2": [ "snippet:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Identical to `/snippets`, except that the new snippet will be\ncreated under the account specified in the path parameter `{username}`.", "responses": { "201": { "headers": { "Location": { "type": "string", "description": "The URL of the newly created snippet." } }, "description": "The newly created snippet object.", "schema": { "$ref": "#/definitions/snippet" } }, "403": { "description": "If the authenticated user does not have permission to create snippets under the specified account.", "schema": { "$ref": "#/definitions/error" } }, "401": { "description": "If the request was not authenticated", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "schema": { "$ref": "#/definitions/snippet" }, "required": true, "description": "The new snippet object.", "name": "_body", "in": "body" } ], "tags": [ "snippets" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" } ], "get": { "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Identical to `/snippets`, except that the result is further filtered\nby the snippet owner and only those that are owned by `{username}` are\nreturned.", "responses": { "200": { "description": "A paginated list of snippets.", "schema": { "$ref": "#/definitions/paginated_snippets" } }, "404": { "description": "If the user does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "enum": [ "owner", "contributor", "member" ], "name": "role", "required": false, "in": "query", "type": "string", "description": "Filter down the result based on the authenticated user's role (`owner`, `contributor`, or `member`)." }, { "required": true, "type": "string", "description": "Limits the result to snippets owned by this user.", "name": "username", "in": "path" } ], "tags": [ "snippets" ] } }, "/repositories/{username}/{repo_slug}/issues/{issue_id}/watch": { "put": { "security": [ { "oauth2": [ "issue", "account:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Start watching this issue.\n\nTo start watching this issue, do an empty PUT. The 204 status code\nindicates that the operation was successful.", "responses": { "401": { "description": "When the request wasn't authenticated.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the authenticated user is not watching this issue, or when the repo does not exist, or does not have an issue tracker.", "schema": { "$ref": "#/definitions/error" } }, "204": { "description": "Indicates that the authenticated user successfully started watching this issue.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "integer", "description": "The issue's id", "name": "issue_id", "in": "path" } ], "tags": [ "issue_tracker" ] }, "delete": { "security": [ { "oauth2": [ "issue:write", "account:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Stop watching this issue.", "responses": { "401": { "description": "When the request wasn't authenticated.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the issue or the repo does not exist, or the repository does not have an issue tracker.", "schema": { "$ref": "#/definitions/error" } }, "204": { "description": "Indicates that the authenticated user successfully stopped watching this issue.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "integer", "description": "The issue's id", "name": "issue_id", "in": "path" } ], "tags": [ "issue_tracker" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "issue_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "issue", "account" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Indicated whether or not the authenticated user is watching this\nissue.", "responses": { "401": { "description": "When the request wasn't authenticated.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the authenticated user is not watching this issue, or when the repo does not exist, or does not have an issue tracker.", "schema": { "$ref": "#/definitions/error" } }, "204": { "description": "If the authenticated user is watching this issue.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "integer", "description": "The issue's id", "name": "issue_id", "in": "path" } ], "tags": [ "issue_tracker" ] } }, "/repositories/{username}/{repo_slug}/milestones/{milestone_id}": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "milestone_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "issue" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the specified issue tracker milestone object.", "responses": { "200": { "description": "The specified milestone object.", "schema": { "$ref": "#/definitions/milestone" } }, "404": { "description": "If the specified repository or milestone does not exist, or if the repository doesn't have the issue tracker enabled.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "integer", "description": "The milestone's id", "name": "milestone_id", "in": "path" } ], "tags": [ "issue_tracker" ] } }, "/teams": { "parameters": [], "get": { "security": [ { "oauth2": [ "team" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns all the teams that the authenticated user is associated\nwith.", "responses": { "200": { "description": "A paginated list of teams.", "schema": { "$ref": "#/definitions/paginated_teams" } }, "401": { "description": "When the request wasn't authenticated.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "enum": [ "admin", "contributor", "member" ], "name": "role", "required": false, "in": "query", "type": "string", "description": "\nFilters the teams based on the authenticated user's role on each team.\n\n* **member**: returns a list of all the teams which the caller is a member of\n at least one team group or repository owned by the team\n* **contributor**: returns a list of teams which the caller has write access\n to at least one repository owned by the team\n* **admin**: returns a list teams which the caller has team administrator access\n" } ], "tags": [ "teams" ] } }, "/user": { "parameters": [], "get": { "security": [ { "oauth2": [ "account" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the currently logged in user.", "responses": { "200": { "description": "The current user.", "schema": { "$ref": "#/definitions/user" } }, "401": { "description": "When the request wasn't authenticated.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "users" ] } }, "/repositories/{username}/{repo_slug}/commits/{revision}": { "post": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Identical to `GET /repositories/{username}/{repo_slug}/commits`,\nexcept that POST allows clients to place the include and exclude\nparameters in the request body to avoid URL length issues.\n\n**Note that this resource does NOT support new commit creation.**", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "commits" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "revision", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "These are the repository's commits. They are paginated and returned\nin reverse chronological order, similar to the output of `git log` and\n`hg log`. Like these tools, the DAG can be filtered.\n\n## GET /repositories/{username}/{repo_slug}/commits/\n\nReturns all commits in the repo in topological order (newest commit\nfirst). All branches and tags are included (similar to\n`git log --all` and `hg log`).\n\n## GET /repositories/{username}/{repo_slug}/commits/master\n\nReturns all commits on rev `master` (similar to `git log master`,\n`hg log master`).\n\n## GET /repositories/{username}/{repo_slug}/commits/dev?exclude=master\n\nReturns all commits on ref `dev`, except those that are reachable on\n`master` (similar to `git log dev ^master`).\n\n## GET /repositories/{username}/{repo_slug}/commits/?exclude=master\n\nReturns all commits in the repo that are not on master\n(similar to `git log --all ^master`).\n\n## GET /repositories/{username}/{repo_slug}/commits/?include=foo&include=bar&exclude=fu&exclude=fubar\n\nReturns all commits that are on refs `foo` or `bar`, but not on `fu` or\n`fubar` (similar to `git log foo bar ^fu ^fubar`).\n\nBecause the response could include a very large number of commits, it\nis paginated. Follow the 'next' link in the response to navigate to the\nnext page of commits. As with other paginated resources, do not\nconstruct your own links.\n\nWhen the include and exclude parameters are more than ca fit in a\nquery string, clients can use a `x-www-form-urlencoded` POST instead.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "commits" ] } }, "/snippets/{username}/{encoded_id}/comments": { "post": { "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Creates a new comment.\n\nThe only required field in the body is `content.raw`.\n\nTo create a threaded reply to an existing comment, include `parent.id`.", "responses": { "201": { "headers": { "Location": { "type": "string", "description": "The URL of the new comment" } }, "description": "The newly created comment.", "schema": { "$ref": "#/definitions/snippet" } }, "403": { "description": "If the authenticated user does not have access to the snippet.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the snippet does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "schema": { "$ref": "#/definitions/snippet" }, "required": true, "description": "The contents of the new comment.", "name": "_body", "in": "body" } ], "tags": [ "snippets" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "encoded_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Used to retrieve a paginated list of all comments for a specific\nsnippet.\n\nThis resource works identical to commit and pull request comments.", "responses": { "200": { "description": "A paginated list of snippet comments, ordered by creation date.", "schema": { "$ref": "#/definitions/paginated_snippet_comments" } }, "403": { "description": "If the authenticated user does not have access to the snippet.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the snippet does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "snippets" ] } }, "/repositories/{username}/{repo_slug}/pullrequests/{pull_request_id}/activity": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "pull_request_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "pullrequest" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the pull request's activity log. This includes comments that\nwere made by the reviewers, updates and approvals.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "pullrequests" ] } }, "/repositories/{username}": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns all repositories owned by the specified account.\n\nThe result can be narrowed down based on the authenticated user's role.\n\nE.g. with `?role=contributor`, only those repositories that the\nauthenticated user has write access to are returned (this includes any\nrepo the user is an admin on, as that implies write access).", "responses": { "200": { "description": "The repositories owned by the specified account.", "schema": { "$ref": "#/definitions/paginated_repositories" } }, "404": { "description": "If the specified account does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "enum": [ "admin", "contributor", "member", "owner" ], "name": "role", "required": false, "in": "query", "type": "string", "description": "\nFilters the result based on the authenticated user's role on each repository.\n\n* **member**: returns repositories to which the user has explicit read access\n* **contributor**: returns repositories to which the user has explicit write access\n* **admin**: returns repositories to which the user has explicit administrator access\n* **owner**: returns all repositories owned by the current user\n" } ], "tags": [ "repositories" ] } }, "/repositories/{username}/{repo_slug}/pullrequests/{pull_request_id}/merge": { "post": { "security": [ { "oauth2": [ "pullrequest:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "pullrequests" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "pull_request_id", "in": "path" } ] }, "/snippets/{username}/{encoded_id}/commits/": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "encoded_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "200": { "description": "The specified snippet commit.", "schema": { "$ref": "#/definitions/snippet_commit" } }, "403": { "description": "If the authenticated user does not have access to the snippet.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the commit or the snippet does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "snippets" ] } }, "/snippets/{username}/{encoded_id}/{node_id}/patch": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "encoded_id", "in": "path" }, { "required": true, "type": "string", "name": "node_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the patch of the specified commit against its first\nparent.\n\nNote that this resource is different in functionality from the `diff`\nresource.\n\nThe differences between a diff and a patch are:\n\n* patches have a commit header with the username, message, etc\n* diffs support the optional `path=foo/bar.py` query param to filter the\n diff to just that one file diff (not supported for patches)\n* for a merge, the diff will show the diff between the merge commit and\n its first parent (identical to how PRs work), while patch returns a\n response containing separate patches for each commit on the second\n parent's ancestry, up to the oldest common ancestor (identical to\n its reachability).\n\nNote that the character encoding of the contents of the patch is\nunspecified as Git and Mercurial do not track this, making it hard for\nBitbucket to reliably determine this.", "responses": { "200": { "description": "The raw patch contents." }, "403": { "description": "If the authenticated user does not have access to the snippet.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the snippet does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The snippet id.", "name": "encoded_id", "in": "path" } ], "tags": [ "snippets" ] } }, "/teams/{username}/followers": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" } ], "get": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the list of accounts that are following this team.", "responses": { "200": { "description": "A paginated list of user objects.", "schema": { "$ref": "#/definitions/paginated_users" } }, "404": { "description": "If no team exists for the specified name, or if the specified account is a personal account, not a team account.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The team's username", "name": "username", "in": "path" } ], "tags": [ "teams" ] } }, "/snippets/{username}/{encoded_id}/{node_id}/files/{path}": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "encoded_id", "in": "path" }, { "required": true, "type": "string", "name": "node_id", "in": "path" }, { "required": true, "type": "string", "name": "path", "in": "path" } ], "get": { "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Retrieves the raw contents of a specific file in the snippet. The\n`Content-Disposition` header will be \"attachment\" to avoid issues with\nmalevolent executable files.\n\nThe file's mime type is derived from its filename and returned in the\n`Content-Type` header.\n\nNote that for text files, no character encoding is included as part of\nthe content type.", "responses": { "200": { "headers": { "Content-Type": { "type": "string", "description": "The mime type as derived from the filename" }, "Content-Disposition": { "type": "string", "description": "attachment" } }, "description": "Returns the contents of the specified file." }, "403": { "description": "If the authenticated user does not have access to the snippet.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the file or snippet does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "snippets" ] } }, "/addon/linkers/{linker_key}": { "parameters": [ { "required": true, "type": "string", "name": "linker_key", "in": "path" } ], "get": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] } }, "/repositories/{username}/{repo_slug}/refs/tags": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "refs" ] } }, "/repositories/{username}/{repo_slug}/commit/{node}/approve": { "post": { "security": [ { "oauth2": [ "repository:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Approve the specified commit as the authenticated user.\n\nThis operation is only available to users that have explicit access to\nthe repository. In contrast, just the fact that a repository is\npublicly accessible to users does not give them the ability to approve\ncommits.", "responses": { "200": { "description": "The `participant` object recording that the authenticated user approved the commit.", "schema": { "$ref": "#/definitions/participant" } }, "404": { "description": "If the specified commit, or the repository does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The commit's SHA1.", "name": "node", "in": "path" } ], "tags": [ "commits" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "node", "in": "path" } ], "delete": { "security": [ { "oauth2": [ "repository:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Redact the authenticated user's approval of the specified commit.\n\nThis operation is only available to users that have explicit access to\nthe repository. In contrast, just the fact that a repository is\npublicly accessible to users does not give them the ability to approve\ncommits.", "responses": { "204": { "description": "An empty response indicating the authenticated user's approval has been withdrawn." }, "404": { "description": "If the specified commit, or the repository does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The commit's SHA1.", "name": "node", "in": "path" } ], "tags": [ "commits" ] } }, "/account/{username}/addons": { "post": { "security": [ { "oauth2": [ "account:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" } ], "get": { "security": [ { "oauth2": [ "account:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] } }, "/repositories/{username}/{repo_slug}/pullrequests/{pull_request_id}/commits": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "pull_request_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "pullrequest" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the pull request's commits.\n\nThese are the commits that are being merged into the destination\nbranch when the pull requests gets accepted.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "pullrequests" ] } }, "/snippets/{username}/{encoded_id}/watchers": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "encoded_id", "in": "path" } ], "get": { "responses": { "200": { "description": "The paginated list of users watching this snippet", "schema": { "$ref": "#/definitions/paginated_users" } }, "404": { "description": "If the snippet does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The snippet id.", "name": "encoded_id", "in": "path" } ], "tags": [ "snippets" ], "deprecated": true, "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns a paginated list of all users watching a specific snippet." } }, "/repositories/{username}/{repo_slug}/branch-restrictions/{id}": { "put": { "security": [ { "oauth2": [ "repository:admin" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "branch_restrictions" ] }, "delete": { "security": [ { "oauth2": [ "repository:admin" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "branch_restrictions" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository:admin" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "branch_restrictions" ] } }, "/repositories/{username}/{repo_slug}/commit/{node}/statuses/build": { "post": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "node", "in": "path" } ] }, "/repositories/{username}/{repo_slug}": { "post": { "security": [ { "oauth2": [ "repository:admin" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Creates a new repository.", "responses": { "200": { "description": "The newly created repository.", "schema": { "$ref": "#/definitions/repository" } }, "401": { "description": "If the request was not authenticated.", "schema": { "$ref": "#/definitions/error" } }, "400": { "description": "If the input document was invalid, or if the caller lacks the privilege to create repositories under the targeted account.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "schema": { "$ref": "#/definitions/repository" }, "required": false, "description": "The repository that is to be created. Note that most object elements are optional. Elements \"owner\" and \"full_name\" are ignored as the URL implies them.", "name": "_body", "in": "body" } ], "tags": [ "repositories" ] }, "delete": { "security": [ { "oauth2": [ "repository:admin" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Deletes the repository. This is an irreversible operation.\n\nThis does not affect its forks.", "responses": { "204": { "description": "Indicates successful deletion." }, "403": { "description": "If the caller either does not have admin access to the repository, or the repository is set to read-only.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the repository does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "repositories" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the object describing this repository.", "responses": { "200": { "description": "The repository object.", "schema": { "$ref": "#/definitions/repository" } }, "403": { "description": "If the repository is private and the authenticated user does not have access to it.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If no repository exists at this location.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "repositories" ] } }, "/repositories/{username}/{repo_slug}/default-reviewers/{target_username}": { "put": { "security": [ { "oauth2": [ "repository:admin" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Adds the specified user to the repository's list of default\nreviewers.\n\nThis method is idempotent. Adding a user a second time has no effect.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "pullrequests" ] }, "delete": { "security": [ { "oauth2": [ "repository:admin" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Removes a default reviewer from the repository.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "pullrequests" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "target_username", "in": "path" } ], "get": { "security": [ { "oauth2": [ "repository:admin" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns the specified reviewer.\n\nThis can be used to test whether a user is among the repository's\ndefault reviewers list. A 404 indicates that that specified user is not\na default reviewer.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "pullrequests" ] } }, "/repositories/{username}/{repo_slug}/issues/{issue_id}/comments": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "issue_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "issue" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns all comments that were made on the specified issue.", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "issue_tracker" ] } }, "/snippets/{username}/{encoded_id}/{node_id}": { "put": { "responses": { "200": { "description": "The updated snippet object.", "schema": { "$ref": "#/definitions/snippet" } }, "401": { "description": "If the snippet is private and the request was not authenticated.", "schema": { "$ref": "#/definitions/error" } }, "403": { "description": "If authenticated user does not have permission to update the private snippet.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the snippet or the revision does not exist.", "schema": { "$ref": "#/definitions/error" } }, "405": { "description": "If `{node_id}` is not the latest revision.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The snippet's id.", "name": "encoded_id", "in": "path" }, { "required": true, "type": "string", "description": "A commit revision (SHA1).", "name": "node_id", "in": "path" } ], "tags": [ "snippets" ], "produces": [ "application/json", "multipart/related", "multipart/form-data" ], "security": [ { "oauth2": [ "snippet:write" ] }, { "basic": [] }, { "api_key": [] } ], "consumes": [ "application/json", "multipart/related", "multipart/form-data" ], "description": "Identical to `UPDATE /snippets/encoded_id`, except that this endpoint\ntakes an explicit commit revision. Only the snippet's \"HEAD\"/\"tip\"\n(most recent) version can be updated and requests on all other,\nolder revisions fail by returning a 405 status.\n\nUsage of this endpoint over the unrestricted `/snippets/encoded_id`\ncould be desired if the caller wants to be sure no concurrent\nmodifications have taken place between the moment of the UPDATE\nrequest and the original GET.\n\nThis can be considered a so-called \"Compare And Swap\", or CAS\noperation.\n\nOther than that, the two endpoints are identical in behavior." }, "delete": { "security": [ { "oauth2": [ "snippet:write" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Deletes the snippet.\n\nNote that this only works for versioned URLs that point to the latest\ncommit of the snippet. Pointing to an older commit results in a 405\nstatus code.\n\nTo delete a snippet, regardless of whether or not concurrent changes\nare being made to it, use `DELETE /snippets/{encoded_id}` instead.", "responses": { "401": { "description": "If the snippet is private and the request was not authenticated.", "schema": { "$ref": "#/definitions/error" } }, "403": { "description": "If authenticated user does not have permission to delete the private snippet.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the snippet does not exist.", "schema": { "$ref": "#/definitions/error" } }, "204": { "description": "If the snippet was deleted successfully." }, "405": { "description": "If `{node_id}` is not the latest revision.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The snippet's id.", "name": "encoded_id", "in": "path" } ], "tags": [ "snippets" ] }, "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "encoded_id", "in": "path" }, { "required": true, "type": "string", "name": "node_id", "in": "path" } ], "get": { "responses": { "200": { "description": "The snippet object.", "schema": { "$ref": "#/definitions/snippet" } }, "401": { "description": "If the snippet is private and the request was not authenticated.", "schema": { "$ref": "#/definitions/error" } }, "403": { "description": "If authenticated user does not have access to the private snippet.", "schema": { "$ref": "#/definitions/error" } }, "404": { "description": "If the snippet, or the revision does not exist.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [ { "required": true, "type": "string", "description": "The snippet's id.", "name": "encoded_id", "in": "path" }, { "required": true, "type": "string", "description": "A commit revision (SHA1).", "name": "node_id", "in": "path" } ], "produces": [ "application/json", "multipart/related", "multipart/form-data" ], "tags": [ "snippets" ], "security": [ { "oauth2": [ "snippet" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Identical to `GET /snippets/encoded_id`, except that this endpoint\ncan be used to retrieve the contents of the snippet as it was at an\nolder revision, while `/snippets/encoded_id` always returns the\nsnippet's current revision.\n\nNote that only the snippet's file contents are versioned, not its\nmeta data properties like the title.\n\nOther than that, the two endpoints are identical in behavior." } }, "/repositories/{username}/{repo_slug}/pullrequests/{pull_request_id}/patch": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" }, { "required": true, "type": "string", "name": "repo_slug", "in": "path" }, { "required": true, "type": "string", "name": "pull_request_id", "in": "path" } ], "get": { "security": [ { "oauth2": [ "pullrequest" ] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "pullrequests" ] } }, "/addon/linkers/{linker_key}/values/": { "delete": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] }, "parameters": [ { "required": true, "type": "string", "name": "linker_key", "in": "path" } ], "get": { "security": [ { "oauth2": [] }, { "basic": [] }, { "api_key": [] } ], "description": "", "responses": { "default": { "description": "Unexpected error.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "addon" ] } }, "/repositories": { "parameters": [], "get": { "security": [ { "oauth2": [ "repository" ] }, { "basic": [] }, { "api_key": [] } ], "description": "Returns all public repositories.", "responses": { "200": { "description": "All public repositories.", "schema": { "$ref": "#/definitions/paginated_repositories" } } }, "parameters": [], "tags": [ "repositories" ] } }, "/teams/{username}/members": { "parameters": [ { "required": true, "type": "string", "name": "username", "in": "path" } ], "get": { "security": [ { "oauth2": [ "account" ] }, { "basic": [] }, { "api_key": [] } ], "description": "All members of a team.\n\nReturns all members of the specified team. Any member of any of the\nteam's groups is considered a member of the team. This includes users\nin groups that may not actually have access to any of the team's\nrepositories.\n\nNote that members using the \"private profile\" feature are not included.", "responses": { "200": { "description": "All members", "schema": { "$ref": "#/definitions/user" } }, "404": { "description": "When the team does not exist, or multiple teams with the same name exist that differ only in casing and the URL did not match the exact casing of a particular one.", "schema": { "$ref": "#/definitions/error" } } }, "parameters": [], "tags": [ "teams" ] } } }, "schemes": [ "https" ], "tags": [ { "name": "users", "description": "" }, { "name": "teams", "description": "" }, { "name": "repositories", "description": "" }, { "name": "refs", "description": "" }, { "name": "commits", "description": "" }, { "name": "pullrequests", "description": "" }, { "name": "issue_tracker", "description": "The issues resource provides functionality for getting information on\nissues in an issue tracker, creating new issues, updating them and deleting\nthem.\n\nYou can access public issues without authentication, but you can't gain access\nto private repositories' issues. By authenticating, you will get the ability\nto create issues, as well as access to updating data or deleting issues you\nhave access to." }, { "name": "wiki", "description": "" }, { "name": "downloads", "description": "" }, { "name": "snippets", "description": "" }, { "name": "webhooks", "description": "Webhooks provide a way to configure Bitbucket Cloud to make requests to\nyour server (or another external service) whenever certain events occur in\nBitbucket Cloud.\n\nA webhook consists of:\n\n* A subject -- The resource that generates the events. Currently, this resource\n is the repository, user account, or team where you create the webhook.\n* One or more event -- The default event is a repository push, but you can\n select multiple events that can trigger the webhook.\n* A URL -- The endpoint where you want Bitbucket to send the event payloads\n when a matching event happens.\n\nThere are two parts to getting a webhook to work: creating the webhook and\ntriggering the webhook. After you create a webhook for an event, every time\nthat event occurs, Bitbucket sends a payload request that describes the event\nto the specified URL. Thus, you can think of webhooks as a kind of\nnotification system.\n\nUse webhooks to integrate applications with Bitbucket Cloud. The following\nuse cases provides examples of when you would want to use webhooks:\n\n* Every time a user pushes commits in a repository, you may want to notify\n your CI server to start a build.\n* Every time a user pushes commits or creates a pull request, you may want to\n display a notification in your application.\n" } ], "basePath": "/2.0", "produces": [ "application/json" ], "securityDefinitions": { "oauth2": { "scopes": { "wiki": "Read and write to your repositories' wikis", "snippet": "Read your snippets", "account": "Read your account information", "repository:admin": "Administer your repositories", "repository": "Read your repositories", "snippet:write": "Read and write to your snippets", "issue:write": "Read and write to your repositories' issues", "pullrequest": "Read your repositories and their pull requests", "webhook": "Read and write to your repositories' webhooks", "pullrequest:write": "Read and write to your repositories and their pull requests", "project": "Read your team's projects", "project:write": "Read and write to your team's projects and move repositories between them", "team": "Read your team membership information", "repository:write": "Read and write to your repositories", "team:write": "Read and write to your team membership information", "account:write": "Read and write to your account information", "issue": "Read your repositories' issues", "email": "Read your account's primary email address" }, "tokenUrl": "http://dev.bitbucket.org:8000/site/oauth2/access_token", "description": "OAuth 2 as per [RFC-6749](https://tools.ietf.org/html/rfc6749).", "flow": "accessCode", "type": "oauth2", "authorizationUrl": "http://dev.bitbucket.org:8000/site/oauth2/authorize" }, "api_key": { "in": "header", "type": "apiKey", "description": "API Keys can be used as Basic HTTP Authentication credentials and provide a substitute for the account's actual username and password. API Keys are only available to team accounts and there is only 1 key per account. API Keys do not support scopes and have therefore access to all contents of the account.", "name": "Authorization" }, "basic": { "type": "basic", "description": "Basic HTTP Authentication as per [RFC-2617](https://tools.ietf.org/html/rfc2617) (Digest not supported). Note that Basic Auth with username and password as credentials is only available on accounts that have 2-factor-auth / 2-step-verification disabled. If you use 2fa, you should authenticate using OAuth2 instead." } }, "x-revision": "dev-1452122794", "host": "api.dev.bitbucket.org:8000", "definitions": { "paginated_repositories": { "allOf": [ { "$ref": "#/definitions/page" }, { "additionalProperties": true, "type": "object", "description": "A paginated list of repositories.", "properties": { "values": { "minItems": 0, "items": { "$ref": "#/definitions/repository" }, "uniqueItems": true, "type": "array" } } } ] }, "subject_types": { "additionalProperties": false, "type": "object", "description": "The mapping of resource/subject types pointing to their individual event types.", "properties": { "user": { "additionalProperties": false, "type": "object", "properties": { "events": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } } } }, "repository": { "additionalProperties": false, "type": "object", "properties": { "events": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } } } }, "team": { "additionalProperties": false, "type": "object", "properties": { "events": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } } } } } }, "paginated_hook_events": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A paginated list of webhook types available to subscribe on.", "properties": { "next": { "type": "string", "description": "Link to the next page if it exists. The last page of a collection does not have this value. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" }, "values": { "minItems": 0, "items": { "$ref": "#/definitions/hook_event" }, "uniqueItems": true, "type": "array" }, "pagelen": { "minimum": 1, "type": "integer", "description": "Current number of objects on the existing page. The default value is 10 with 100 being the maximum allowed value. Individual APIs may enforce different values." }, "size": { "minimum": 0, "type": "integer", "description": "Total number of objects in the response. This is an optional element that is not provided in all responses, as it can be expensive to compute." }, "page": { "minimum": 1, "type": "integer", "description": "Page number of the current results. This is an optional element that is not provided in all responses." }, "previous": { "type": "string", "description": "Link to previous page if it exists. A collections first page does not have this value. This is an optional element that is not provided in all responses. Some result sets strictly support forward navigation and never provide previous links. Clients must anticipate that backwards navigation is not always available. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" } } } ] }, "base_commit": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "The common base type for both repository and snippet commits.", "properties": { "date": { "type": "string", "format": "date-time" }, "parents": { "minItems": 0, "items": { "$ref": "#/definitions/base_commit" }, "type": "array" }, "message": { "type": "string" }, "hash": { "pattern": "[0-9a-f]{7,}?", "type": "string" }, "author": { "$ref": "#/definitions/account" } } } ] }, "error": { "additionalProperties": false, "type": "object", "properties": { "error": { "additionalProperties": false, "required": [ "message" ], "type": "object", "properties": { "message": { "type": "string" }, "detail": { "type": "string" } } } } }, "participant": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "Object describing a user's role on resources like commits or pull requests.", "properties": { "role": { "enum": [ "PARTICIPANT", "REVIEWER" ], "type": "string" }, "user": { "$ref": "#/definitions/user" }, "approved": { "type": "boolean" } } } ] }, "paginated_versions": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A paginated list of issue tracker versions.", "properties": { "next": { "type": "string", "description": "Link to the next page if it exists. The last page of a collection does not have this value. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" }, "values": { "minItems": 0, "items": { "$ref": "#/definitions/version" }, "uniqueItems": true, "type": "array" }, "pagelen": { "minimum": 1, "type": "integer", "description": "Current number of objects on the existing page. The default value is 10 with 100 being the maximum allowed value. Individual APIs may enforce different values." }, "size": { "minimum": 0, "type": "integer", "description": "Total number of objects in the response. This is an optional element that is not provided in all responses, as it can be expensive to compute." }, "page": { "minimum": 1, "type": "integer", "description": "Page number of the current results. This is an optional element that is not provided in all responses." }, "previous": { "type": "string", "description": "Link to previous page if it exists. A collections first page does not have this value. This is an optional element that is not provided in all responses. Some result sets strictly support forward navigation and never provide previous links. Clients must anticipate that backwards navigation is not always available. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" } } } ] }, "paginated_users": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A paginated list of users.", "properties": { "next": { "type": "string", "description": "Link to the next page if it exists. The last page of a collection does not have this value. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" }, "values": { "minItems": 0, "items": { "$ref": "#/definitions/user" }, "uniqueItems": true, "type": "array" }, "pagelen": { "minimum": 1, "type": "integer", "description": "Current number of objects on the existing page. The default value is 10 with 100 being the maximum allowed value. Individual APIs may enforce different values." }, "size": { "minimum": 0, "type": "integer", "description": "Total number of objects in the response. This is an optional element that is not provided in all responses, as it can be expensive to compute." }, "page": { "minimum": 1, "type": "integer", "description": "Page number of the current results. This is an optional element that is not provided in all responses." }, "previous": { "type": "string", "description": "Link to previous page if it exists. A collections first page does not have this value. This is an optional element that is not provided in all responses. Some result sets strictly support forward navigation and never provide previous links. Clients must anticipate that backwards navigation is not always available. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" } } } ] }, "snippet": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A snippet object.", "properties": { "scm": { "enum": [ "hg", "git" ], "type": "string", "description": "The DVCS used to store the snippet." }, "title": { "type": "string" }, "creator": { "$ref": "#/definitions/account" }, "created_on": { "type": "string", "format": "date-time" }, "owner": { "$ref": "#/definitions/account" }, "updated_on": { "type": "string", "format": "date-time" }, "id": { "minimum": 0, "type": "integer" }, "is_private": { "type": "boolean" } } } ] }, "hook_event": { "additionalProperties": false, "type": "object", "description": "An event, associated with a resource or subject type.", "properties": { "category": { "type": "string", "description": "The category this event belongs to." }, "event": { "enum": [ "pullrequest:updated", "repo:commit_status_created", "repo:fork", "issue:comment_created", "pullrequest:rejected", "pullrequest:fulfilled", "pullrequest:comment_created", "pullrequest:comment_deleted", "issue:created", "repo:commit_comment_created", "pullrequest:approved", "repo:commit_status_updated", "pullrequest:comment_updated", "issue:updated", "pullrequest:unapproved", "pullrequest:created", "repo:push" ], "type": "string", "description": "The event identifier." }, "description": { "type": "string", "description": "More detailed description of the webhook event type." }, "label": { "type": "string", "description": "Summary of the webhook event type." } } }, "version": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A version as defined in a repository's issue tracker.", "properties": { "name": { "type": "string" }, "links": { "additionalProperties": false, "type": "object", "properties": { "self": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } } } }, "id": { "type": "integer" } } } ] }, "issue": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "An issue.", "properties": { "content": { "additionalProperties": false, "type": "object", "properties": { "raw": { "type": "string", "description": "The text as it was typed by a user." }, "markup": { "enum": [ "markdown", "creole" ], "type": "string", "description": "The type of markup language the content is to be interpreted in." }, "html": { "type": "string", "description": "The user's markup rendered as HTML." } } }, "kind": { "enum": [ "bug", "enhancement", "proposal", "task" ], "type": "string" }, "repository": { "$ref": "#/definitions/repository" }, "links": { "additionalProperties": false, "type": "object", "properties": { "attachments": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "self": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "watch": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "comments": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "html": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "vote": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } } } }, "title": { "type": "string" }, "reporter": { "$ref": "#/definitions/user" }, "component": { "$ref": "#/definitions/component" }, "votes": { "type": "integer" }, "priority": { "enum": [ "trivial", "minor", "major", "critical", "blocker" ], "type": "string" }, "assignee": { "$ref": "#/definitions/user" }, "state": { "enum": [ "new", "open", "resolved", "on hold", "invalid", "duplicate", "wontfix", "closed" ], "type": "string" }, "version": { "$ref": "#/definitions/version" }, "edited_on": { "type": "string", "format": "date-time" }, "created_on": { "type": "string", "format": "date-time" }, "milestone": { "$ref": "#/definitions/milestone" }, "updated_on": { "type": "string", "format": "date-time" }, "id": { "type": "integer" } } } ] }, "webhook_subscription": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A Webhook subscription.", "properties": { "subject_type": { "enum": [ "user", "repository", "team" ], "type": "string", "description": "The type of entity, which is `repository` in the case of webhook subscriptions on repositories." }, "uuid": { "type": "string", "description": "The webhook's id" }, "url": { "type": "string", "description": "The URL events get delivered to.", "format": "uri" }, "created_at": { "type": "string", "format": "date-time" }, "description": { "type": "string", "description": "A user-defined description of the webhook." }, "active": { "type": "boolean" }, "events": { "minItems": 1, "items": { "enum": [ "pullrequest:updated", "repo:commit_status_created", "repo:fork", "issue:comment_created", "pullrequest:rejected", "pullrequest:fulfilled", "pullrequest:comment_created", "pullrequest:comment_deleted", "issue:created", "repo:commit_comment_created", "pullrequest:approved", "repo:commit_status_updated", "pullrequest:comment_updated", "issue:updated", "pullrequest:unapproved", "pullrequest:created", "repo:push" ], "type": "string" }, "uniqueItems": true, "description": "The events this webhook is subscribed to.", "type": "array" }, "subject": { "$ref": "#/definitions/object" } } } ] }, "repository": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A Bitbucket repository.", "properties": { "scm": { "enum": [ "hg", "git" ], "type": "string" }, "has_wiki": { "type": "boolean" }, "uuid": { "type": "string", "description": "The repository's immutable id. This can be used as a substitute for the slug segment in URLs. Doing this guarantees your URLs will survive renaming of the repository by its owner, or even transfer of the repository to a different user." }, "links": { "additionalProperties": false, "type": "object", "properties": { "watchers": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "commits": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "downloads": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "self": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "html": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "avatar": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "hooks": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "forks": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "clone": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "pullrequests": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } } } }, "fork_policy": { "enum": [ "allow_forks", "no_public_forks", "no_forks" ], "type": "string", "description": "\nControls the rules for forking this repository.\n\n* **allow_forks**: unrestricted forking\n* **no_public_forks**: restrict forking to private forks (forks cannot\n be made public later)\n* **no_forks**: deny all forking\n" }, "description": { "type": "string" }, "language": { "type": "string" }, "created_on": { "type": "string", "format": "date-time" }, "parent": { "$ref": "#/definitions/repository" }, "full_name": { "type": "string", "description": "The concatenation of the repository owner's username and the slugified name, e.g. \"evzijst/interruptingcow\". This is the same string used in Bitbucket URLs." }, "has_issues": { "type": "boolean" }, "owner": { "$ref": "#/definitions/account" }, "updated_on": { "type": "string", "format": "date-time" }, "size": { "type": "integer" }, "is_private": { "type": "boolean" }, "name": { "type": "string" } } } ] }, "snippet_commit": { "allOf": [ { "$ref": "#/definitions/base_commit" }, { "additionalProperties": true, "type": "object", "description": "", "properties": { "snippet": { "$ref": "#/definitions/snippet" }, "links": { "additionalProperties": false, "type": "object", "properties": { "diff": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "self": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "html": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } } } } } } ] }, "object": { "description": "Base type for most resource objects. It defines the common `type` element that identifies an object's type. It also identifies the element as Swagger's `discriminator`.", "required": [ "type" ], "additionalProperties": true, "discriminator": "type", "type": "object", "properties": { "type": { "type": "string" } } }, "component": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A component as defined in a repository's issue tracker.", "properties": { "name": { "type": "string" }, "links": { "additionalProperties": false, "type": "object", "properties": { "self": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } } } }, "id": { "type": "integer" } } } ] }, "paginated_issues": { "allOf": [ { "$ref": "#/definitions/page" }, { "additionalProperties": true, "type": "object", "description": "A paginated list of issues.", "properties": { "values": { "minItems": 0, "items": { "$ref": "#/definitions/issue" }, "uniqueItems": true, "type": "array" } } } ] }, "user": { "allOf": [ { "$ref": "#/definitions/account" }, { "additionalProperties": true, "type": "object", "description": "A user object.", "properties": {} } ] }, "milestone": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A milestone as defined in a repository's issue tracker.", "properties": { "name": { "type": "string" }, "links": { "additionalProperties": false, "type": "object", "properties": { "self": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } } } }, "id": { "type": "integer" } } } ] }, "paginated_issue_attachments": { "allOf": [ { "$ref": "#/definitions/page" }, { "additionalProperties": true, "type": "object", "description": "A paginated list of issue attachments.", "properties": { "values": { "minItems": 0, "items": { "$ref": "#/definitions/issue_attachment" }, "type": "array" } } } ] }, "paginated_webhook_subscriptions": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A paginated list of webhook subscriptions", "properties": { "next": { "type": "string", "description": "Link to the next page if it exists. The last page of a collection does not have this value. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" }, "values": { "minItems": 0, "items": { "$ref": "#/definitions/webhook_subscription" }, "uniqueItems": true, "type": "array" }, "pagelen": { "minimum": 1, "type": "integer", "description": "Current number of objects on the existing page. The default value is 10 with 100 being the maximum allowed value. Individual APIs may enforce different values." }, "size": { "minimum": 0, "type": "integer", "description": "Total number of objects in the response. This is an optional element that is not provided in all responses, as it can be expensive to compute." }, "page": { "minimum": 1, "type": "integer", "description": "Page number of the current results. This is an optional element that is not provided in all responses." }, "previous": { "type": "string", "description": "Link to previous page if it exists. A collections first page does not have this value. This is an optional element that is not provided in all responses. Some result sets strictly support forward navigation and never provide previous links. Clients must anticipate that backwards navigation is not always available. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" } } } ] }, "snippet_comment": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A comment on a snippet.", "properties": { "snippet": { "$ref": "#/definitions/snippet" }, "links": { "additionalProperties": false, "type": "object", "properties": { "self": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "html": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } } } } } } ] }, "paginated_milestones": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A paginated list of issue tracker milestones.", "properties": { "next": { "type": "string", "description": "Link to the next page if it exists. The last page of a collection does not have this value. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" }, "values": { "minItems": 0, "items": { "$ref": "#/definitions/milestone" }, "uniqueItems": true, "type": "array" }, "pagelen": { "minimum": 1, "type": "integer", "description": "Current number of objects on the existing page. The default value is 10 with 100 being the maximum allowed value. Individual APIs may enforce different values." }, "size": { "minimum": 0, "type": "integer", "description": "Total number of objects in the response. This is an optional element that is not provided in all responses, as it can be expensive to compute." }, "page": { "minimum": 1, "type": "integer", "description": "Page number of the current results. This is an optional element that is not provided in all responses." }, "previous": { "type": "string", "description": "Link to previous page if it exists. A collections first page does not have this value. This is an optional element that is not provided in all responses. Some result sets strictly support forward navigation and never provide previous links. Clients must anticipate that backwards navigation is not always available. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" } } } ] }, "paginated_components": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A paginated list of issue tracker components.", "properties": { "next": { "type": "string", "description": "Link to the next page if it exists. The last page of a collection does not have this value. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" }, "values": { "minItems": 0, "items": { "$ref": "#/definitions/component" }, "uniqueItems": true, "type": "array" }, "pagelen": { "minimum": 1, "type": "integer", "description": "Current number of objects on the existing page. The default value is 10 with 100 being the maximum allowed value. Individual APIs may enforce different values." }, "size": { "minimum": 0, "type": "integer", "description": "Total number of objects in the response. This is an optional element that is not provided in all responses, as it can be expensive to compute." }, "page": { "minimum": 1, "type": "integer", "description": "Page number of the current results. This is an optional element that is not provided in all responses." }, "previous": { "type": "string", "description": "Link to previous page if it exists. A collections first page does not have this value. This is an optional element that is not provided in all responses. Some result sets strictly support forward navigation and never provide previous links. Clients must anticipate that backwards navigation is not always available. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" } } } ] }, "account": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "An account object.", "properties": { "username": { "pattern": "^[a-zA-Z0-9_\\-]+$", "type": "string" }, "website": { "type": "string" }, "display_name": { "type": "string" }, "uuid": { "type": "string" }, "links": { "additionalProperties": false, "type": "object", "properties": { "self": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "repositories": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "html": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "followers": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "avatar": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "following": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } } } }, "created_on": { "type": "string", "format": "date-time" } } } ] }, "issue_attachment": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "An issue file attachment's meta data. Note this does not contain the file's actual contents.", "properties": { "name": { "type": "string" }, "links": { "additionalProperties": false, "type": "object", "properties": { "self": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } } } } } } ] }, "paginated_snippet_commit": { "allOf": [ { "$ref": "#/definitions/page" }, { "additionalProperties": true, "type": "object", "description": "A paginated list of snippet commits.", "properties": { "values": { "minItems": 0, "items": { "$ref": "#/definitions/snippet_commit" }, "type": "array" } } } ] }, "pullrequest": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A pull request object.", "properties": { "state": { "enum": [ "MERGED", "SUPERSEDED", "OPEN", "DECLINED" ], "type": "string" }, "author": { "$ref": "#/definitions/account" }, "id": { "type": "integer" }, "links": { "additionalProperties": false, "type": "object", "properties": { "decline": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "commits": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "self": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "comments": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "merge": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "html": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "activity": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "diff": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "approve": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } } } }, "title": { "type": "string" } } } ] }, "paginated_teams": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A paginated list of teams.", "properties": { "next": { "type": "string", "description": "Link to the next page if it exists. The last page of a collection does not have this value. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" }, "values": { "minItems": 0, "items": { "$ref": "#/definitions/team" }, "uniqueItems": true, "type": "array" }, "pagelen": { "minimum": 1, "type": "integer", "description": "Current number of objects on the existing page. The default value is 10 with 100 being the maximum allowed value. Individual APIs may enforce different values." }, "size": { "minimum": 0, "type": "integer", "description": "Total number of objects in the response. This is an optional element that is not provided in all responses, as it can be expensive to compute." }, "page": { "minimum": 1, "type": "integer", "description": "Page number of the current results. This is an optional element that is not provided in all responses." }, "previous": { "type": "string", "description": "Link to previous page if it exists. A collections first page does not have this value. This is an optional element that is not provided in all responses. Some result sets strictly support forward navigation and never provide previous links. Clients must anticipate that backwards navigation is not always available. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" } } } ] }, "paginated_snippet_comments": { "allOf": [ { "$ref": "#/definitions/page" }, { "additionalProperties": true, "type": "object", "description": "A paginated list of snippet comments.", "properties": { "values": { "minItems": 0, "items": { "$ref": "#/definitions/snippet_comment" }, "uniqueItems": true, "type": "array" } } } ] }, "team": { "allOf": [ { "$ref": "#/definitions/account" }, { "additionalProperties": true, "type": "object", "description": "A team object.", "properties": {} } ] }, "commit": { "allOf": [ { "$ref": "#/definitions/base_commit" }, { "additionalProperties": true, "type": "object", "description": "A repository commit object.", "properties": { "participants": { "minItems": 0, "items": { "$ref": "#/definitions/participant" }, "type": "array" }, "repository": { "$ref": "#/definitions/repository" }, "links": { "additionalProperties": false, "type": "object", "properties": { "self": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "comments": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "patch": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "html": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "diff": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } }, "approve": { "additionalProperties": false, "type": "object", "properties": { "href": { "type": "string", "format": "uri" } } } } } } } ] }, "paginated_snippets": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "A paginated list of snippets.", "properties": { "next": { "type": "string", "description": "Link to the next page if it exists. The last page of a collection does not have this value. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" }, "values": { "minItems": 0, "items": { "$ref": "#/definitions/snippet" }, "uniqueItems": true, "type": "array" }, "pagelen": { "minimum": 1, "type": "integer", "description": "Current number of objects on the existing page. The default value is 10 with 100 being the maximum allowed value. Individual APIs may enforce different values." }, "size": { "minimum": 0, "type": "integer", "description": "Total number of objects in the response. This is an optional element that is not provided in all responses, as it can be expensive to compute." }, "page": { "minimum": 1, "type": "integer", "description": "Page number of the current results. This is an optional element that is not provided in all responses." }, "previous": { "type": "string", "description": "Link to previous page if it exists. A collections first page does not have this value. This is an optional element that is not provided in all responses. Some result sets strictly support forward navigation and never provide previous links. Clients must anticipate that backwards navigation is not always available. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" } } } ] }, "page": { "allOf": [ { "$ref": "#/definitions/object" }, { "additionalProperties": true, "type": "object", "description": "Base type for most resource objects. It defines the common `type` element that identifies an object's type. It also identifies the element as Swagger's `discriminator`.", "properties": { "previous": { "type": "string", "description": "Link to previous page if it exists. A collections first page does not have this value. This is an optional element that is not provided in all responses. Some result sets strictly support forward navigation and never provide previous links. Clients must anticipate that backwards navigation is not always available. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" }, "pagelen": { "minimum": 1, "type": "integer", "description": "Current number of objects on the existing page. The default value is 10 with 100 being the maximum allowed value. Individual APIs may enforce different values." }, "next": { "type": "string", "description": "Link to the next page if it exists. The last page of a collection does not have this value. Use this link to navigate the result set and refrain from constructing your own URLs.", "format": "uri" }, "page": { "minimum": 1, "type": "integer", "description": "Page number of the current results. This is an optional element that is not provided in all responses." }, "size": { "minimum": 0, "type": "integer", "description": "Total number of objects in the response. This is an optional element that is not provided in all responses, as it can be expensive to compute." } } } ] } }, "swagger": "2.0", "consumes": [ "application/json" ] } golang-github-go-openapi-analysis-0.15.0/fixtures/definitions.yml000066400000000000000000000031571332100444700251140ustar00rootroot00000000000000--- swagger: "2.0" info: version: "0.1.0" title: Definition analysis parameters: someParam: name: someParam in: body schema: type: object responses: someResponse: schema: type: object paths: "/some/where/{id}": parameters: - name: id in: path type: integer format: int32 - name: bodyId in: body schema: type: object get: parameters: - name: limit in: query type: integer format: int32 required: false - name: body in: body schema: type: object responses: default: schema: type: object 200: schema: type: object definitions: tag: type: object properties: id: type: integer format: int64 value: type: string definitions: category: type: object properties: id: type: integer format: int32 value: type: string withAdditionalProps: type: object additionalProperties: type: boolean withAdditionalItems: type: array items: - type: string - type: bool additionalItems: type: integer format: int32 withNot: type: object not: $ref: "#/definitions/tag" withAnyOf: anyOf: - type: object - type: string withOneOf: type: object oneOf: - $ref: "#/definitions/tag" - $ref: "#/definitions/withAdditionalProps" withAllOf: allOf: - type: object - type: string golang-github-go-openapi-analysis-0.15.0/fixtures/empty-paths.json000066400000000000000000000001731332100444700252170ustar00rootroot00000000000000{ "swagger": "2.0", "info": { "title": "empty-paths", "version": "79.2.1" }, "paths": {} } golang-github-go-openapi-analysis-0.15.0/fixtures/errors.yml000066400000000000000000000000471332100444700241100ustar00rootroot00000000000000error: type: string maxLength: 255 golang-github-go-openapi-analysis-0.15.0/fixtures/errors/000077500000000000000000000000001332100444700233645ustar00rootroot00000000000000golang-github-go-openapi-analysis-0.15.0/fixtures/errors/fixture-unexpandable-2.yaml000066400000000000000000000012401332100444700305360ustar00rootroot00000000000000--- swagger: '2.0' info: title: cannot expand schemes: - http basePath: /api consumes: - application/json produces: - application/json parameters: someWhere: name: someWhere in: body required: true schema: type: integer paths: /common: get: operationId: commonGet summary: here to test path collisons responses: '200': description: OK schema: type: array items: $ref: '#/definitions/nowhere' '201': description: OK schema: $ref: '#/thisIs/anAbitrary/jsonPointer/toNowhere' definitions: somePlace: type: string golang-github-go-openapi-analysis-0.15.0/fixtures/errors/fixture-unexpandable.yaml000066400000000000000000000007261332100444700304070ustar00rootroot00000000000000--- swagger: '2.0' info: title: cannot expand schemes: - http basePath: /api consumes: - application/json produces: - application/json parameters: someWhere: name: someWhere in: body required: true schema: type: integer paths: /common: get: operationId: commonGet summary: here to test path collisons responses: '200': description: OK schema: $ref: "nowhere.yml#/definitions/foo" golang-github-go-openapi-analysis-0.15.0/fixtures/external/000077500000000000000000000000001332100444700236725ustar00rootroot00000000000000golang-github-go-openapi-analysis-0.15.0/fixtures/external/definitions.yml000066400000000000000000000021451332100444700267320ustar00rootroot00000000000000definitions: named: type: string tag: type: object properties: id: type: integer format: int64 value: type: string audit: $ref: "#/definitions/record" record: type: object properties: createdAt: type: string format: date-time nestedThing: type: object properties: record: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time - allOf: - type: string format: date - type: object additionalProperties: type: object properties: id: type: integer format: int64 value: type: string properties: value: type: string name: $ref: "definitions2.yml#/coordinate"golang-github-go-openapi-analysis-0.15.0/fixtures/external/definitions2.yml000066400000000000000000000002261332100444700270120ustar00rootroot00000000000000coordinate: type: object properties: id: type: integer format: int64 createdAt: type: string format: date-timegolang-github-go-openapi-analysis-0.15.0/fixtures/external/errors.yml000066400000000000000000000003011332100444700257230ustar00rootroot00000000000000error: type: object required: - id - message properties: id: type: integer format: int64 readOnly: true message: type: string readOnly: true golang-github-go-openapi-analysis-0.15.0/fixtures/external/nestedParams.yml000066400000000000000000000015131332100444700270430ustar00rootroot00000000000000bodyParam: name: body in: body schema: type: object properties: record: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time - allOf: - type: string format: date - type: object properties: id: type: integer format: int64 value: type: string name: type: object properties: id: type: integer format: int64 createdAt: type: string format: date-timegolang-github-go-openapi-analysis-0.15.0/fixtures/external/nestedResponses.yml000066400000000000000000000013671332100444700276100ustar00rootroot00000000000000genericResponse: type: object properties: record: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time - allOf: - type: string format: date - type: object properties: id: type: integer format: int64 value: type: string name: type: object properties: id: type: integer format: int64 createdAt: type: string format: date-timegolang-github-go-openapi-analysis-0.15.0/fixtures/external/parameters.yml000066400000000000000000000002701332100444700265570ustar00rootroot00000000000000parameters: idParam: name: id in: path type: integer format: int32 limitParam: name: limit in: query type: integer format: int32 required: falsegolang-github-go-openapi-analysis-0.15.0/fixtures/external/pathItem.yml000066400000000000000000000003701332100444700261700ustar00rootroot00000000000000get: operationId: modelOp summary: many model variations description: Used to see if a codegen can render all the possible parameter variations for a header param tags: - testcgen responses: default: description: Generic Outgolang-github-go-openapi-analysis-0.15.0/fixtures/external/responses.yml000066400000000000000000000001021332100444700264270ustar00rootroot00000000000000responses: notFound: schema: $ref: "errors.yml#/error"golang-github-go-openapi-analysis-0.15.0/fixtures/external_definitions.yml000066400000000000000000000042701332100444700270130ustar00rootroot00000000000000--- swagger: "2.0" info: version: "0.1.0" title: reference analysis parameters: someParam: name: someParam in: body schema: $ref: "external/definitions.yml#/definitions/record" responses: someResponse: schema: $ref: "external/definitions.yml#/definitions/record" paths: "/some/where/{id}": parameters: - $ref: "external/parameters.yml#/parameters/idParam" - name: bodyId in: body schema: $ref: "external/definitions.yml#/definitions/record" get: parameters: - $ref: "external/parameters.yml#/parameters/limitParam" - name: other in: query type: array items: $ref: "external/definitions.yml#/definitions/named" - name: body in: body schema: $ref: "external/definitions.yml#/definitions/record" responses: default: schema: $ref: "external/definitions.yml#/definitions/record" 404: $ref: "external/responses.yml#/responses/notFound" 200: schema: $ref: "external/definitions.yml#/definitions/tag" "/other/place": $ref: "external/pathItem.yml" definitions: namedAgain: $ref: "external/definitions.yml#/definitions/named" datedTag: allOf: - type: string format: date - $ref: "external/definitions.yml#/definitions/tag" records: type: array items: - $ref: "external/definitions.yml#/definitions/record" datedRecords: type: array items: - type: string format: date-time - $ref: "external/definitions.yml#/definitions/record" datedTaggedRecords: type: array items: - type: string format: date-time - $ref: "external/definitions.yml#/definitions/record" additionalItems: $ref: "external/definitions.yml#/definitions/tag" otherRecords: type: array items: $ref: "external/definitions.yml#/definitions/record" tags: type: object additionalProperties: $ref: "external/definitions.yml#/definitions/tag" namedThing: type: object properties: name: $ref: "external/definitions.yml#/definitions/named"golang-github-go-openapi-analysis-0.15.0/fixtures/fixer/000077500000000000000000000000001332100444700231655ustar00rootroot00000000000000golang-github-go-openapi-analysis-0.15.0/fixtures/fixer/fixer.yaml000066400000000000000000000064131332100444700251720ustar00rootroot00000000000000--- swagger: '2.0' info: title: spec fixing of empty descriptions version: x80.86 schemes: - http - https basePath: /zorg responses: someResponse: description: '' schema: type: integer anotherRespone: # nil case #description: '' schema: type: integer paths: /noDesc: get: responses: default: description: '' schema: type: integer 200: description: '' schema: type: integer put: responses: default: description: '' schema: type: integer 200: description: '' schema: type: integer delete: responses: default: description: '' schema: type: integer 200: description: '' schema: type: integer post: responses: default: description: '' schema: type: integer 200: description: '' schema: type: integer options: responses: default: description: '' schema: type: integer 200: description: '' schema: type: integer patch: responses: default: description: '' schema: type: integer 200: description: '' schema: type: integer head: responses: default: description: '' schema: type: integer 200: description: '' schema: type: integer /withDesc: get: responses: default: description: 'my description' schema: type: integer 200: description: 'my description' schema: type: integer put: responses: default: description: 'my description' schema: type: integer 200: description: 'my description' schema: type: integer delete: responses: default: description: 'my description' schema: type: integer 200: description: 'my description' schema: type: integer post: responses: default: description: 'my description' schema: type: integer 200: description: 'my description' schema: type: integer options: responses: default: description: 'my description' schema: type: integer 200: description: 'my description' schema: type: integer patch: responses: default: description: 'my description' schema: type: integer 200: description: 'my description' schema: type: integer head: responses: default: description: 'my description' schema: type: integer 200: description: 'my description' schema: type: integer 300: $ref: '#/somewhere/inspace' golang-github-go-openapi-analysis-0.15.0/fixtures/fixture-1289-param.yaml000066400000000000000000000011271332100444700261220ustar00rootroot00000000000000--- swagger: '2.0' info: title: 'fixture 1289' description: an invalid spec but which passes the analysis stage version: '1.0' produces: - application/json paths: '/fixture': get: operationId: fixtureOp parameters: - $ref: '#/parameters/getSomeIds' responses: '200': parameters: getSomeIds: name: despicableMe in: body description: a bad parameter description schema: type: object properties: someIds: # Wrong now $ref: '#/definitions/someIds' type: array someIds: type: string golang-github-go-openapi-analysis-0.15.0/fixtures/fixture-342-2.yaml000066400000000000000000000012671332100444700250750ustar00rootroot00000000000000swagger: '2.0' info: title: issue-342-2 description: | A spec which triggers a panic because of invalid ref version: 0.0.1 license: name: MIT host: localhost:8081 basePath: /api/v1 schemes: - http consumes: - application/json produces: - application/json paths: /fixture: get: tags: - maindata operationID: fixtureOp parameters: # Wrong: a whole schema replaces the parameter - name: wrongme in: query required: true $ref: "#/definitions/sample_info/properties/sid" responses: '200': definitions: sample_info: type: object properties: sid: type: string golang-github-go-openapi-analysis-0.15.0/fixtures/fixture-342-3.yaml000066400000000000000000000012721332100444700250720ustar00rootroot00000000000000swagger: '2.0' info: title: issue-342 description: | A spec which triggers a panic because of invalid type assertion on parameters version: 0.0.1 license: name: MIT host: localhost:8081 basePath: /api/v1 schemes: - http consumes: - application/json produces: - application/json paths: /fixture: get: tags: - maindata operationID: fixtureOp parameters: # Wrong: invalid ref - name: despicableme in: query required: true $ref: "#/definitions/sample_info/properties/sids" responses: '200': definitions: sample_info: type: object properties: sid: type: string golang-github-go-openapi-analysis-0.15.0/fixtures/fixture-342.yaml000066400000000000000000000015631332100444700247350ustar00rootroot00000000000000swagger: '2.0' info: title: issue-342 description: | A spec which triggers a panic because of invalid type assertion on parameters version: 0.0.1 license: name: MIT host: localhost:8081 basePath: /api/v1 schemes: - http consumes: - application/json produces: - application/json paths: /fixture: get: tags: - maindata operationID: fixtureOp parameters: # Wrong: a whole schema replaces the parameter - name: wrongme in: query required: true $ref: "#/definitions/sample_info/properties/sid" # Wrong: invalid ref - name: despicableme in: query required: true $ref: "#/definitions/sample_info/properties/sids" responses: '200': definitions: sample_info: type: object properties: sid: type: string golang-github-go-openapi-analysis-0.15.0/fixtures/flatten.yml000066400000000000000000000035211332100444700242310ustar00rootroot00000000000000--- swagger: "2.0" info: version: "0.1.0" title: reference analysis parameters: someParam: name: some in: query type: string responses: notFound: description: "Not Found" schema: $ref: "external/errors.yml#/error" paths: "/some/where/{id}": parameters: - $ref: "external/parameters.yml#/parameters/idParam" get: parameters: - $ref: "external/parameters.yml#/parameters/limitParam" - $ref: "#/parameters/someParam" - name: other in: query type: string - $ref: "external/nestedParams.yml#/bodyParam" responses: default: $ref: "external/nestedResponses.yml#/genericResponse" 404: $ref: "#/responses/notFound" 200: description: "RecordHolder" schema: type: object properties: record: $ref: "external/definitions.yml#/definitions/nestedThing" "/other/place": $ref: "external/pathItem.yml" definitions: namedAgain: $ref: "external/definitions.yml#/definitions/named" datedTag: allOf: - type: string format: date - $ref: "external/definitions.yml#/definitions/tag" records: type: array items: - $ref: "external/definitions.yml#/definitions/record" datedRecords: type: array items: - type: string format: date-time - $ref: "external/definitions.yml#/definitions/record" otherRecords: type: array items: $ref: "external/definitions.yml#/definitions/record" tags: type: object additionalProperties: $ref: "external/definitions.yml#/definitions/tag" namedThing: type: object properties: name: $ref: "external/definitions.yml#/definitions/named" namedAgain: $ref: "#/definitions/namedAgain" golang-github-go-openapi-analysis-0.15.0/fixtures/foo-crud.yml000066400000000000000000000070501332100444700243130ustar00rootroot00000000000000--- swagger: '2.0' info: title: foo CRUD API version: 4.2.0 schemes: - http basePath: /api consumes: - application/json produces: - application/json paths: /common: get: operationId: commonGet summary: here to test path collisons responses: '200': description: OK schema: $ref: "#/definitions/foo" /foos: post: operationId: create summary: Create a new foo parameters: - name: info in: body schema: $ref: "#/definitions/foo" responses: '201': description: created schema: $ref: "#/definitions/fooId" default: description: error schema: $ref: "#/definitions/error" /foos/{fooid}: get: operationId: get summary: Get a foo by id parameters: - $ref: "#/parameters/fooid" responses: '200': description: OK schema: $ref: "#/definitions/foo" '401': $ref: "#/responses/401" '404': $ref: "#/responses/404" default: description: error schema: $ref: "#/definitions/error" delete: operationId: delete summary: delete a foo by id parameters: - name: fooid in: path required: true type: string responses: '200': description: OK '401': description: unauthorized schema: $ref: "#/definitions/error" '404': description: resource not found schema: $ref: "#/definitions/error" default: description: error schema: $ref: "#/definitions/error" post: operationId: update summary: update a foo by id parameters: - name: fooid in: path required: true type: string - name: info in: body schema: $ref: "#/definitions/foo" responses: '200': description: OK '401': description: unauthorized schema: $ref: "#/definitions/error" '404': description: resource not found schema: $ref: "#/definitions/error" default: description: error schema: $ref: "#/definitions/error" definitions: common: type: object required: - id properties: id: type: string format: string minLength: 1 foo: type: object required: - name - description properties: id: type: string format: string readOnly: true name: type: string format: string minLength: 1 description: type: string format: string minLength: 1 fooId: type: object required: - id properties: id: type: string format: string minLength: 1 error: type: object required: - message properties: code: type: string format: string message: type: string fields: type: string parameters: common: name: common in: query type: string fooid: name: fooid in: path required: true type: string responses: 401: description: foo unauthorized schema: $ref: "#/definitions/error" 404: description: foo resource not found schema: $ref: "#/definitions/error" golang-github-go-openapi-analysis-0.15.0/fixtures/inline_schemas.yml000066400000000000000000000070521332100444700255600ustar00rootroot00000000000000--- swagger: "2.0" info: version: "0.1.0" title: reference analysis parameters: someParam: name: someParam in: body schema: type: object properties: createdAt: type: string format: date-time responses: someResponse: schema: type: object properties: createdAt: type: string format: date-time paths: "/some/where/{id}": parameters: - name: id in: path type: integer format: int32 - name: bodyId in: body schema: type: object properties: createdAt: type: string format: date-time post: responses: default: description: all good get: parameters: - name: limit in: query type: integer format: int32 required: false - name: other in: query type: array items: type: object properties: id: type: integer format: int64 - name: body in: body schema: type: object properties: createdAt: type: string format: date-time responses: default: schema: type: object properties: createdAt: type: string format: date-time 404: schema: $ref: "errors.yml#/error" 200: schema: type: object properties: id: type: integer format: int64 value: type: string "/other/place": $ref: "external/pathItem.yml" definitions: namedAgain: type: object properties: id: type: integer format: int64 datedTag: allOf: - type: string format: date - type: object properties: id: type: integer format: int64 value: type: string records: type: array items: - type: object properties: createdAt: type: string format: date-time datedRecords: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time datedTaggedRecords: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time additionalItems: type: object properties: id: type: integer format: int64 value: type: string otherRecords: type: array items: type: object properties: createdAt: type: string format: date-time tags: type: object additionalProperties: type: object properties: id: type: integer format: int64 value: type: string namedThing: type: object properties: name: type: object properties: id: type: integer format: int64 # depth first should have this at the bottom, it's just a very long name pneumonoultramicroscopicsilicovolcanoconiosisAntidisestablishmentarianism: type: object properties: floccinaucinihilipilificationCreatedAt: type: integer format: int64 golang-github-go-openapi-analysis-0.15.0/fixtures/more_nested_inline_schemas.yml000066400000000000000000000200661332100444700301440ustar00rootroot00000000000000--- swagger: "2.0" info: version: "0.1.0" title: reference analysis parameters: someParam: name: someParam in: body schema: type: object properties: createdAt: type: string format: date-time responses: someResponse: schema: type: object properties: createdAt: type: string format: date-time paths: "/some/where/{id}": parameters: - name: id in: path type: integer format: int32 - name: bodyId in: body schema: type: array items: type: object properties: createdAt: type: string format: date-time post: responses: 200: description: some nested maps schema: type: object additionalProperties: type: object additionalProperties: type: object additionalProperties: type: obect properties: prop1: type: integer prop2: type: string 204: description: some nested maps schema: type: object additionalProperties: type: array items: type: object additionalProperties: type: array items: type: object additionalProperties: type: array items: type: object properties: prop3: type: integer prop4: type: string default: description: all good get: parameters: - name: limit in: query type: integer format: int32 required: false - name: other in: query type: array items: type: object properties: id: type: integer format: int64 - name: body in: body schema: type: object properties: record: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time - allOf: - type: string format: date - type: object properties: id: type: integer format: int64 value: type: string name: type: object properties: id: type: integer format: int64 createdAt: type: string format: date-time responses: default: schema: type: object properties: record: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time - allOf: - type: string format: date - type: object properties: id: type: integer format: int64 value: type: string name: type: object properties: id: type: integer format: int64 createdAt: type: string format: date-time 404: schema: $ref: "external/errors.yml#/error" 200: schema: type: object properties: record: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time - allOf: - type: string format: date - type: object properties: id: type: integer format: int64 value: type: string name: type: object properties: id: type: integer format: int64 createdAt: type: string format: date-time "/other/place": $ref: "external/pathItem.yml" definitions: namedAgain: type: object properties: id: type: integer format: int64 datedTag: allOf: - type: string format: date - type: object properties: id: type: integer format: int64 value: type: string records: type: array items: - type: object properties: createdAt: type: string format: date-time datedRecords: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time datedTaggedRecords: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time additionalItems: type: object properties: id: type: integer format: int64 value: type: string otherRecords: type: array items: type: object properties: createdAt: type: string format: date-time tags: type: object additionalProperties: type: object properties: id: type: integer format: int64 value: type: string namedThing: type: object properties: name: type: object properties: id: type: integer format: int64 nestedThing: type: object properties: record: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time - allOf: - type: string format: date - type: object additionalProperties: type: object properties: id: type: integer format: int64 value: type: string properties: id: type: integer format: int64 value: type: string name: type: object properties: id: type: integer format: int64 createdAt: type: string format: date-time golang-github-go-openapi-analysis-0.15.0/fixtures/nested_inline_schemas.yml000066400000000000000000000160531332100444700271230ustar00rootroot00000000000000--- swagger: "2.0" info: version: "0.1.0" title: reference analysis parameters: someParam: name: someParam in: body schema: type: object properties: createdAt: type: string format: date-time responses: someResponse: schema: type: object properties: createdAt: type: string format: date-time paths: "/some/where/{id}": parameters: - name: id in: path type: integer format: int32 - name: bodyId in: body schema: type: array items: type: object properties: createdAt: type: string format: date-time post: responses: default: description: all good get: parameters: - name: limit in: query type: integer format: int32 required: false - name: other in: query type: array items: type: object properties: id: type: integer format: int64 - name: body in: body schema: type: object properties: record: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time - allOf: - type: string format: date - type: object properties: id: type: integer format: int64 value: type: string name: type: object properties: id: type: integer format: int64 createdAt: type: string format: date-time responses: default: description: ok schema: type: object properties: record: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time - allOf: - type: string format: date - type: object properties: id: type: integer format: int64 value: type: string name: type: object properties: id: type: integer format: int64 createdAt: type: string format: date-time 404: description: ok schema: $ref: "errors.yml#/error" 200: description: ok schema: type: object properties: record: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time - allOf: - type: string format: date - type: object properties: id: type: integer format: int64 value: type: string name: type: object properties: id: type: integer format: int64 createdAt: type: string format: date-time "/other/place": $ref: "external/pathItem.yml" definitions: namedAgain: type: object properties: id: type: integer format: int64 datedTag: allOf: - type: string format: date - type: object properties: id: type: integer format: int64 value: type: string records: type: array items: - type: object properties: createdAt: type: string format: date-time datedRecords: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time datedTaggedRecords: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time additionalItems: type: object properties: id: type: integer format: int64 value: type: string otherRecords: type: array items: type: object properties: createdAt: type: string format: date-time tags: type: object additionalProperties: type: object properties: id: type: integer format: int64 value: type: string namedThing: type: object properties: name: type: object properties: id: type: integer format: int64 nestedThing: type: object properties: record: type: array items: - type: string format: date-time - type: object properties: createdAt: type: string format: date-time - allOf: - type: string format: date - type: object additionalProperties: type: object properties: id: type: integer format: int64 value: type: string properties: id: type: integer format: int64 value: type: string name: type: object properties: id: type: integer format: int64 createdAt: type: string format: date-time golang-github-go-openapi-analysis-0.15.0/fixtures/no-paths.yml000066400000000000000000000011131332100444700243200ustar00rootroot00000000000000--- swagger: '2.0' info: title: no paths API version: 4.1.7 schemes: - http basePath: /wooble consumes: - application/json produces: - application/json paths: definitions: common: type: object required: - id properties: id: type: string format: string minLength: 1 parameters: common: name: common in: query type: string responses: 401: description: bar unauthorized schema: $ref: "#/definitions/error" 404: description: bar resource not found schema: $ref: "#/definitions/error" golang-github-go-openapi-analysis-0.15.0/fixtures/oaigen/000077500000000000000000000000001332100444700233125ustar00rootroot00000000000000golang-github-go-openapi-analysis-0.15.0/fixtures/oaigen/fixture-oaigen.yaml000066400000000000000000000102121332100444700271200ustar00rootroot00000000000000--- swagger: '2.0' info: version: '0.1.0' title: reference analysis parameters: someParam: name: some in: query type: string bodyParam: name: some in: body schema: # expect this $ref to be kept $ref: '#/definitions/myBody' responses: notFound: description: 'Not Found' schema: $ref: '#/definitions/notFound' paths: /some/where: parameters: - $ref: '#/parameters/someParam' get: parameters: - $ref: '#/parameters/bodyParam' - name: other in: query type: string responses: default: $ref: '#/responses/notFound' 404: description: ok schema: $ref: '#/definitions/myResponse' 304: description: ok schema: $ref: 'transitive-1.yaml#/definitions/transitive-1.1' 204: description: ok schema: $ref: '#/definitions/uniqueName1' 200: description: 'RecordHolder' schema: type: object properties: prop0: $ref: '#/definitions/myBody' 206: description: ok schema: $ref: 'transitive-1.yaml#/definitions/a' 205: description: ok schema: $ref: 'transitive-1.yaml#/definitions/b' # arbitrary json pointers post: responses: 200: description: ok schema: # this one gets resolved $ref: 'transitive-2.yaml#/definitions/a/properties/b' 204: description: ok schema: # this one gets resolved $ref: 'transitive-1.yaml#/definitions/c/properties/d' default: description: default schema: # this one remains (same file) $ref: '#/definitions/myDefaultResponse/properties/zzz' /some/where/else: get: responses: default: description: default schema: $ref: '#/definitions/notFound' /yet/again/some/where: get: responses: default: description: default schema: $ref: 'transitive-1.yaml#/somewhere' /with/slice/container: get: responses: default: description: default schema: allOf: - $ref: '#/definitions/uniqueName3' - $ref: 'transitive-1.yaml#/definitions/uniqueName3' /with/tuple/container: get: responses: default: description: default schema: type: array items: - $ref: '#/definitions/uniqueName3' - $ref: 'transitive-1.yaml#/definitions/uniqueName3' /with/tuple/conflict: get: responses: default: description: default schema: type: array items: - $ref: 'transitive-1.yaml#/definitions/uniqueName4' - $ref: 'transitive-2.yaml#/definitions/uniqueName4' /with/boolable/container: get: responses: default: description: default schema: type: object additionalProperties: $ref: 'transitive-1.yaml#/definitions/uniqueName5' definitions: myDefaultResponse: type: object properties: zzz: type: integer myBody: type: object properties: prop1: type: integer aA: $ref: '#/definitions/aA' aA: type: string format: date bB: type: string format: date-time myResponse: type: object properties: prop2: type: integer notFound: type: array items: type: integer uniqueName1: # expect this to be expanded after OAIGen stripping $ref: 'transitive-1.yaml#/definitions/uniqueName1' notUniqueName2: # this one prevents OAIGen stripping $ref: 'transitive-1.yaml#/definitions/uniqueName2' uniqueName2: $ref: 'transitive-1.yaml#/definitions/uniqueName2' uniqueName3: type: object properties: prop7: type: integer uniqueName5: type: object properties: prop10: type: integer golang-github-go-openapi-analysis-0.15.0/fixtures/oaigen/test3-bis-swagger.yaml000066400000000000000000000016001332100444700274450ustar00rootroot00000000000000swagger: '2.0' info: version: 0.1.1 title: test 1 description: recursively following JSON references contact: name: Fred schemes: - http consumes: - application/json produces: - application/json paths: /getAll: get: operationId: getAll parameters: - name: a in: body description: max number of results schema: $ref: '#/definitions/a' responses: '200': description: Success schema: $ref: '#/definitions/b' '201': description: Success schema: $ref: '#/definitions/b' '203': description: Success schema: $ref: '#/definitions/c' definitions: a: type: string b: type: array items: type: string c: type: object additionalProperties: type: integer golang-github-go-openapi-analysis-0.15.0/fixtures/oaigen/test3-model-schema.json000066400000000000000000000010761332100444700276070ustar00rootroot00000000000000{ "$schema": "http://json-schema.org/draft-04/schema#", "id": "./test3-model-schema.json", "title": "test3-model-schema", "description": "Test schema responses", "definitions": { "b": { "type": "object", "required": ["a1"], "additionalProperties": false, "properties": { "a1": { "type": "string" }, "a2": { "type": "integer" } } }, "c": { "type": "object", "additionalProperties": { "type": "integer" } } } } golang-github-go-openapi-analysis-0.15.0/fixtures/oaigen/test3-swagger.yaml000066400000000000000000000014601332100444700266760ustar00rootroot00000000000000swagger: '2.0' info: version: 0.1.1 title: test 1 description: recursively following JSON references contact: name: Fred schemes: - http consumes: - application/json produces: - application/json paths: /getAll: get: operationId: getAll parameters: - name: a in: body description: max number of results required: false schema: $ref: '#/definitions/a' responses: '200': description: Success schema: $ref: '#/definitions/b' '203': description: Success schema: $ref: '#/definitions/c' definitions: a: type: string b: $ref: 'test3-model-schema.json#/definitions/b' c: $ref: 'test3-model-schema.json#/definitions/c' golang-github-go-openapi-analysis-0.15.0/fixtures/oaigen/test3-ter-model-schema.json000066400000000000000000000010041332100444700303660ustar00rootroot00000000000000{ "$schema": "http://json-schema.org/draft-04/schema#", "title": "test3-model-schema", "description": "Test schema responses", "definitions": { "b1": { "type": "array", "items": { "type": "string" } }, "b2": { "type": "object", "properties": { "x2": { "type": "string" } } }, "b3": { "type": "object", "additionalProperties": { "type": "string" } } } } golang-github-go-openapi-analysis-0.15.0/fixtures/oaigen/test3-ter-swagger-flat.json000066400000000000000000000032541332100444700304240ustar00rootroot00000000000000{ "consumes": [ "application/json" ], "produces": [ "application/json" ], "schemes": [ "http" ], "swagger": "2.0", "info": { "description": "recursively following JSON references", "title": "test 1", "contact": { "name": "Fred" }, "version": "0.1.1" }, "paths": { "/getAll": { "get": { "operationId": "getAll", "parameters": [ { "description": "max number of results", "name": "a", "in": "body", "schema": { "$ref": "#/definitions/a" } } ], "responses": { "200": { "description": "Success", "schema": { "$ref": "#/definitions/b1" } }, "201": { "description": "Success", "schema": { "$ref": "#/definitions/b2" } }, "203": { "description": "Success", "schema": { "$ref": "#/definitions/b3" } } } } } }, "definitions": { "a": { "type": "string" }, "b1": { "$ref": "#/definitions/b1OAIGen" }, "b1OAIGen": { "type": "array", "items": { "type": "string" } }, "b2": { "$ref": "#/definitions/b2OAIGen" }, "b2OAIGen": { "type": "object", "properties": { "x2": { "type": "string" } } }, "b3": { "$ref": "#/definitions/b3OAIGen" }, "b3OAIGen": { "type": "object", "additionalProperties": { "type": "string" } } } } golang-github-go-openapi-analysis-0.15.0/fixtures/oaigen/test3-ter-swagger.yaml000066400000000000000000000017501332100444700274700ustar00rootroot00000000000000swagger: '2.0' info: version: 0.1.1 title: test 1 description: recursively following JSON references contact: name: Fred schemes: - http consumes: - application/json produces: - application/json paths: /getAll: get: operationId: getAll parameters: - name: a in: body description: max number of results required: false schema: $ref: '#/definitions/a' responses: '200': description: Success schema: $ref: '#/definitions/b1' '201': description: Success schema: $ref: '#/definitions/b2' '203': description: Success schema: $ref: '#/definitions/b3' definitions: a: type: string b1: $ref: './test3-ter-model-schema.json#/definitions/b1' b2: $ref: './test3-ter-model-schema.json#/definitions/b2' b3: $ref: './test3-ter-model-schema.json#/definitions/b3' golang-github-go-openapi-analysis-0.15.0/fixtures/oaigen/transitive-1.yaml000066400000000000000000000014501332100444700265240ustar00rootroot00000000000000somewhere: type: array items: type: integer definitions: transitive-1.1: $ref: "#/definitions/transitive-1.2" transitive-1.2: $ref: "transitive-2.yaml#/definitions/transitive-2.1" uniqueName1: $ref: "transitive-2.yaml#/definitions/uniqueName1" uniqueName2: $ref: "transitive-2.yaml#/definitions/uniqueName2" uniqueName3: type: object properties: prop8: type: string uniqueName4: type: object properties: prop9: type: string uniqueName5: type: number a: type: object properties: a: $ref: "transitive-2.yaml#/definitions/a" b: type: array items: $ref: "transitive-2.yaml#/definitions/b" c: type: object properties: d: $ref: "transitive-2.yaml#/definitions/b" golang-github-go-openapi-analysis-0.15.0/fixtures/oaigen/transitive-2.yaml000066400000000000000000000007461332100444700265340ustar00rootroot00000000000000definitions: transitive-2.1: type: object properties: prop4: type: integer uniqueName1: type: object properties: prop5: type: integer uniqueName2: type: object properties: prop6: type: integer uniqueName4: type: object properties: prop10: type: string a: type: object properties: b: type: integer b: type: object properties: c: type: integer golang-github-go-openapi-analysis-0.15.0/fixtures/operations/000077500000000000000000000000001332100444700242335ustar00rootroot00000000000000golang-github-go-openapi-analysis-0.15.0/fixtures/operations/fixture-operations.yaml000066400000000000000000000040541332100444700307710ustar00rootroot00000000000000--- swagger: '2.0' info: version: '0.1.0' title: operations and operationIDs parameters: someParam: name: some in: query type: string bodyParam: name: some in: body schema: $ref: '#/definitions/myBody' responses: notFound: description: 'Not Found' schema: type: string default: "Element no found" defaultResponse: description: 'Default response' schema: type: string maxLength: 255 definitions: myBody: type: integer unused: type: integer paths: /some/where: parameters: - $ref: '#/parameters/someParam' get: operationId: getSomeWhere parameters: - $ref: '#/parameters/bodyParam' - name: other in: query type: string responses: 403: $ref: '#/responses/notFound' post: operationId: postSomeWhere responses: default: $ref: '#/responses/defaultResponse' /some/where/else: parameters: - $ref: '#/parameters/someParam' get: operationId: getSomeWhereElse parameters: - $ref: '#/parameters/someParam' - name: myOtherBodyParam in: body schema: $ref: '#/definitions/myBody' responses: default: $ref: '#/responses/defaultResponse' put: operationId: putSomeWhereElse responses: default: $ref: '#/responses/defaultResponse' post: operationId: postSomeWhereElse responses: default: $ref: '#/responses/defaultResponse' patch: operationId: patchSomeWhereElse responses: default: $ref: '#/responses/defaultResponse' delete: operationId: deleteSomeWhereElse responses: default: $ref: '#/responses/defaultResponse' head: operationId: headSomeWhereElse responses: default: $ref: '#/responses/defaultResponse' options: operationId: optionsSomeWhereElse responses: default: $ref: '#/responses/defaultResponse' golang-github-go-openapi-analysis-0.15.0/fixtures/other-mixin.yml000066400000000000000000000007241332100444700250410ustar00rootroot00000000000000--- swagger: '2.0' info: title: extension of tags, schemes and consumes/produces version: 4.2.0 schemes: - http - https basePath: /api consumes: - application/json - application/octet-stream produces: - application/json - application/xml tags: - name: ticket description: operations to get and store tickets - name: game description: operations to follow games - name: conflict description: a tag in conflict with primary spec paths: golang-github-go-openapi-analysis-0.15.0/fixtures/parameters/000077500000000000000000000000001332100444700242135ustar00rootroot00000000000000golang-github-go-openapi-analysis-0.15.0/fixtures/parameters/fixture-parameters.yaml000066400000000000000000000062771332100444700307420ustar00rootroot00000000000000--- swagger: '2.0' info: version: '0.1.0' title: parameters and responses expansion parameters: someParam: name: some in: query type: string pattern: '^[a-z]$' bodyParam: name: some in: body schema: $ref: '#/definitions/myBody' someHeader: name: headerRefed in: header type: string anotherHeader: name: headerSlice in: header type: array items: type: string arrayQuery: name: arrayInQuery in: query type: array items: type: integer responses: notFound: description: 'Not Found' schema: type: string default: "Element no found" defaultResponse: description: 'Default response' headers: x-yet-another-header: type: array items: type: string schema: type: string maxLength: 255 definitions: myBody: type: integer unused: type: integer schemaWithAllOf: type: object allOf: - type: object properties: prop1: type: integer - type: object properties: prop2: type: string pattern: '^[a-z]$' paths: /some/where: parameters: - $ref: '#/parameters/someParam' - $ref: '#/parameters/arrayQuery' - name: anotherArray in: query type: array items: type: integer get: parameters: - $ref: '#/parameters/bodyParam' - name: other in: query type: string - $ref: '#/parameters/anotherHeader' - name: andAnother in: query type: array items: type: integer responses: 403: $ref: '#/responses/notFound' default: description: default headers: x-pattern-default: type: string pattern: '^[a-z]$' x-array-pattern-default: type: array items: type: string pattern: '^[a-z]$' post: operationId: postSomeWhere parameters: - name: headerParam in: header type: string - $ref: '#/parameters/someHeader' - name: nestedParam in: query type: array items: type: array items: type: string pattern: '^[a-z]$' responses: 200: description: ok headers: x-post-header: type: string pattern: '^[a-z]$' x-response-array-header: type: array items: type: string schema: $ref: '#/definitions/schemaWithAllOf' 201: description: ok schema: type: array items: $ref: '#/definitions/schemaWithAllOf' default: $ref: '#/responses/defaultResponse' /some/where/else: get: parameters: - $ref: '#/parameters/someParam' - name: myOtherBodyParam in: body schema: $ref: '#/definitions/myBody' responses: default: $ref: '#/responses/defaultResponse' /some/remote: $ref: 'other-source.yaml#/pathItems/patchMethod' golang-github-go-openapi-analysis-0.15.0/fixtures/parameters/other-source.yaml000066400000000000000000000005361332100444700275220ustar00rootroot00000000000000pathItems: patchMethod: parameters: - name: somePatchParam in: query type: string patch: operationId: patchOp parameters: - name: myPatchParam in: query required: false type: integer responses: 200: description: ok schema: type: string golang-github-go-openapi-analysis-0.15.0/fixtures/patterns.yml000066400000000000000000000047471332100444700244470ustar00rootroot00000000000000--- swagger: "2.0" info: version: "0.1.0" title: reference analysis parameters: idParam: name: id in: path type: string pattern: 'a[A-Za-Z0-9]+' responses: notFound: headers: ContentLength: type: string pattern: '[0-9]+' schema: $ref: "#/definitions/error" paths: "/some/where/{id}": parameters: - $ref: "#/parameters/idParam" - name: name in: query pattern: 'b[A-Za-z0-9]+' - name: bodyId in: body schema: type: object get: parameters: - name: filter in: query type: string pattern: "[abc][0-9]+" - name: other in: query type: array items: type: string pattern: 'c[A-Za-z0-9]+' - name: body in: body schema: type: object responses: default: schema: type: object 404: $ref: "#/responses/notFound" 200: headers: X-Request-Id: type: string pattern: 'd[A-Za-z0-9]+' schema: $ref: "#/definitions/tag" "/other/place": post: parameters: - name: body in: body schema: type: object properties: value: type: string pattern: 'e[A-Za-z0-9]+' responses: default: headers: Via: type: array items: type: string pattern: '[A-Za-z]+' 200: schema: type: object properties: data: type: string pattern: "[0-9]+[abd]" definitions: named: type: string pattern: 'f[A-Za-z0-9]+' tag: type: object properties: id: type: integer format: int64 value: type: string pattern: 'g[A-Za-z0-9]+' audit: $ref: "#/definitions/record" record: type: object properties: createdAt: type: string format: date-time error: type: object required: - id - message properties: id: type: integer format: int64 readOnly: true message: type: string readOnly: true withPatternProperties: type: object additionalProperties: true patternProperties: '^prop[0-9]+$': type: string golang-github-go-openapi-analysis-0.15.0/fixtures/pointers/000077500000000000000000000000001332100444700237135ustar00rootroot00000000000000golang-github-go-openapi-analysis-0.15.0/fixtures/pointers/fixture-pointers-loop.yaml000066400000000000000000000010531332100444700310740ustar00rootroot00000000000000--- swagger: '2.0' info: version: '0.1.0' title: JSON pointers paths: /some/where: get: responses: default: schema: $ref: '#/definitions/whiteStone/properties/p1' definitions: whiteStone: type: object properties: p1: $ref: '#/definitions/blackStone/properties/p2' blackStone: type: object properties: p2: $ref: '#/definitions/redStone/properties/p3' redStone: type: object properties: p3: $ref: '#/definitions/whiteStone/properties/p1' golang-github-go-openapi-analysis-0.15.0/fixtures/pointers/fixture-pointers.yaml000066400000000000000000000110221332100444700301220ustar00rootroot00000000000000--- swagger: '2.0' info: version: '0.1.0' title: JSON pointers parameters: someParam: name: some in: query type: string bodyParam: name: some in: body schema: $ref: '#/responses/notFound/schema' remoteParam: name: some in: body schema: $ref: 'remote.yaml#/remotes/u64' responses: notFound: description: 'Not Found' schema: $ref: '#/definitions/notFound' funnyResponse: description: ok schema: $ref: '#/parameters/bodyParam/schema' remoteResponse: description: ok schema: type: object properties: prop0: $ref: 'remote.yaml#/remotes/u64' paths: /some/where: parameters: - $ref: '#/parameters/someParam' get: parameters: - $ref: '#/parameters/bodyParam' - name: other in: query type: string responses: default: $ref: '#/responses/notFound' 404: description: ok schema: $ref: '#/definitions/myResponse' 200: description: 'RecordHolder' schema: type: object properties: prop0: $ref: '#/definitions/myBody' post: responses: default: description: default schema: $ref: '#/definitions/myDefaultResponse/properties/zzz' 203: description: funny schema: $ref: '#/responses/funnyResponse/schema' 204: $ref: '#/responses/funnyResponse' /some/where/else: get: responses: default: description: default schema: $ref: '#/definitions/notFound' 200: description: ok schema: $ref: '#/definitions/myDefaultResponse' /with/slice/container: get: responses: default: description: default schema: allOf: - $ref: 'remote.yaml#/remotes/u64' - $ref: 'remote.yaml#/remotes/u32' - $ref: '#/definitions/myBody/properties/prop3' /with/tuple/container: get: responses: default: description: default schema: type: array items: - $ref: '#/definitions/myBody/properties/prop2' - $ref: '#/definitions/myBody/properties/prop3' - $ref: 'remote.yaml#/remotes/i32' /with/array/container: get: responses: 200: description: ok schema: $ref: '#/definitions/anArray' default: description: default schema: type: array items: $ref: '#/definitions/anArray/items' /with/boolable/container: get: responses: 200: description: ok schema: type: array items: - type: integer - type: string additionalItems: $ref: '#/definitions/myBody/properties/prop3' default: description: default schema: type: object additionalProperties: $ref: '#/definitions/myBody/properties/prop3' /with/ZcomesFirstInOrder/container: get: responses: 200: description: ok schema: type: array items: - type: integer - type: string additionalItems: $ref: '#/definitions/myBody/properties/prop3' 203: description: ok schema: $ref: '#/definitions/anExtensible/additionalProperties' 204: description: ok schema: type: object additionalProperties: type: object additionalProperties: $ref: '#/definitions/anExtensible/additionalProperties' definitions: anExtensible: type: object additionalProperties: type: object properties: addProp1: type: string anArray: type: array items: type: string format: uuid notFound: type: object properties: prop1: $ref: '#/definitions/myDefaultResponse/properties/zzz' myDefaultResponse: type: object properties: zzz: type: integer myResponse: type: object additionalProperties: $ref: '#/definitions/notFound/properties/prop1' myBody: type: object properties: prop2: type: integer prop3: $ref: '#/definitions/myDefaultResponse/properties/zzz' golang-github-go-openapi-analysis-0.15.0/fixtures/pointers/remote.yaml000066400000000000000000000002671332100444700260770ustar00rootroot00000000000000remotes: u32: type: integer format: uint32 i32: type: integer format: int32 u64: type: integer format: uint64 i64: type: integer format: int64 golang-github-go-openapi-analysis-0.15.0/fixtures/references.yml000066400000000000000000000041261332100444700247170ustar00rootroot00000000000000--- swagger: "2.0" info: version: "0.1.0" title: reference analysis parameters: idParam: name: id in: path type: integer format: int32 limitParam: name: limit in: query type: integer format: int32 required: false responses: notFound: schema: $ref: "#/definitions/error" paths: "/some/where/{id}": parameters: - $ref: "#/parameters/idParam" - name: bodyId in: body schema: type: object get: parameters: - $ref: "#/parameters/limitParam" - name: other in: query type: array items: # NOTE: $ref here is forbidden in swagger 2.0 # however, it is possible to analyze this $ref: "#/definitions/named" - name: otherHeader in: header type: array items: # NOTE: $ref here is forbidden in swagger 2.0 # however, it is possible to analyze this $ref: "#/definitions/named" - name: body in: body schema: type: object responses: default: headers: x-array-header: type: array items: # NOTE: $ref here is forbidden in swagger 2.0 # however, it is possible to analyze this $ref: '#/definitions/named' schema: type: object 404: $ref: "#/responses/notFound" 200: schema: $ref: "#/definitions/tag" "/other/place": $ref: "#/x-shared-path/getItems" definitions: named: type: string tag: type: object properties: id: type: integer format: int64 value: type: string audit: $ref: "#/definitions/record" record: type: object properties: createdAt: type: string format: date-time error: type: object required: - id - message properties: id: type: integer format: int64 readOnly: true message: type: string readOnly: true golang-github-go-openapi-analysis-0.15.0/fixtures/securitydef.yml000066400000000000000000000012301332100444700251150ustar00rootroot00000000000000--- swagger: '2.0' info: title: extension of securityDefinitions version: 4.2.0 schemes: - http basePath: /api consumes: - application/json produces: - application/json securityDefinitions: myOtherRoles: description: definition of scopes as roles type: oauth2 flow: accessCode authorizationUrl: https://foo.bar.com/authorize tokenUrl: https://foo.bar.com/token scopes: otherSellers: group of sellers otherBuyers: group of buyers mySecondaryApiKey: type: apiKey name: X-secondaryApiKey in: header myBasicAuth: type: basic description: basic primary auth security: - myBasicAuth: [] paths: golang-github-go-openapi-analysis-0.15.0/fixtures/widget-crud.yml000066400000000000000000000103221332100444700250070ustar00rootroot00000000000000--- swagger: '2.0' info: title: widget CRUD API version: 4.2.0 schemes: - http basePath: /api consumes: - application/json produces: - application/json securityDefinitions: myRoles: description: definition of scopes as roles type: oauth2 flow: accessCode authorizationUrl: https://foo.bar.com/authorize tokenUrl: https://foo.bar.com/token scopes: sellers: group of sellers buyers: group of buyers myPrimaryAPIKey: type: apiKey name: X-primaryApiKey in: header myBasicAuth: type: basic description: basic primary auth security: - myPrimaryAPIKey: [] - myBasicAuth: [] myRoles: [ sellers ] - myBasicAuth: [] tags: - name: conflict description: a tag in conflict with primary spec paths: /common: get: operationId: commonGet summary: here to test path collisons responses: '200': description: OK schema: $ref: "#/definitions/widget" /widgets: post: operationId: create summary: Create a new widget parameters: - name: info in: body schema: $ref: "#/definitions/widget" responses: '201': description: created schema: $ref: "#/definitions/widgetId" default: description: error schema: $ref: "#/definitions/error" /widgets/{widgetid}: get: operationId: get summary: Get a widget by id parameters: - $ref: "#/parameters/widgetid" responses: '200': description: OK schema: $ref: "#/definitions/widget" '401': $ref: "#/responses/401" '404': $ref: "#/responses/404" default: description: error schema: $ref: "#/definitions/error" delete: operationId: delete summary: delete a widget by id parameters: - name: widgetid in: path required: true type: string responses: '200': description: OK '401': description: unauthorized schema: $ref: "#/definitions/error" '404': description: resource not found schema: $ref: "#/definitions/error" default: description: error schema: $ref: "#/definitions/error" post: operationId: update summary: update a widget by id parameters: - name: widgetid in: path required: true type: string - name: info in: body schema: $ref: "#/definitions/widget" responses: '200': description: OK '401': description: unauthorized schema: $ref: "#/definitions/error" '404': description: resource not found schema: $ref: "#/definitions/error" default: description: error schema: $ref: "#/definitions/error" definitions: common: type: object required: - id properties: id: type: string format: string minLength: 1 widget: type: object required: - name - description properties: id: type: string format: string readOnly: true name: type: string format: string minLength: 1 description: type: string format: string minLength: 1 widgetId: type: object required: - id properties: id: type: string format: string minLength: 1 error: type: object required: - message properties: code: type: string format: string message: type: string fields: type: string parameters: common: name: common in: query type: string widgetid: name: widgetid in: path required: true type: string responses: 401: description: widget unauthorized schema: $ref: "#/definitions/error" 404: description: widget resource not found schema: $ref: "#/definitions/error" golang-github-go-openapi-analysis-0.15.0/flatten.go000066400000000000000000001226251332100444700221730ustar00rootroot00000000000000// Copyright 2015 go-swagger maintainers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package analysis import ( "fmt" "log" "net/http" "os" slashpath "path" "path/filepath" "sort" "strings" "strconv" "github.com/go-openapi/jsonpointer" swspec "github.com/go-openapi/spec" "github.com/go-openapi/swag" ) // FlattenOpts configuration for flattening a swagger specification. type FlattenOpts struct { Spec *Spec // The analyzed spec to work with flattenContext *context // Internal context to track flattening activity BasePath string // Flattening options Expand bool // If Expand is true, we skip flattening the spec and expand it instead Minimal bool Verbose bool RemoveUnused bool /* Extra keys */ _ struct{} // require keys } // ExpandOpts creates a spec.ExpandOptions to configure expanding a specification document. func (f *FlattenOpts) ExpandOpts(skipSchemas bool) *swspec.ExpandOptions { return &swspec.ExpandOptions{RelativeBase: f.BasePath, SkipSchemas: skipSchemas} } // Swagger gets the swagger specification for this flatten operation func (f *FlattenOpts) Swagger() *swspec.Swagger { return f.Spec.spec } // newRef stores information about refs created during the flattening process type newRef struct { key string newName string path string isOAIGen bool resolved bool schema *swspec.Schema parents []string } // context stores intermediary results from flatten type context struct { newRefs map[string]*newRef } func newContext() *context { return &context{ newRefs: make(map[string]*newRef), } } // Flatten an analyzed spec and produce a self-contained spec bundle. // // There is a minimal and a full flattening mode. // // Minimally flattening a spec means: // - Expanding parameters, responses, path items, parameter items and header items (references to schemas are left // unscathed) // - Importing external (http, file) references so they become internal to the document // - Moving every JSON pointer to a $ref to a named definition (i.e. the reworked spec does not contain pointers // like "$ref": "#/definitions/myObject/allOfs/1") // // A minimally flattened spec thus guarantees the following properties: // - all $refs point to a local definition (i.e. '#/definitions/...') // - definitions are unique // // NOTE: arbitrary JSON pointers (other than $refs to top level definitions) are rewritten as definitions if they // represent a complex schema or express commonality in the spec. // Otherwise, they are simply expanded. // // Minimal flattening is necessary and sufficient for codegen rendering using go-swagger. // // Fully flattening a spec means: // - Moving every complex inline schema to be a definition with an auto-generated name in a depth-first fashion. // // By complex, we mean every JSON object with some properties. // Arrays, when they do not define a tuple, // or empty objects with or without additionalProperties, are not considered complex and remain inline. // // NOTE: rewritten schemas get a vendor extension x-go-gen-location so we know from which part of the spec definitions // have been created. // // Available flattening options: // - Minimal: stops flattening after minimal $ref processing, leaving schema constructs untouched // - Expand: expand all $ref's in the document (inoperant if Minimal set to true) // - Verbose: croaks about name conflicts detected // - RemoveUnused: removes unused parameters, responses and definitions after expansion/flattening // // NOTE: expansion removes all $ref save circular $ref, which remain in place // // TODO: additional options // - ProgagateNameExtensions: ensure that created entries properly follow naming rules when their parent have set a // x-go-name extension // - LiftAllOfs: // - limit the flattening of allOf members when simple objects // - merge allOf with validation only // - merge allOf with extensions only // - ... // func Flatten(opts FlattenOpts) error { // Make sure opts.BasePath is an absolute path if !filepath.IsAbs(opts.BasePath) { cwd, _ := os.Getwd() opts.BasePath = filepath.Join(cwd, opts.BasePath) } opts.flattenContext = newContext() // recursively expand responses, parameters, path items and items in simple schemas // TODO: we should not expand discriminated types if err := swspec.ExpandSpec(opts.Swagger(), opts.ExpandOpts(!opts.Expand)); err != nil { return err } // strip current file from $ref's, so we can recognize them as proper definitions // In particular, this works around for issue go-openapi/spec#76: leading absolute file in $ref is stripped if err := normalizeRef(&opts); err != nil { return err } if opts.RemoveUnused { // optionally removes shared parameters and responses already expanded (now unused) // default parameters (i.e. under paths) remain. opts.Swagger().Parameters = nil opts.Swagger().Responses = nil } opts.Spec.reload() // re-analyze // at this point there are no other references left but schemas if err := importExternalReferences(&opts); err != nil { return err } opts.Spec.reload() // re-analyze if !opts.Minimal && !opts.Expand { // full flattening: rewrite inline schemas (schemas that aren't simple types or arrays or maps) if err := nameInlinedSchemas(&opts); err != nil { return err } opts.Spec.reload() // re-analyze } // rewrite JSON pointers other than $ref to named definitions // and attempts to resolve conflicting names if err := stripPointersAndOAIGen(&opts); err != nil { return err } if opts.RemoveUnused { // remove unused definitions expected := make(map[string]struct{}) for k := range opts.Swagger().Definitions { expected[slashpath.Join(definitionsPath, jsonpointer.Escape(k))] = struct{}{} } for _, k := range opts.Spec.AllDefinitionReferences() { if _, ok := expected[k]; ok { delete(expected, k) } } for k := range expected { debugLog("removing unused definition %s", slashpath.Base(k)) if opts.Verbose { log.Printf("info: removing unused definition: %s", slashpath.Base(k)) } delete(opts.Swagger().Definitions, slashpath.Base(k)) } opts.Spec.reload() // re-analyze } // TODO: simplify known schema patterns to flat objects with properties // examples: // - lift simple allOf object, // - empty allOf with validation only or extensions only // - rework allOf arrays // - rework allOf additionalProperties if opts.Verbose { // issue notifications croak(&opts) } return nil } // isAnalyzedAsComplex determines if an analyzed schema is eligible to flattening (i.e. it is "complex"). // // Complex means the schema is any of: // - a simple type (primitive) // - an array of something (items are possibly complex ; if this is the case, items will generate a definition) // - a map of something (additionalProperties are possibly complex ; if this is the case, additionalProperties will // generate a definition) func isAnalyzedAsComplex(asch *AnalyzedSchema) bool { if !asch.IsSimpleSchema && !asch.IsArray && !asch.IsMap { return true } return false } // nameInlinedSchemas replaces every complex inline construct by a named definition. func nameInlinedSchemas(opts *FlattenOpts) error { debugLog("nameInlinedSchemas") namer := &inlineSchemaNamer{ Spec: opts.Swagger(), Operations: opRefsByRef(gatherOperations(opts.Spec, nil)), flattenContext: opts.flattenContext, opts: opts, } depthFirst := sortDepthFirst(opts.Spec.allSchemas) for _, key := range depthFirst { sch := opts.Spec.allSchemas[key] if sch.Schema != nil && sch.Schema.Ref.String() == "" && !sch.TopLevel { // inline schema asch, err := Schema(SchemaOpts{Schema: sch.Schema, Root: opts.Swagger(), BasePath: opts.BasePath}) if err != nil { return fmt.Errorf("schema analysis [%s]: %v", key, err) } if isAnalyzedAsComplex(asch) { // move complex schemas to definitions if err := namer.Name(key, sch.Schema, asch); err != nil { return err } } } } return nil } var depthGroupOrder = []string{ "sharedParam", "sharedResponse", "sharedOpParam", "opParam", "codeResponse", "defaultResponse", "definition", } func sortDepthFirst(data map[string]SchemaRef) []string { // group by category (shared params, op param, statuscode response, default response, definitions) // sort groups internally by number of parts in the key and lexical names // flatten groups into a single list of keys sorted := make([]string, 0, len(data)) grouped := make(map[string]keys, len(data)) for k := range data { split := keyParts(k) var pk string if split.IsSharedOperationParam() { pk = "sharedOpParam" } if split.IsOperationParam() { pk = "opParam" } if split.IsStatusCodeResponse() { pk = "codeResponse" } if split.IsDefaultResponse() { pk = "defaultResponse" } if split.IsDefinition() { pk = "definition" } if split.IsSharedParam() { pk = "sharedParam" } if split.IsSharedResponse() { pk = "sharedResponse" } grouped[pk] = append(grouped[pk], key{Segments: len(split), Key: k}) } for _, pk := range depthGroupOrder { res := grouped[pk] sort.Sort(res) for _, v := range res { sorted = append(sorted, v.Key) } } return sorted } type key struct { Segments int Key string } type keys []key func (k keys) Len() int { return len(k) } func (k keys) Swap(i, j int) { k[i], k[j] = k[j], k[i] } func (k keys) Less(i, j int) bool { return k[i].Segments > k[j].Segments || (k[i].Segments == k[j].Segments && k[i].Key < k[j].Key) } type inlineSchemaNamer struct { Spec *swspec.Swagger Operations map[string]opRef flattenContext *context opts *FlattenOpts } func opRefsByRef(oprefs map[string]opRef) map[string]opRef { result := make(map[string]opRef, len(oprefs)) for _, v := range oprefs { result[v.Ref.String()] = v } return result } func (isn *inlineSchemaNamer) Name(key string, schema *swspec.Schema, aschema *AnalyzedSchema) error { debugLog("naming inlined schema at %s", key) parts := keyParts(key) for _, name := range namesFromKey(parts, aschema, isn.Operations) { if name != "" { // create unique name newName, isOAIGen := uniqifyName(isn.Spec.Definitions, swag.ToJSONName(name)) // clone schema sch, err := cloneSchema(schema) if err != nil { return err } // replace values on schema if err := rewriteSchemaToRef(isn.Spec, key, swspec.MustCreateRef(slashpath.Join(definitionsPath, newName))); err != nil { return fmt.Errorf("name inlined schema: %v", err) } // rewrite any dependent $ref pointing to this place, // when not already pointing to a top-level definition. // NOTE: this is important if such referers use arbitrary JSON pointers. an := New(isn.Spec) for k, v := range an.references.allRefs { r, _, erd := deepestRef(isn.opts, v) if erd != nil { return fmt.Errorf("at %s, %v", k, erd) } if r.String() == key || r.String() == slashpath.Join(definitionsPath, newName) && slashpath.Dir(v.String()) != definitionsPath { debugLog("found a $ref to a rewritten schema: %s points to %s", k, v.String()) // rewrite $ref to the new target if err := updateRef(isn.Spec, k, swspec.MustCreateRef(slashpath.Join(definitionsPath, newName))); err != nil { return err } } } // NOTE: this extension is currently not used by go-swagger (provided for information only) sch.AddExtension("x-go-gen-location", genLocation(parts)) // save cloned schema to definitions saveSchema(isn.Spec, newName, sch) // keep track of created refs if isn.flattenContext != nil { debugLog("track created ref: key=%s, newName=%s, isOAIGen=%t", key, newName, isOAIGen) resolved := false if _, ok := isn.flattenContext.newRefs[key]; ok { resolved = isn.flattenContext.newRefs[key].resolved } isn.flattenContext.newRefs[key] = &newRef{ key: key, newName: newName, path: slashpath.Join(definitionsPath, newName), isOAIGen: isOAIGen, resolved: resolved, schema: sch, } } } } return nil } // genLocation indicates from which section of the specification (models or operations) a definition has been created. // This is reflected in the output spec with a "x-go-gen-location" extension. At the moment, this is is provided // for information only. func genLocation(parts splitKey) string { if parts.IsOperation() { return "operations" } if parts.IsDefinition() { return "models" } return "" } func uniqifyName(definitions swspec.Definitions, name string) (string, bool) { isOAIGen := false if name == "" { name = "oaiGen" isOAIGen = true } if len(definitions) == 0 { return name, isOAIGen } unq := true for k := range definitions { if strings.ToLower(k) == strings.ToLower(name) { unq = false break } } if unq { return name, isOAIGen } name += "OAIGen" isOAIGen = true var idx int unique := name _, known := definitions[unique] for known { idx++ unique = fmt.Sprintf("%s%d", name, idx) _, known = definitions[unique] } return unique, isOAIGen } func namesFromKey(parts splitKey, aschema *AnalyzedSchema, operations map[string]opRef) []string { var baseNames [][]string var startIndex int if parts.IsOperation() { // params if parts.IsOperationParam() || parts.IsSharedOperationParam() { piref := parts.PathItemRef() if piref.String() != "" && parts.IsOperationParam() { if op, ok := operations[piref.String()]; ok { startIndex = 5 baseNames = append(baseNames, []string{op.ID, "params", "body"}) } } else if parts.IsSharedOperationParam() { pref := parts.PathRef() for k, v := range operations { if strings.HasPrefix(k, pref.String()) { startIndex = 4 baseNames = append(baseNames, []string{v.ID, "params", "body"}) } } } } // responses if parts.IsOperationResponse() { piref := parts.PathItemRef() if piref.String() != "" { if op, ok := operations[piref.String()]; ok { startIndex = 6 baseNames = append(baseNames, []string{op.ID, parts.ResponseName(), "body"}) } } } } // definitions if parts.IsDefinition() { nm := parts.DefinitionName() if nm != "" { startIndex = 2 baseNames = append(baseNames, []string{parts.DefinitionName()}) } } var result []string for _, segments := range baseNames { nm := parts.BuildName(segments, startIndex, aschema) if nm != "" { result = append(result, nm) } } sort.Strings(result) return result } const ( paths = "paths" responses = "responses" parameters = "parameters" definitions = "definitions" definitionsPath = "#/definitions" ) var ignoredKeys map[string]struct{} func init() { ignoredKeys = map[string]struct{}{ "schema": {}, "properties": {}, "not": {}, "anyOf": {}, "oneOf": {}, } } type splitKey []string func (s splitKey) IsDefinition() bool { return len(s) > 1 && s[0] == definitions } func (s splitKey) DefinitionName() string { if !s.IsDefinition() { return "" } return s[1] } func (s splitKey) isKeyName(i int) bool { if i <= 0 { return false } count := 0 for idx := i - 1; idx > 0; idx-- { if s[idx] != "properties" { break } count++ } return count%2 != 0 } func (s splitKey) BuildName(segments []string, startIndex int, aschema *AnalyzedSchema) string { for i, part := range s[startIndex:] { if _, ignored := ignoredKeys[part]; !ignored || s.isKeyName(startIndex+i) { if part == "items" || part == "additionalItems" { if aschema.IsTuple || aschema.IsTupleWithExtra { segments = append(segments, "tuple") } else { segments = append(segments, "items") } if part == "additionalItems" { segments = append(segments, part) } continue } segments = append(segments, part) } } return strings.Join(segments, " ") } func (s splitKey) IsOperation() bool { return len(s) > 1 && s[0] == paths } func (s splitKey) IsSharedOperationParam() bool { return len(s) > 2 && s[0] == paths && s[2] == parameters } func (s splitKey) IsSharedParam() bool { return len(s) > 1 && s[0] == parameters } func (s splitKey) IsOperationParam() bool { return len(s) > 3 && s[0] == paths && s[3] == parameters } func (s splitKey) IsOperationResponse() bool { return len(s) > 3 && s[0] == paths && s[3] == responses } func (s splitKey) IsSharedResponse() bool { return len(s) > 1 && s[0] == responses } func (s splitKey) IsDefaultResponse() bool { return len(s) > 4 && s[0] == paths && s[3] == responses && s[4] == "default" } func (s splitKey) IsStatusCodeResponse() bool { isInt := func() bool { _, err := strconv.Atoi(s[4]) return err == nil } return len(s) > 4 && s[0] == paths && s[3] == responses && isInt() } func (s splitKey) ResponseName() string { if s.IsStatusCodeResponse() { code, _ := strconv.Atoi(s[4]) return http.StatusText(code) } if s.IsDefaultResponse() { return "Default" } return "" } var validMethods map[string]struct{} func init() { validMethods = map[string]struct{}{ "GET": {}, "HEAD": {}, "OPTIONS": {}, "PATCH": {}, "POST": {}, "PUT": {}, "DELETE": {}, } } func (s splitKey) PathItemRef() swspec.Ref { if len(s) < 3 { return swspec.Ref{} } pth, method := s[1], s[2] if _, validMethod := validMethods[strings.ToUpper(method)]; !validMethod && !strings.HasPrefix(method, "x-") { return swspec.Ref{} } return swspec.MustCreateRef("#" + slashpath.Join("/", paths, jsonpointer.Escape(pth), strings.ToUpper(method))) } func (s splitKey) PathRef() swspec.Ref { if !s.IsOperation() { return swspec.Ref{} } return swspec.MustCreateRef("#" + slashpath.Join("/", paths, jsonpointer.Escape(s[1]))) } func keyParts(key string) splitKey { var res []string for _, part := range strings.Split(key[1:], "/") { if part != "" { res = append(res, jsonpointer.Unescape(part)) } } return res } func rewriteSchemaToRef(spec *swspec.Swagger, key string, ref swspec.Ref) error { debugLog("rewriting schema to ref for %s with %s", key, ref.String()) _, value, err := getPointerFromKey(spec, key) if err != nil { return err } switch refable := value.(type) { case *swspec.Schema: return rewriteParentRef(spec, key, ref) case swspec.Schema: return rewriteParentRef(spec, key, ref) case *swspec.SchemaOrArray: if refable.Schema != nil { refable.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}} } case *swspec.SchemaOrBool: if refable.Schema != nil { refable.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}} } default: return fmt.Errorf("no schema with ref found at %s for %T", key, value) } return nil } func rewriteParentRef(spec *swspec.Swagger, key string, ref swspec.Ref) error { parent, entry, pvalue, err := getParentFromKey(spec, key) if err != nil { return err } debugLog("rewriting holder for %T", pvalue) switch container := pvalue.(type) { case swspec.Response: if err := rewriteParentRef(spec, "#"+parent, ref); err != nil { return err } case *swspec.Response: container.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}} case *swspec.Responses: statusCode, err := strconv.Atoi(entry) if err != nil { return fmt.Errorf("%s not a number: %v", key[1:], err) } resp := container.StatusCodeResponses[statusCode] resp.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}} container.StatusCodeResponses[statusCode] = resp case map[string]swspec.Response: resp := container[entry] resp.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}} container[entry] = resp case swspec.Parameter: if err := rewriteParentRef(spec, "#"+parent, ref); err != nil { return err } case map[string]swspec.Parameter: param := container[entry] param.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}} container[entry] = param case []swspec.Parameter: idx, err := strconv.Atoi(entry) if err != nil { return fmt.Errorf("%s not a number: %v", key[1:], err) } param := container[idx] param.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}} container[idx] = param case swspec.Definitions: container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}} case map[string]swspec.Schema: container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}} case []swspec.Schema: idx, err := strconv.Atoi(entry) if err != nil { return fmt.Errorf("%s not a number: %v", key[1:], err) } container[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}} case *swspec.SchemaOrArray: // NOTE: this is necessarily an array - otherwise, the parent would be *Schema idx, err := strconv.Atoi(entry) if err != nil { return fmt.Errorf("%s not a number: %v", key[1:], err) } container.Schemas[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}} // NOTE: can't have case *swspec.SchemaOrBool = parent in this case is *Schema default: return fmt.Errorf("unhandled parent schema rewrite %s (%T)", key, pvalue) } return nil } func cloneSchema(schema *swspec.Schema) (*swspec.Schema, error) { var sch swspec.Schema if err := swag.FromDynamicJSON(schema, &sch); err != nil { return nil, fmt.Errorf("name inlined schema: %v", err) } return &sch, nil } func importExternalReferences(opts *FlattenOpts) error { groupedRefs := reverseIndexForSchemaRefs(opts) sortedRefStr := make([]string, 0, len(groupedRefs)) // sort $ref resolution to ensure deterministic name conflict resolution for refStr := range groupedRefs { sortedRefStr = append(sortedRefStr, refStr) } sort.Strings(sortedRefStr) for _, refStr := range sortedRefStr { entry := groupedRefs[refStr] if !entry.Ref.HasFragmentOnly { debugLog("importing external schema for [%s] from %s", strings.Join(entry.Keys, ", "), refStr) // resolve to actual schema sch := new(swspec.Schema) sch.Ref = entry.Ref if err := swspec.ExpandSchemaWithBasePath(sch, nil, opts.ExpandOpts(false)); err != nil { return err } if sch == nil { return fmt.Errorf("no schema found at %s for [%s]", refStr, strings.Join(entry.Keys, ", ")) } debugLog("importing external schema for [%s] from %s", strings.Join(entry.Keys, ", "), refStr) // generate a unique name - isOAIGen means that a naming conflict was resolved by changing the name newName, isOAIGen := uniqifyName(opts.Swagger().Definitions, nameFromRef(entry.Ref)) debugLog("new name for [%s]: %s - with name conflict:%t", strings.Join(entry.Keys, ", "), newName, isOAIGen) // rewrite the external refs to local ones for _, key := range entry.Keys { if err := updateRef(opts.Swagger(), key, swspec.MustCreateRef(slashpath.Join(definitionsPath, newName))); err != nil { return err } // keep track of created refs if opts.flattenContext != nil { resolved := false if _, ok := opts.flattenContext.newRefs[key]; ok { resolved = opts.flattenContext.newRefs[key].resolved } opts.flattenContext.newRefs[key] = &newRef{ key: key, newName: newName, path: slashpath.Join(definitionsPath, newName), isOAIGen: isOAIGen, resolved: resolved, schema: sch, } } } // add the resolved schema to the definitions saveSchema(opts.Swagger(), newName, sch) } } return nil } type refRevIdx struct { Ref swspec.Ref Keys []string } func reverseIndexForSchemaRefs(opts *FlattenOpts) map[string]refRevIdx { collected := make(map[string]refRevIdx) for key, schRef := range opts.Spec.references.schemas { if entry, ok := collected[schRef.String()]; ok { entry.Keys = append(entry.Keys, key) collected[schRef.String()] = entry } else { collected[schRef.String()] = refRevIdx{ Ref: schRef, Keys: []string{key}, } } } return collected } func nameFromRef(ref swspec.Ref) string { u := ref.GetURL() if u.Fragment != "" { return swag.ToJSONName(slashpath.Base(u.Fragment)) } if u.Path != "" { bn := slashpath.Base(u.Path) if bn != "" && bn != "/" { ext := slashpath.Ext(bn) if ext != "" { return swag.ToJSONName(bn[:len(bn)-len(ext)]) } return swag.ToJSONName(bn) } } return swag.ToJSONName(strings.Replace(u.Host, ".", " ", -1)) } func saveSchema(spec *swspec.Swagger, name string, schema *swspec.Schema) { if schema == nil { return } if spec.Definitions == nil { spec.Definitions = make(map[string]swspec.Schema, 150) } spec.Definitions[name] = *schema } // getPointerFromKey retrieves the content of the JSON pointer "key" func getPointerFromKey(spec *swspec.Swagger, key string) (string, interface{}, error) { pth := key[1:] ptr, err := jsonpointer.New(pth) if err != nil { return "", nil, err } value, _, err := ptr.Get(spec) if err != nil { return "", nil, err } return pth, value, nil } // getParentFromKey retrieves the container of the JSON pointer "key" func getParentFromKey(spec *swspec.Swagger, key string) (string, string, interface{}, error) { pth := key[1:] parent, entry := slashpath.Dir(pth), slashpath.Base(pth) debugLog("getting schema holder at: %s, with entry: %s", parent, entry) pptr, err := jsonpointer.New(parent) if err != nil { return "", "", nil, err } pvalue, _, err := pptr.Get(spec) if err != nil { return "", "", nil, fmt.Errorf("can't get parent for %s: %v", parent, err) } return parent, entry, pvalue, nil } // updateRef replaces a ref by another one func updateRef(spec *swspec.Swagger, key string, ref swspec.Ref) error { debugLog("updating ref for %s with %s", key, ref.String()) pth, value, err := getPointerFromKey(spec, key) if err != nil { return err } switch refable := value.(type) { case *swspec.Schema: refable.Ref = ref case *swspec.SchemaOrArray: if refable.Schema != nil { refable.Schema.Ref = ref } case *swspec.SchemaOrBool: if refable.Schema != nil { refable.Schema.Ref = ref } case swspec.Schema: debugLog("rewriting holder for %T", refable) _, entry, pvalue, erp := getParentFromKey(spec, key) if erp != nil { return err } switch container := pvalue.(type) { case swspec.Definitions: container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}} case map[string]swspec.Schema: container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}} case []swspec.Schema: idx, err := strconv.Atoi(entry) if err != nil { return fmt.Errorf("%s not a number: %v", pth, err) } container[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}} case *swspec.SchemaOrArray: // NOTE: this is necessarily an array - otherwise, the parent would be *Schema idx, err := strconv.Atoi(entry) if err != nil { return fmt.Errorf("%s not a number: %v", pth, err) } container.Schemas[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}} // NOTE: can't have case *swspec.SchemaOrBool = parent in this case is *Schema default: return fmt.Errorf("unhandled container type at %s: %T", key, value) } default: return fmt.Errorf("no schema with ref found at %s for %T", key, value) } return nil } // updateRefWithSchema replaces a ref with a schema (i.e. re-inline schema) func updateRefWithSchema(spec *swspec.Swagger, key string, sch *swspec.Schema) error { debugLog("updating ref for %s with schema", key) pth, value, err := getPointerFromKey(spec, key) if err != nil { return err } switch refable := value.(type) { case *swspec.Schema: *refable = *sch case swspec.Schema: _, entry, pvalue, erp := getParentFromKey(spec, key) if erp != nil { return err } switch container := pvalue.(type) { case swspec.Definitions: container[entry] = *sch case map[string]swspec.Schema: container[entry] = *sch case []swspec.Schema: idx, err := strconv.Atoi(entry) if err != nil { return fmt.Errorf("%s not a number: %v", pth, err) } container[idx] = *sch case *swspec.SchemaOrArray: // NOTE: this is necessarily an array - otherwise, the parent would be *Schema idx, err := strconv.Atoi(entry) if err != nil { return fmt.Errorf("%s not a number: %v", pth, err) } container.Schemas[idx] = *sch // NOTE: can't have case *swspec.SchemaOrBool = parent in this case is *Schema default: return fmt.Errorf("unhandled type for parent of [%s]: %T", key, value) } case *swspec.SchemaOrArray: *refable.Schema = *sch // NOTE: can't have case *swspec.SchemaOrBool = parent in this case is *Schema case *swspec.SchemaOrBool: *refable.Schema = *sch default: return fmt.Errorf("no schema with ref found at %s for %T", key, value) } return nil } func containsString(names []string, name string) bool { for _, nm := range names { if nm == name { return true } } return false } type opRef struct { Method string Path string Key string ID string Op *swspec.Operation Ref swspec.Ref } type opRefs []opRef func (o opRefs) Len() int { return len(o) } func (o opRefs) Swap(i, j int) { o[i], o[j] = o[j], o[i] } func (o opRefs) Less(i, j int) bool { return o[i].Key < o[j].Key } func gatherOperations(specDoc *Spec, operationIDs []string) map[string]opRef { var oprefs opRefs for method, pathItem := range specDoc.Operations() { for pth, operation := range pathItem { vv := *operation oprefs = append(oprefs, opRef{ Key: swag.ToGoName(strings.ToLower(method) + " " + pth), Method: method, Path: pth, ID: vv.ID, Op: &vv, Ref: swspec.MustCreateRef("#" + slashpath.Join("/paths", jsonpointer.Escape(pth), method)), }) } } sort.Sort(oprefs) operations := make(map[string]opRef) for _, opr := range oprefs { nm := opr.ID if nm == "" { nm = opr.Key } oo, found := operations[nm] if found && oo.Method != opr.Method && oo.Path != opr.Path { nm = opr.Key } if len(operationIDs) == 0 || containsString(operationIDs, opr.ID) || containsString(operationIDs, nm) { opr.ID = nm opr.Op.ID = nm operations[nm] = opr } } return operations } // stripPointersAndOAIGen removes anonymous JSON pointers from spec and chain with name conflicts handler. // This loops until the spec has no such pointer and all name conflicts have been reduced as much as possible. func stripPointersAndOAIGen(opts *FlattenOpts) error { // name all JSON pointers to anonymous documents if err := namePointers(opts); err != nil { return err } // remove unnecessary OAIGen ref (created when flattening external refs creates name conflicts) hasIntroducedPointerOrInline, ers := stripOAIGen(opts) if ers != nil { return ers } // iterate as pointer or OAIGen resolution may introduce inline schemas or pointers for hasIntroducedPointerOrInline { if !opts.Minimal { if err := nameInlinedSchemas(opts); err != nil { return err } } if err := namePointers(opts); err != nil { return err } // restrip if hasIntroducedPointerOrInline, ers = stripOAIGen(opts); ers != nil { return ers } } return nil } // stripOAIGen strips the spec from unnecessary OAIGen constructs, initially created to dedupe flattened definitions. // A dedupe is deemed unnecessary whenever: // - the only conflict is with its (single) parent: OAIGen is merged into its parent // - there is a conflict with multiple parents: merge OAIGen in first parent, the rewrite other parents to point to // the first parent. // // This function returns a true bool whenever it re-inlined a complex schema, so the caller may chose to iterate // flattening again. // // NOTE: the OAIGen definition cannot be itself a $ref. func stripOAIGen(opts *FlattenOpts) (bool, error) { debugLog("stripOAIGen") replacedWithComplex := false for k, v := range opts.Spec.references.allRefs { // figure out referers of OAIGen definitions for _, r := range opts.flattenContext.newRefs { if r.isOAIGen && !r.resolved && r.path == v.String() { // bail on already resolved entries (avoid looping) r.parents = append(r.parents, k) } } } for _, r := range opts.flattenContext.newRefs { if r.isOAIGen && len(r.parents) >= 1 && r.schema.Ref.String() == "" { pr := r.parents sort.Strings(pr) // rewrite first parent schema in lexicographical order debugLog("rewrite first parent %s with schema", pr[0]) if err := updateRefWithSchema(opts.Swagger(), pr[0], r.schema); err != nil { return false, err } // rewrite other parents to point to first parent if len(pr) > 1 { for _, p := range pr[1:] { replacingRef := swspec.MustCreateRef(pr[0]) // Set complex when replacing ref is an anonymous jsonpointer: further processing may be required replacedWithComplex = replacedWithComplex || slashpath.Dir(replacingRef.String()) != definitionsPath debugLog("rewrite parent with ref: %s", replacingRef.String()) // NOTE: it is possible at this stage to introduce json pointers (to non-definitions places). // Those are stripped later on. if err := updateRef(opts.Swagger(), p, replacingRef); err != nil { return false, err } } } // remove OAIGen definition debugLog("removing definition %s", slashpath.Base(r.path)) delete(opts.Swagger().Definitions, slashpath.Base(r.path)) // mark naming conflict as resolved opts.flattenContext.newRefs[r.key].isOAIGen = false opts.flattenContext.newRefs[r.key].resolved = true // determine if the previous substitution did inline a complex schema if r.schema != nil && r.schema.Ref.String() == "" { // inline schema asch, err := Schema(SchemaOpts{Schema: r.schema, Root: opts.Swagger(), BasePath: opts.BasePath}) if err != nil { return false, err } debugLog("re-inline schema: parent: %s, %t", pr[0], isAnalyzedAsComplex(asch)) replacedWithComplex = replacedWithComplex || !(slashpath.Dir(pr[0]) == definitionsPath) && isAnalyzedAsComplex(asch) } } } opts.Spec.reload() // re-analyze return replacedWithComplex, nil } // croaks logs notifications and warnings about valid, but possibly unwanted constructs resulting // from flattening a spec func croak(opts *FlattenOpts) { reported := make(map[string]bool, len(opts.flattenContext.newRefs)) for _, v := range opts.Spec.references.allRefs { // warns about duplicate handling for _, r := range opts.flattenContext.newRefs { if r.isOAIGen && r.path == v.String() { reported[r.newName] = true } } } for k := range reported { log.Printf("warning: duplicate flattened definition name resolved as %s", k) } } // namePointers replaces all JSON pointers to anonymous documents by a $ref to a new named definitions. // // This is carried on depth-first. Pointers to $refs which are top level definitions are replaced by the $ref itself. // Pointers to simple types are expanded, unless they express commonality (i.e. several such $ref are used). func namePointers(opts *FlattenOpts) error { debugLog("name pointers") refsToReplace := make(map[string]SchemaRef, len(opts.Spec.references.schemas)) //for k, ref := range opts.Spec.references.schemas { for k, ref := range opts.Spec.references.allRefs { if slashpath.Dir(ref.String()) == definitionsPath { // this a ref to a top-level definition: ok continue } replacingRef, sch, erd := deepestRef(opts, ref) if erd != nil { return fmt.Errorf("at %s, %v", k, erd) } debugLog("planning pointer to replace at %s: %s, resolved to: %s", k, ref.String(), replacingRef.String()) refsToReplace[k] = SchemaRef{ Name: k, // caller Ref: replacingRef, // callee Schema: sch, TopLevel: slashpath.Dir(replacingRef.String()) == definitionsPath, } } depthFirst := sortDepthFirst(refsToReplace) namer := &inlineSchemaNamer{ Spec: opts.Swagger(), Operations: opRefsByRef(gatherOperations(opts.Spec, nil)), flattenContext: opts.flattenContext, opts: opts, } for _, key := range depthFirst { v := refsToReplace[key] // update current replacement, which may have been updated by previous changes of deeper elements replacingRef, sch, erd := deepestRef(opts, v.Ref) if erd != nil { return fmt.Errorf("at %s, %v", key, erd) } v.Ref = replacingRef v.Schema = sch v.TopLevel = slashpath.Dir(replacingRef.String()) == definitionsPath debugLog("replacing pointer at %s: resolved to: %s", key, v.Ref.String()) if v.TopLevel { debugLog("replace pointer %s by canonical definition: %s", key, v.Ref.String()) // if the schema is a $ref to a top level definition, just rewrite the pointer to this $ref if err := updateRef(opts.Swagger(), key, v.Ref); err != nil { return err } } else { // this is a JSON pointer to an anonymous document (internal or external): // create a definition for this schema when: // - it is a complex schema // - or it is pointed by more than one $ref (i.e. expresses commonality) // otherwise, expand the pointer (single reference to a simple type) // // The named definition for this follows the target's key, not the caller's debugLog("namePointers at %s for %s", key, v.Ref.String()) // qualify the expanded schema asch, ers := Schema(SchemaOpts{Schema: v.Schema, Root: opts.Swagger(), BasePath: opts.BasePath}) if ers != nil { return fmt.Errorf("schema analysis [%s]: %v", key, ers) } callers := make([]string, 0, 64) debugLog("looking for callers") an := New(opts.Swagger()) for k, w := range an.references.allRefs { r, _, erd := deepestRef(opts, w) if erd != nil { return fmt.Errorf("at %s, %v", key, erd) } if r.String() == v.Ref.String() { callers = append(callers, k) } } debugLog("callers for %s: %d", v.Ref.String(), len(callers)) if len(callers) == 0 { // has already been updated and resolved continue } debugLog("number of callers for %s: %d", v.Ref.String(), len(callers)) if !asch.IsSimpleSchema || len(callers) > 1 { debugLog("replace JSON pointer at [%s] by definition: %s", key, v.Ref.String()) if err := namer.Name(v.Ref.String(), v.Schema, asch); err != nil { return err } for _, caller := range callers { if caller != key { // move $ref for next to resolve debugLog("identified caller of %s at [%s]", v.Ref.String(), caller) c := refsToReplace[caller] c.Ref = v.Ref refsToReplace[caller] = c } } } else { debugLog("expand JSON pointer") if err := updateRefWithSchema(opts.Swagger(), key, v.Schema); err != nil { return err } // NOTE: there is no other caller to update } } } opts.Spec.reload() // re-analyze return nil } // deepestRef finds the first definition ref, from a cascade of nested refs which are not definitions. // - if no definition is found, returns the deepest ref. // - pointers to external files are expanded // // NOTE: all external $ref's are assumed to be already expanded at this stage. func deepestRef(opts *FlattenOpts, ref swspec.Ref) (swspec.Ref, *swspec.Schema, error) { debugLog("deepestRef for %s", ref.String()) if !ref.HasFragmentOnly { // does nothing on external $refs return ref, nil, nil } currentRef := ref visited := make(map[string]bool, 64) DOWNREF: for currentRef.String() != "" { if slashpath.Dir(currentRef.String()) == definitionsPath { // this is a top-level definition: stop here and return this ref return currentRef, nil, nil } if _, beenThere := visited[currentRef.String()]; beenThere { return swspec.Ref{}, nil, fmt.Errorf("cannot resolve cyclic chain of pointers under %s", currentRef.String()) } visited[currentRef.String()] = true value, _, err := currentRef.GetPointer().Get(opts.Swagger()) if err != nil { return swspec.Ref{}, nil, err } switch refable := value.(type) { case *swspec.Schema: if refable.Ref.String() == "" { break DOWNREF } currentRef = refable.Ref case swspec.Schema: if refable.Ref.String() == "" { break DOWNREF } currentRef = refable.Ref case *swspec.SchemaOrArray: if refable.Schema == nil || refable.Schema != nil && refable.Schema.Ref.String() == "" { break DOWNREF } currentRef = refable.Schema.Ref case *swspec.SchemaOrBool: if refable.Schema == nil || refable.Schema != nil && refable.Schema.Ref.String() == "" { break DOWNREF } currentRef = refable.Schema.Ref default: return swspec.Ref{}, nil, fmt.Errorf("unhandled type to resolve JSON pointer %s. Expected a Schema, got: %T", currentRef.String(), value) } } // assess what schema we're ending with sch, erv := swspec.ResolveRefWithBase(opts.Swagger(), ¤tRef, opts.ExpandOpts(false)) if erv != nil { return swspec.Ref{}, nil, erv } if sch == nil { return swspec.Ref{}, nil, fmt.Errorf("no schema found at %s", currentRef.String()) } return currentRef, sch, nil } // normalizeRef strips the current file from any $ref. This works around issue go-openapi/spec#76: // leading absolute file in $ref is stripped func normalizeRef(opts *FlattenOpts) error { debugLog("normalizeRef") opts.Spec.reload() // re-analyze for k, w := range opts.Spec.references.allRefs { if strings.HasPrefix(w.String(), opts.BasePath+definitionsPath) { // may be a mix of / and \, depending on OS // strip base path from definition debugLog("stripping absolute path for: %s", w.String()) if err := updateRef(opts.Swagger(), k, swspec.MustCreateRef(slashpath.Join(definitionsPath, slashpath.Base(w.String())))); err != nil { return err } } } opts.Spec.reload() // re-analyze return nil } golang-github-go-openapi-analysis-0.15.0/flatten_test.go000066400000000000000000001537341332100444700232370ustar00rootroot00000000000000// Copyright 2015 go-swagger maintainers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package analysis import ( "bytes" "encoding/json" "io/ioutil" "log" "os" "path" "path/filepath" "strings" "testing" "github.com/go-openapi/jsonpointer" "github.com/go-openapi/spec" "github.com/stretchr/testify/assert" ) func TestSaveDefinition(t *testing.T) { sp := &spec.Swagger{} saveSchema(sp, "theName", spec.StringProperty()) assert.Contains(t, sp.Definitions, "theName") } func TestNameFromRef(t *testing.T) { values := []struct{ Source, Expected string }{ {"#/definitions/errorModel", "errorModel"}, {"http://somewhere.com/definitions/errorModel", "errorModel"}, {"http://somewhere.com/definitions/errorModel.json", "errorModel"}, {"/definitions/errorModel", "errorModel"}, {"/definitions/errorModel.json", "errorModel"}, {"http://somewhere.com", "somewhereCom"}, {"#", ""}, } for _, v := range values { assert.Equal(t, v.Expected, nameFromRef(spec.MustCreateRef(v.Source))) } } func TestDefinitionName(t *testing.T) { values := []struct { Source, Expected string Definitions spec.Definitions }{ {"#/definitions/errorModel", "errorModel", map[string]spec.Schema(nil)}, {"http://somewhere.com/definitions/errorModel", "errorModel", map[string]spec.Schema(nil)}, {"#/definitions/errorModel", "errorModel", map[string]spec.Schema{"apples": *spec.StringProperty()}}, {"#/definitions/errorModel", "errorModelOAIGen", map[string]spec.Schema{"errorModel": *spec.StringProperty()}}, {"#/definitions/errorModel", "errorModelOAIGen1", map[string]spec.Schema{"errorModel": *spec.StringProperty(), "errorModelOAIGen": *spec.StringProperty()}}, {"#", "oaiGen", nil}, } for _, v := range values { u, _ := uniqifyName(v.Definitions, nameFromRef(spec.MustCreateRef(v.Source))) assert.Equal(t, v.Expected, u) } } var refFixture = []struct { Key string Ref spec.Ref }{ {"#/parameters/someParam/schema", spec.MustCreateRef("#/definitions/record")}, {"#/paths/~1some~1where~1{id}/parameters/1/schema", spec.MustCreateRef("#/definitions/record")}, {"#/paths/~1some~1where~1{id}/get/parameters/2/schema", spec.MustCreateRef("#/definitions/record")}, {"#/responses/someResponse/schema", spec.MustCreateRef("#/definitions/record")}, {"#/paths/~1some~1where~1{id}/get/responses/default/schema", spec.MustCreateRef("#/definitions/record")}, {"#/paths/~1some~1where~1{id}/get/responses/200/schema", spec.MustCreateRef("#/definitions/record")}, {"#/definitions/namedAgain", spec.MustCreateRef("#/definitions/named")}, {"#/definitions/datedTag/allOf/1", spec.MustCreateRef("#/definitions/tag")}, {"#/definitions/datedRecords/items/1", spec.MustCreateRef("#/definitions/record")}, {"#/definitions/datedTaggedRecords/items/1", spec.MustCreateRef("#/definitions/record")}, {"#/definitions/datedTaggedRecords/additionalItems", spec.MustCreateRef("#/definitions/tag")}, {"#/definitions/otherRecords/items", spec.MustCreateRef("#/definitions/record")}, {"#/definitions/tags/additionalProperties", spec.MustCreateRef("#/definitions/tag")}, {"#/definitions/namedThing/properties/name", spec.MustCreateRef("#/definitions/named")}, } func TestUpdateRef(t *testing.T) { bp := filepath.Join("fixtures", "external_definitions.yml") sp, err := loadSpec(bp) if assert.NoError(t, err) { for _, v := range refFixture { err := updateRef(sp, v.Key, v.Ref) if assert.NoError(t, err) { ptr, err := jsonpointer.New(v.Key[1:]) if assert.NoError(t, err) { vv, _, err := ptr.Get(sp) if assert.NoError(t, err) { switch tv := vv.(type) { case *spec.Schema: assert.Equal(t, v.Ref.String(), tv.Ref.String()) case spec.Schema: assert.Equal(t, v.Ref.String(), tv.Ref.String()) case *spec.SchemaOrBool: assert.Equal(t, v.Ref.String(), tv.Schema.Ref.String()) case *spec.SchemaOrArray: assert.Equal(t, v.Ref.String(), tv.Schema.Ref.String()) default: assert.Fail(t, "unknown type", "got %T", vv) } } } } } } } func TestImportExternalReferences(t *testing.T) { bp := filepath.Join(".", "fixtures", "external_definitions.yml") sp, err := loadSpec(bp) if assert.NoError(t, err) { for i, v := range refFixture { // there is 1 notable difference with the updateRef test: if i == 5 { v.Ref = spec.MustCreateRef("#/definitions/tag") } // technically not necessary to run for each value, but if things go right // this is idempotent, so having it repeat shouldn't matter // this validates that behavior err := importExternalReferences(&FlattenOpts{ Spec: New(sp), BasePath: bp, }) if assert.NoError(t, err) { ptr, err := jsonpointer.New(v.Key[1:]) if assert.NoError(t, err) { vv, _, err := ptr.Get(sp) if assert.NoError(t, err) { switch tv := vv.(type) { case *spec.Schema: assert.Equal(t, v.Ref.String(), tv.Ref.String(), "for %s", v.Key) case spec.Schema: assert.Equal(t, v.Ref.String(), tv.Ref.String(), "for %s", v.Key) case *spec.SchemaOrBool: assert.Equal(t, v.Ref.String(), tv.Schema.Ref.String(), "for %s", v.Key) case *spec.SchemaOrArray: assert.Equal(t, v.Ref.String(), tv.Schema.Ref.String(), "for %s", v.Key) default: assert.Fail(t, "unknown type", "got %T", vv) } } } } } assert.Len(t, sp.Definitions, 11) assert.Contains(t, sp.Definitions, "tag") assert.Contains(t, sp.Definitions, "named") assert.Contains(t, sp.Definitions, "record") } } func TestRewriteSchemaRef(t *testing.T) { bp := filepath.Join("fixtures", "inline_schemas.yml") sp, err := loadSpec(bp) if assert.NoError(t, err) { for i, v := range refFixture { err := rewriteSchemaToRef(sp, v.Key, v.Ref) if assert.NoError(t, err) { ptr, err := jsonpointer.New(v.Key[1:]) if assert.NoError(t, err) { vv, _, err := ptr.Get(sp) if assert.NoError(t, err) { switch tv := vv.(type) { case *spec.Schema: assert.Equal(t, v.Ref.String(), tv.Ref.String(), "at %d for %s", i, v.Key) case spec.Schema: assert.Equal(t, v.Ref.String(), tv.Ref.String(), "at %d for %s", i, v.Key) case *spec.SchemaOrBool: assert.Equal(t, v.Ref.String(), tv.Schema.Ref.String(), "at %d for %s", i, v.Key) case *spec.SchemaOrArray: assert.Equal(t, v.Ref.String(), tv.Schema.Ref.String(), "at %d for %s", i, v.Key) default: assert.Fail(t, "unknown type", "got %T", vv) } } } } } } } func TestSplitKey(t *testing.T) { type KeyFlag uint64 const ( isOperation KeyFlag = 1 << iota isDefinition isSharedOperationParam isOperationParam isOperationResponse isDefaultResponse isStatusCodeResponse ) values := []struct { Key string Flags KeyFlag PathItemRef spec.Ref PathRef spec.Ref Name string }{ { "#/paths/~1some~1where~1{id}/parameters/1/schema", isOperation | isSharedOperationParam, spec.Ref{}, spec.MustCreateRef("#/paths/~1some~1where~1{id}"), "", }, { "#/paths/~1some~1where~1{id}/get/parameters/2/schema", isOperation | isOperationParam, spec.MustCreateRef("#/paths/~1some~1where~1{id}/GET"), spec.MustCreateRef("#/paths/~1some~1where~1{id}"), "", }, { "#/paths/~1some~1where~1{id}/get/responses/default/schema", isOperation | isOperationResponse | isDefaultResponse, spec.MustCreateRef("#/paths/~1some~1where~1{id}/GET"), spec.MustCreateRef("#/paths/~1some~1where~1{id}"), "Default", }, { "#/paths/~1some~1where~1{id}/get/responses/200/schema", isOperation | isOperationResponse | isStatusCodeResponse, spec.MustCreateRef("#/paths/~1some~1where~1{id}/GET"), spec.MustCreateRef("#/paths/~1some~1where~1{id}"), "OK", }, { "#/definitions/namedAgain", isDefinition, spec.Ref{}, spec.Ref{}, "namedAgain", }, { "#/definitions/datedRecords/items/1", isDefinition, spec.Ref{}, spec.Ref{}, "datedRecords", }, { "#/definitions/datedRecords/items/1", isDefinition, spec.Ref{}, spec.Ref{}, "datedRecords", }, { "#/definitions/datedTaggedRecords/items/1", isDefinition, spec.Ref{}, spec.Ref{}, "datedTaggedRecords", }, { "#/definitions/datedTaggedRecords/additionalItems", isDefinition, spec.Ref{}, spec.Ref{}, "datedTaggedRecords", }, { "#/definitions/otherRecords/items", isDefinition, spec.Ref{}, spec.Ref{}, "otherRecords", }, { "#/definitions/tags/additionalProperties", isDefinition, spec.Ref{}, spec.Ref{}, "tags", }, { "#/definitions/namedThing/properties/name", isDefinition, spec.Ref{}, spec.Ref{}, "namedThing", }, } for i, v := range values { parts := keyParts(v.Key) pref := parts.PathRef() piref := parts.PathItemRef() assert.Equal(t, v.PathRef.String(), pref.String(), "pathRef: %s at %d", v.Key, i) assert.Equal(t, v.PathItemRef.String(), piref.String(), "pathItemRef: %s at %d", v.Key, i) if v.Flags&isOperation != 0 { assert.True(t, parts.IsOperation(), "isOperation: %s at %d", v.Key, i) } else { assert.False(t, parts.IsOperation(), "isOperation: %s at %d", v.Key, i) } if v.Flags&isDefinition != 0 { assert.True(t, parts.IsDefinition(), "isDefinition: %s at %d", v.Key, i) assert.Equal(t, v.Name, parts.DefinitionName(), "definition name: %s at %d", v.Key, i) } else { assert.False(t, parts.IsDefinition(), "isDefinition: %s at %d", v.Key, i) if v.Name != "" { assert.Equal(t, v.Name, parts.ResponseName(), "response name: %s at %d", v.Key, i) } } if v.Flags&isOperationParam != 0 { assert.True(t, parts.IsOperationParam(), "isOperationParam: %s at %d", v.Key, i) } else { assert.False(t, parts.IsOperationParam(), "isOperationParam: %s at %d", v.Key, i) } if v.Flags&isSharedOperationParam != 0 { assert.True(t, parts.IsSharedOperationParam(), "isSharedOperationParam: %s at %d", v.Key, i) } else { assert.False(t, parts.IsSharedOperationParam(), "isSharedOperationParam: %s at %d", v.Key, i) } if v.Flags&isOperationResponse != 0 { assert.True(t, parts.IsOperationResponse(), "isOperationResponse: %s at %d", v.Key, i) } else { assert.False(t, parts.IsOperationResponse(), "isOperationResponse: %s at %d", v.Key, i) } if v.Flags&isDefaultResponse != 0 { assert.True(t, parts.IsDefaultResponse(), "isDefaultResponse: %s at %d", v.Key, i) } else { assert.False(t, parts.IsDefaultResponse(), "isDefaultResponse: %s at %d", v.Key, i) } if v.Flags&isStatusCodeResponse != 0 { assert.True(t, parts.IsStatusCodeResponse(), "isStatusCodeResponse: %s at %d", v.Key, i) } else { assert.False(t, parts.IsStatusCodeResponse(), "isStatusCodeResponse: %s at %d", v.Key, i) } } } func definitionPtr(key string) string { if !strings.HasPrefix(key, "#/definitions") { return key } return strings.Join(strings.Split(key, "/")[:3], "/") } func TestNamesFromKey(t *testing.T) { bp := filepath.Join("fixtures", "inline_schemas.yml") sp, err := loadSpec(bp) if assert.NoError(t, err) { values := []struct { Key string Names []string }{ {"#/paths/~1some~1where~1{id}/parameters/1/schema", []string{"GetSomeWhereID params body", "PostSomeWhereID params body"}}, {"#/paths/~1some~1where~1{id}/get/parameters/2/schema", []string{"GetSomeWhereID params body"}}, {"#/paths/~1some~1where~1{id}/get/responses/default/schema", []string{"GetSomeWhereID Default body"}}, {"#/paths/~1some~1where~1{id}/get/responses/200/schema", []string{"GetSomeWhereID OK body"}}, {"#/definitions/namedAgain", []string{"namedAgain"}}, {"#/definitions/datedTag/allOf/1", []string{"datedTag allOf 1"}}, {"#/definitions/datedRecords/items/1", []string{"datedRecords tuple 1"}}, {"#/definitions/datedTaggedRecords/items/1", []string{"datedTaggedRecords tuple 1"}}, {"#/definitions/datedTaggedRecords/additionalItems", []string{"datedTaggedRecords tuple additionalItems"}}, {"#/definitions/otherRecords/items", []string{"otherRecords items"}}, {"#/definitions/tags/additionalProperties", []string{"tags additionalProperties"}}, {"#/definitions/namedThing/properties/name", []string{"namedThing name"}}, } for i, v := range values { ptr, err := jsonpointer.New(definitionPtr(v.Key)[1:]) if assert.NoError(t, err) { vv, _, err := ptr.Get(sp) if assert.NoError(t, err) { switch tv := vv.(type) { case *spec.Schema: aschema, err := Schema(SchemaOpts{Schema: tv, Root: sp, BasePath: bp}) if assert.NoError(t, err) { names := namesFromKey(keyParts(v.Key), aschema, opRefsByRef(gatherOperations(New(sp), nil))) assert.Equal(t, v.Names, names, "for %s at %d", v.Key, i) } case spec.Schema: aschema, err := Schema(SchemaOpts{Schema: &tv, Root: sp, BasePath: bp}) if assert.NoError(t, err) { names := namesFromKey(keyParts(v.Key), aschema, opRefsByRef(gatherOperations(New(sp), nil))) assert.Equal(t, v.Names, names, "for %s at %d", v.Key, i) } default: assert.Fail(t, "unknown type", "got %T", vv) } } } } } } func TestDepthFirstSort(t *testing.T) { bp := filepath.Join("fixtures", "inline_schemas.yml") sp, err := loadSpec(bp) values := []string{ // Added shared parameters and responses "#/parameters/someParam/schema/properties/createdAt", "#/parameters/someParam/schema", "#/responses/someResponse/schema/properties/createdAt", "#/responses/someResponse/schema", "#/paths/~1some~1where~1{id}/parameters/1/schema/properties/createdAt", "#/paths/~1some~1where~1{id}/parameters/1/schema", "#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/createdAt", "#/paths/~1some~1where~1{id}/get/parameters/2/schema", "#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/id", "#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/value", "#/paths/~1some~1where~1{id}/get/responses/200/schema", "#/paths/~1some~1where~1{id}/get/responses/404/schema", "#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/createdAt", "#/paths/~1some~1where~1{id}/get/responses/default/schema", "#/definitions/datedRecords/items/1/properties/createdAt", "#/definitions/datedTaggedRecords/items/1/properties/createdAt", "#/definitions/namedThing/properties/name/properties/id", "#/definitions/records/items/0/properties/createdAt", "#/definitions/datedTaggedRecords/additionalItems/properties/id", "#/definitions/datedTaggedRecords/additionalItems/properties/value", "#/definitions/otherRecords/items/properties/createdAt", "#/definitions/tags/additionalProperties/properties/id", "#/definitions/tags/additionalProperties/properties/value", "#/definitions/datedRecords/items/0", "#/definitions/datedRecords/items/1", "#/definitions/datedTag/allOf/0", "#/definitions/datedTag/allOf/1", "#/definitions/datedTag/properties/id", "#/definitions/datedTag/properties/value", "#/definitions/datedTaggedRecords/items/0", "#/definitions/datedTaggedRecords/items/1", "#/definitions/namedAgain/properties/id", "#/definitions/namedThing/properties/name", "#/definitions/pneumonoultramicroscopicsilicovolcanoconiosisAntidisestablishmentarianism/properties/" + "floccinaucinihilipilificationCreatedAt", "#/definitions/records/items/0", "#/definitions/datedTaggedRecords/additionalItems", "#/definitions/otherRecords/items", "#/definitions/tags/additionalProperties", "#/definitions/datedRecords", "#/definitions/datedTag", "#/definitions/datedTaggedRecords", "#/definitions/namedAgain", "#/definitions/namedThing", "#/definitions/otherRecords", "#/definitions/pneumonoultramicroscopicsilicovolcanoconiosisAntidisestablishmentarianism", "#/definitions/records", "#/definitions/tags", } if assert.NoError(t, err) { a := New(sp) result := sortDepthFirst(a.allSchemas) assert.Equal(t, values, result) } } func TestBuildNameWithReservedKeyWord(t *testing.T) { s := splitKey([]string{"definitions", "fullview", "properties", "properties"}) startIdx := 2 segments := []string{"fullview"} newName := s.BuildName(segments, startIdx, nil) assert.Equal(t, "fullview properties", newName) s = splitKey([]string{"definitions", "fullview", "properties", "properties", "properties", "properties", "properties", "properties"}) newName = s.BuildName(segments, startIdx, nil) assert.Equal(t, "fullview properties properties properties", newName) } func TestNameInlinedSchemas(t *testing.T) { values := []struct { Key string Location string Ref spec.Ref }{ {"#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/record/items/2/properties/name", "#/definitions/getSomeWhereIdParamsBodyRecordItems2/properties/name", spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems2Name"), }, {"#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/record/items/1", "#/definitions/getSomeWhereIdParamsBodyRecord/items/1", spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems1"), }, {"#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/record/items/2", "#/definitions/getSomeWhereIdParamsBodyRecord/items/2", spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems2"), }, {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record/items/2/properties/name", "#/definitions/getSomeWhereIdOKBodyRecordItems2/properties/name", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecordItems2Name"), }, {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record/items/1", "#/definitions/getSomeWhereIdOKBodyRecord/items/1", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecordItems1"), }, {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record/items/2", "#/definitions/getSomeWhereIdOKBodyRecord/items/2", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecordItems2"), }, {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record", "#/definitions/getSomeWhereIdOKBody/properties/record", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecord"), }, {"#/paths/~1some~1where~1{id}/get/responses/200/schema", "#/paths/~1some~1where~1{id}/get/responses/200/schema", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBody"), }, {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record/items/2/properties/name", "#/definitions/getSomeWhereIdDefaultBodyRecordItems2/properties/name", spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecordItems2Name"), }, {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record/items/1", "#/definitions/getSomeWhereIdDefaultBodyRecord/items/1", spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecordItems1"), }, {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record/items/2", "#/definitions/getSomeWhereIdDefaultBodyRecord/items/2", spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecordItems2"), }, {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record", "#/definitions/getSomeWhereIdDefaultBody/properties/record", spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecord"), }, {"#/paths/~1some~1where~1{id}/get/responses/default/schema", "#/paths/~1some~1where~1{id}/get/responses/default/schema", spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBody"), }, // maps: //{"#/definitions/nestedThing/properties/record/items/2/allOf/1/additionalProperties", //"#/definitions/nestedThingRecordItems2AllOf1/additionalProperties", //spec.MustCreateRef("#/definitions/nestedThingRecordItems2AllOf1AdditionalProperties"), // }, //{"#/definitions/nestedThing/properties/record/items/2/allOf/1", //"#/definitions/nestedThingRecordItems2/allOf/1", //spec.MustCreateRef("#/definitions/nestedThingRecordItems2AllOf1"), //}, {"#/definitions/nestedThing/properties/record/items/2/properties/name", "#/definitions/nestedThingRecordItems2/properties/name", spec.MustCreateRef("#/definitions/nestedThingRecordItems2Name"), }, {"#/definitions/nestedThing/properties/record/items/1", "#/definitions/nestedThingRecord/items/1", spec.MustCreateRef("#/definitions/nestedThingRecordItems1"), }, {"#/definitions/nestedThing/properties/record/items/2", "#/definitions/nestedThingRecord/items/2", spec.MustCreateRef("#/definitions/nestedThingRecordItems2"), }, {"#/definitions/datedRecords/items/1", "#/definitions/datedRecords/items/1", spec.MustCreateRef("#/definitions/datedRecordsItems1"), }, {"#/definitions/datedTaggedRecords/items/1", "#/definitions/datedTaggedRecords/items/1", spec.MustCreateRef("#/definitions/datedTaggedRecordsItems1"), }, {"#/definitions/namedThing/properties/name", "#/definitions/namedThing/properties/name", spec.MustCreateRef("#/definitions/namedThingName"), }, {"#/definitions/nestedThing/properties/record", "#/definitions/nestedThing/properties/record", spec.MustCreateRef("#/definitions/nestedThingRecord"), }, {"#/definitions/records/items/0", "#/definitions/records/items/0", spec.MustCreateRef("#/definitions/recordsItems0"), }, {"#/definitions/datedTaggedRecords/additionalItems", "#/definitions/datedTaggedRecords/additionalItems", spec.MustCreateRef("#/definitions/datedTaggedRecordsItemsAdditionalItems"), }, {"#/definitions/otherRecords/items", "#/definitions/otherRecords/items", spec.MustCreateRef("#/definitions/otherRecordsItems"), }, {"#/definitions/tags/additionalProperties", "#/definitions/tags/additionalProperties", spec.MustCreateRef("#/definitions/tagsAdditionalProperties"), }, } bp := filepath.Join("fixtures", "nested_inline_schemas.yml") sp := loadOrFail(t, bp) ere := spec.ExpandSpec(sp, &spec.ExpandOptions{ RelativeBase: bp, SkipSchemas: true, }) if !assert.NoError(t, ere) { t.FailNow() return } ern := nameInlinedSchemas(&FlattenOpts{ Spec: New(sp), BasePath: bp, }) if !assert.NoError(t, ern) { t.FailNow() return } for i, v := range values { ptr, err := jsonpointer.New(v.Location[1:]) if assert.NoError(t, err, "at %d for %s", i, v.Key) { vv, _, err := ptr.Get(sp) if assert.NoError(t, err, "at %d for %s", i, v.Key) { switch tv := vv.(type) { case *spec.Schema: assert.Equal(t, v.Ref.String(), tv.Ref.String(), "at %d for %s", i, v.Key) case spec.Schema: assert.Equal(t, v.Ref.String(), tv.Ref.String(), "at %d for %s", i, v.Key) case *spec.SchemaOrBool: var sRef spec.Ref if tv != nil && tv.Schema != nil { sRef = tv.Schema.Ref } assert.Equal(t, v.Ref.String(), sRef.String(), "at %d for %s", i, v.Key) case *spec.SchemaOrArray: var sRef spec.Ref if tv != nil && tv.Schema != nil { sRef = tv.Schema.Ref } assert.Equal(t, v.Ref.String(), sRef.String(), "at %d for %s", i, v.Key) default: assert.Fail(t, "unknown type", "got %T", vv) } } } } for k, rr := range New(sp).allSchemas { if !strings.HasPrefix(k, "#/responses") && !strings.HasPrefix(k, "#/parameters") { if rr.Schema != nil && rr.Schema.Ref.String() == "" && !rr.TopLevel { asch, err := Schema(SchemaOpts{Schema: rr.Schema, Root: sp, BasePath: bp}) if assert.NoError(t, err, "for key: %s", k) { if !asch.IsSimpleSchema && !asch.IsArray && !asch.IsMap { assert.Fail(t, "not a top level schema", "for key: %s", k) } } } } } } func TestFlatten(t *testing.T) { cwd, _ := os.Getwd() bp := filepath.Join(cwd, "fixtures", "flatten.yml") sp, err := loadSpec(bp) values := []struct { Key string Location string Ref spec.Ref Expected interface{} }{ { "#/responses/notFound/schema", "#/responses/notFound/schema", spec.MustCreateRef("#/definitions/error"), nil, }, { "#/paths/~1some~1where~1{id}/parameters/0", "#/paths/~1some~1where~1{id}/parameters/0/name", spec.Ref{}, "id", }, { "#/paths/~1other~1place", "#/paths/~1other~1place/get/operationId", spec.Ref{}, "modelOp", }, { "#/paths/~1some~1where~1{id}/get/parameters/0", "#/paths/~1some~1where~1{id}/get/parameters/0/name", spec.Ref{}, "limit", }, { "#/paths/~1some~1where~1{id}/get/parameters/1", "#/paths/~1some~1where~1{id}/get/parameters/1/name", spec.Ref{}, "some", }, { "#/paths/~1some~1where~1{id}/get/parameters/2", "#/paths/~1some~1where~1{id}/get/parameters/2/name", spec.Ref{}, "other", }, { "#/paths/~1some~1where~1{id}/get/parameters/3", "#/paths/~1some~1where~1{id}/get/parameters/3/schema", spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBody"), "", }, { "#/paths/~1some~1where~1{id}/get/responses/200", "#/paths/~1some~1where~1{id}/get/responses/200/schema", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBody"), "", }, { "#/definitions/namedAgain", "", spec.MustCreateRef("#/definitions/named"), "", }, { "#/definitions/namedThing/properties/name", "", spec.MustCreateRef("#/definitions/named"), "", }, { "#/definitions/namedThing/properties/namedAgain", "", spec.MustCreateRef("#/definitions/namedAgain"), "", }, { "#/definitions/datedRecords/items/1", "", spec.MustCreateRef("#/definitions/record"), "", }, { "#/definitions/otherRecords/items", "", spec.MustCreateRef("#/definitions/record"), "", }, { "#/definitions/tags/additionalProperties", "", spec.MustCreateRef("#/definitions/tag"), "", }, { "#/definitions/datedTag/allOf/1", "", spec.MustCreateRef("#/definitions/tag"), "", }, /* Maps are now considered simple schemas { "#/definitions/nestedThingRecordItems2/allOf/1", "", spec.MustCreateRef("#/definitions/nestedThingRecordItems2AllOf1"), "", }, */ { "#/definitions/nestedThingRecord/items/1", "", spec.MustCreateRef("#/definitions/nestedThingRecordItems1"), "", }, { "#/definitions/nestedThingRecord/items/2", "", spec.MustCreateRef("#/definitions/nestedThingRecordItems2"), "", }, { "#/definitions/nestedThing/properties/record", "", spec.MustCreateRef("#/definitions/nestedThingRecord"), "", }, { "#/definitions/named", "#/definitions/named/type", spec.Ref{}, spec.StringOrArray{"string"}, }, { "#/definitions/error", "#/definitions/error/properties/id/type", spec.Ref{}, spec.StringOrArray{"integer"}, }, { "#/definitions/record", "#/definitions/record/properties/createdAt/format", spec.Ref{}, "date-time", }, { "#/definitions/getSomeWhereIdOKBody", "#/definitions/getSomeWhereIdOKBody/properties/record", spec.MustCreateRef("#/definitions/nestedThing"), nil, }, { "#/definitions/getSomeWhereIdParamsBody", "#/definitions/getSomeWhereIdParamsBody/properties/record", spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecord"), nil, }, { "#/definitions/getSomeWhereIdParamsBodyRecord", "#/definitions/getSomeWhereIdParamsBodyRecord/items/1", spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems1"), nil, }, { "#/definitions/getSomeWhereIdParamsBodyRecord", "#/definitions/getSomeWhereIdParamsBodyRecord/items/2", spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems2"), nil, }, { "#/definitions/getSomeWhereIdParamsBodyRecordItems2", "#/definitions/getSomeWhereIdParamsBodyRecordItems2/allOf/0/format", spec.Ref{}, "date", }, { "#/definitions/getSomeWhereIdParamsBodyRecordItems2Name", "#/definitions/getSomeWhereIdParamsBodyRecordItems2Name/properties/createdAt/format", spec.Ref{}, "date-time", }, { "#/definitions/getSomeWhereIdParamsBodyRecordItems2", "#/definitions/getSomeWhereIdParamsBodyRecordItems2/properties/name", spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems2Name"), "date", }, } if assert.NoError(t, err) { err := Flatten(FlattenOpts{Spec: New(sp), BasePath: bp}) if assert.NoError(t, err) { for i, v := range values { pk := v.Key[1:] if v.Location != "" { pk = v.Location[1:] } ptr, err := jsonpointer.New(pk) if assert.NoError(t, err, "at %d for %s", i, v.Key) { d, _, err := ptr.Get(sp) if assert.NoError(t, err) { if v.Ref.String() != "" { switch s := d.(type) { case *spec.Schema: assert.Equal(t, v.Ref.String(), s.Ref.String(), "at %d for %s", i, v.Key) case spec.Schema: assert.Equal(t, v.Ref.String(), s.Ref.String(), "at %d for %s", i, v.Key) case *spec.SchemaOrArray: var sRef spec.Ref if s != nil && s.Schema != nil { sRef = s.Schema.Ref } assert.Equal(t, v.Ref.String(), sRef.String(), "at %d for %s", i, v.Key) case *spec.SchemaOrBool: var sRef spec.Ref if s != nil && s.Schema != nil { sRef = s.Schema.Ref } assert.Equal(t, v.Ref.String(), sRef.String(), "at %d for %s", i, v.Key) default: assert.Fail(t, "unknown type", "got %T at %d for %s", d, i, v.Key) } } else { assert.Equal(t, v.Expected, d) } } } } } } } func TestFlatten_oaigenFull(t *testing.T) { defer log.SetOutput(os.Stdout) cwd, _ := os.Getwd() bp := filepath.Join(cwd, "fixtures", "oaigen", "fixture-oaigen.yaml") sp, err := loadSpec(bp) if !assert.NoError(t, err) { t.FailNow() return } var logCapture bytes.Buffer log.SetOutput(&logCapture) err = Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Verbose: true, Minimal: false, RemoveUnused: false}) msg := logCapture.String() if !assert.NoError(t, err) { t.FailNow() return } if !assert.Containsf(t, msg, "warning: duplicate flattened definition name resolved as aAOAIGen", "Expected log message") { t.Logf("Captured log: %s", msg) } if !assert.NotContainsf(t, msg, "warning: duplicate flattened definition name resolved as uniqueName2OAIGen", "Expected log message") { t.Logf("Captured log: %s", msg) } res := getInPath(t, sp, "/some/where", "/get/responses/204/schema") assert.JSONEqf(t, `{"$ref": "#/definitions/uniqueName1"}`, res, "Expected a simple schema for response") res = getInPath(t, sp, "/some/where", "/post/responses/204/schema") assert.JSONEqf(t, `{"$ref": "#/definitions/d"}`, res, "Expected a simple schema for response") res = getInPath(t, sp, "/some/where", "/get/responses/206/schema") assert.JSONEqf(t, `{"$ref": "#/definitions/a"}`, res, "Expected a simple schema for response") res = getInPath(t, sp, "/some/where", "/get/responses/304/schema") assert.JSONEqf(t, `{"$ref": "#/definitions/transitive11"}`, res, "Expected a simple schema for response") res = getInPath(t, sp, "/some/where", "/get/responses/205/schema") assert.JSONEqf(t, `{"$ref": "#/definitions/b"}`, res, "Expected a simple schema for response") res = getInPath(t, sp, "/some/where", "/post/responses/200/schema") assert.JSONEqf(t, `{"type": "integer"}`, res, "Expected a simple schema for response") res = getInPath(t, sp, "/some/where", "/post/responses/default/schema") // pointer expanded assert.JSONEqf(t, `{"type": "integer"}`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "a") assert.JSONEqf(t, `{"type": "object", "properties": { "a": { "$ref": "#/definitions/aAOAIGen" }}}`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "aA") assert.JSONEqf(t, `{"type": "string", "format": "date"}`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "aAOAIGen") assert.JSONEqf(t, `{ "type": "object", "properties": { "b": { "type": "integer" }}, "x-go-gen-location": "models"}`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "bB") assert.JSONEqf(t, `{"type": "string", "format": "date-time"}`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "d") assert.JSONEqf(t, `{ "type": "object", "properties": { "c": { "type": "integer" } } }`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "myBody") assert.JSONEqf(t, `{ "type": "object", "properties": { "aA": { "$ref": "#/definitions/aA" }, "prop1": { "type": "integer" } } }`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "uniqueName2") assert.JSONEqf(t, `{"$ref": "#/definitions/notUniqueName2"}`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "notUniqueName2") assert.JSONEqf(t, `{ "type": "object", "properties": { "prop6": { "type": "integer" } } }`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "uniqueName1") assert.JSONEqf(t, `{ "type": "object", "properties": { "prop5": { "type": "integer" }}}`, res, "Expected a simple schema for response") // allOf container: []spec.Schema res = getDefinition(t, sp, "getWithSliceContainerDefaultBody") assert.JSONEqf(t, `{ "allOf": [ { "$ref": "#/definitions/uniqueName3" }, { "$ref": "#/definitions/getWithSliceContainerDefaultBodyAllOf1" } ], "x-go-gen-location": "operations" }`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "getWithSliceContainerDefaultBodyAllOf1") assert.JSONEqf(t, `{ "type": "object", "properties": { "prop8": { "type": "string" } }, "x-go-gen-location": "models" }`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "getWithTupleContainerDefaultBody") assert.JSONEqf(t, `{ "type": "array", "items": [ { "$ref": "#/definitions/uniqueName3" }, { "$ref": "#/definitions/getWithSliceContainerDefaultBodyAllOf1" } ], "x-go-gen-location": "operations" }`, res, "Expected a simple schema for response") // with container SchemaOrArray res = getDefinition(t, sp, "getWithTupleConflictDefaultBody") assert.JSONEqf(t, `{ "type": "array", "items": [ { "$ref": "#/definitions/uniqueName4" }, { "$ref": "#/definitions/getWithTupleConflictDefaultBodyItems1" } ], "x-go-gen-location": "operations" }`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "getWithTupleConflictDefaultBodyItems1") assert.JSONEqf(t, `{ "type": "object", "properties": { "prop10": { "type": "string" } }, "x-go-gen-location": "models" }`, res, "Expected a simple schema for response") } func TestFlatten_oaigenMinimal(t *testing.T) { defer log.SetOutput(os.Stdout) cwd, _ := os.Getwd() bp := filepath.Join(cwd, "fixtures", "oaigen", "fixture-oaigen.yaml") sp, err := loadSpec(bp) if !assert.NoError(t, err) { t.FailNow() return } var logCapture bytes.Buffer log.SetOutput(&logCapture) err = Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Verbose: true, Minimal: true, RemoveUnused: false}) if !assert.NoError(t, err) { t.FailNow() return } //bb, _ := json.MarshalIndent(sp, "", " ") //t.Log(string(bb)) msg := logCapture.String() //t.Log(msg) if !assert.NotContainsf(t, msg, "warning: duplicate flattened definition name resolved as aAOAIGen", "Expected log message") { t.Logf("Captured log: %s", msg) } if !assert.NotContainsf(t, msg, "warning: duplicate flattened definition name resolved as uniqueName2OAIGen", "Expected log message") { t.Logf("Captured log: %s", msg) } res := getInPath(t, sp, "/some/where", "/get/responses/204/schema") assert.JSONEqf(t, `{"$ref": "#/definitions/uniqueName1"}`, res, "Expected a simple schema for response") res = getInPath(t, sp, "/some/where", "/post/responses/204/schema") assert.JSONEqf(t, `{"$ref": "#/definitions/d"}`, res, "Expected a simple schema for response") res = getInPath(t, sp, "/some/where", "/get/responses/206/schema") assert.JSONEqf(t, `{"$ref": "#/definitions/a"}`, res, "Expected a simple schema for response") res = getInPath(t, sp, "/some/where", "/get/responses/304/schema") assert.JSONEqf(t, `{"$ref": "#/definitions/transitive11"}`, res, "Expected a simple schema for response") res = getInPath(t, sp, "/some/where", "/get/responses/205/schema") assert.JSONEqf(t, `{"$ref": "#/definitions/b"}`, res, "Expected a simple schema for response") res = getInPath(t, sp, "/some/where", "/post/responses/200/schema") assert.JSONEqf(t, `{"type": "integer"}`, res, "Expected a simple schema for response") res = getInPath(t, sp, "/some/where", "/post/responses/default/schema") // This JSON pointer is expanded assert.JSONEqf(t, `{"type": "integer"}`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "aA") assert.JSONEqf(t, `{"type": "string", "format": "date"}`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "a") assert.JSONEqf(t, `{ "type": "object", "properties": { "a": { "type": "object", "properties": { "b": { "type": "integer" } } } } }`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "bB") assert.JSONEqf(t, `{"type": "string", "format": "date-time"}`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "d") assert.JSONEqf(t, `{ "type": "object", "properties": { "c": { "type": "integer" } } }`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "myBody") assert.JSONEqf(t, `{ "type": "object", "properties": { "aA": { "$ref": "#/definitions/aA" }, "prop1": { "type": "integer" } } }`, res, "Expected a simple schema for response") res = getDefinition(t, sp, "uniqueName2") assert.JSONEqf(t, `{"$ref": "#/definitions/notUniqueName2"}`, res, "Expected a simple schema for response") // with allOf container: []spec.Schema res = getInPath(t, sp, "/with/slice/container", "/get/responses/default/schema") assert.JSONEqf(t, `{ "allOf": [ { "$ref": "#/definitions/uniqueName3" }, { "$ref": "#/definitions/getWithSliceContainerDefaultBodyAllOf1" } ] }`, res, "Expected a simple schema for response") // with tuple container res = getInPath(t, sp, "/with/tuple/container", "/get/responses/default/schema") assert.JSONEqf(t, `{ "type": "array", "items": [ { "$ref": "#/definitions/uniqueName3" }, { "$ref": "#/definitions/getWithSliceContainerDefaultBodyAllOf1" } ] }`, res, "Expected a simple schema for response") // with SchemaOrArray container res = getInPath(t, sp, "/with/tuple/conflict", "/get/responses/default/schema") assert.JSONEqf(t, `{ "type": "array", "items": [ { "$ref": "#/definitions/uniqueName4" }, { "type": "object", "properties": { "prop10": { "type": "string" } } } ] }`, res, "Expected a simple schema for response") } func loadOrFail(t *testing.T, bp string) *spec.Swagger { cwd, _ := os.Getwd() sp, err := loadSpec(filepath.Join(cwd, bp)) if !assert.NoError(t, err) { t.FailNow() return nil } return sp } func assertNoOAIGen(t *testing.T, bp string, sp *spec.Swagger) bool { var logCapture bytes.Buffer log.SetOutput(&logCapture) err := Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Verbose: true, Minimal: false, RemoveUnused: false}) if !assert.NoError(t, err) { t.Fail() return false } msg := logCapture.String() assert.NotContains(t, msg, "warning") for k := range sp.Definitions { if !assert.NotContains(t, k, "OAIGen") { t.Fail() return false } } return true } func TestFlatten_oaigen_1260(t *testing.T) { // test fixture from issue go-swagger/go-swagger#1260 bp := filepath.Join("fixtures", "oaigen", "test3-swagger.yaml") sp := loadOrFail(t, bp) assert.Truef(t, assertNoOAIGen(t, bp, sp), "did not expect an OAIGen definition here") } func TestFlatten_oaigen_1260bis(t *testing.T) { // test fixture from issue go-swagger/go-swagger#1260 bp := filepath.Join("fixtures", "oaigen", "test3-bis-swagger.yaml") sp := loadOrFail(t, bp) assert.Truef(t, assertNoOAIGen(t, bp, sp), "did not expect an OAIGen definition here") } func TestFlatten_oaigen_1260ter(t *testing.T) { // test fixture from issue go-swagger/go-swagger#1260 bp := filepath.Join("fixtures", "oaigen", "test3-ter-swagger.yaml") sp := loadOrFail(t, bp) assert.Truef(t, assertNoOAIGen(t, bp, sp), "did not expect an OAIGen definition here") } func getDefinition(t *testing.T, sp *spec.Swagger, key string) string { d, ok := sp.Definitions[key] if !assert.Truef(t, ok, "Expected definition for %s", key) { t.FailNow() } res, _ := json.Marshal(d) return string(res) } func getInPath(t *testing.T, sp *spec.Swagger, path, key string) string { ptr, erp := jsonpointer.New(key) if !assert.NoError(t, erp, "at %s no key", key) { t.FailNow() } d, _, erg := ptr.Get(sp.Paths.Paths[path]) if !assert.NoError(t, erg, "at %s no value for %s", path, key) { t.FailNow() } res, _ := json.Marshal(d) return string(res) } func TestMoreNameInlinedSchemas(t *testing.T) { bp := filepath.Join("fixtures", "more_nested_inline_schemas.yml") sp := loadOrFail(t, bp) err := Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Verbose: true, Minimal: false, RemoveUnused: false}) assert.NoError(t, err) res := getInPath(t, sp, "/some/where/{id}", "/post/responses/200/schema") assert.JSONEqf(t, `{"type": "object", "additionalProperties":`+ `{ "type": "object", "additionalProperties": { "type": "object", "additionalProperties":`+ ` { "$ref":`+ ` "#/definitions/postSomeWhereIdOKBodyAdditionalPropertiesAdditionalPropertiesAdditionalProperties"}}}}`, res, "Expected a simple schema for response") res = getInPath(t, sp, "/some/where/{id}", "/post/responses/204/schema") assert.JSONEqf(t, `{ "type": "object", "additionalProperties": { "type": "array", "items": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "object", "additionalProperties": { "type": "array", "items": { "$ref":`+ `"#/definitions/`+ `postSomeWhereIdNoContentBodyAdditionalPropertiesItemsAdditionalPropertiesItemsAdditionalPropertiesItems" } } } } } } }`, res, "Expected a simple schema for response") } func TestRemoveUnused(t *testing.T) { bp := filepath.Join("fixtures", "oaigen", "fixture-oaigen.yaml") sp := loadOrFail(t, bp) err := Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Verbose: false, Minimal: true, RemoveUnused: true}) if !assert.NoError(t, err) { t.FailNow() return } assert.Nil(t, sp.Parameters) assert.Nil(t, sp.Responses) bp = filepath.Join("fixtures", "parameters", "fixture-parameters.yaml") sp = loadOrFail(t, bp) an := New(sp) err = Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: false, Minimal: true, RemoveUnused: true}) if !assert.NoError(t, err) { t.FailNow() return } assert.Nil(t, sp.Parameters) assert.Nil(t, sp.Responses) op, ok := an.OperationFor("GET", "/some/where") assert.True(t, ok) assert.Lenf(t, op.Parameters, 4, "Expected 4 parameters expanded for this operation") assert.Lenf(t, an.ParamsFor("GET", "/some/where"), 7, "Expected 7 parameters (with default) expanded for this operation") op, ok = an.OperationFor("PATCH", "/some/remote") assert.True(t, ok) assert.Lenf(t, op.Parameters, 1, "Expected 1 parameter expanded for this operation") assert.Lenf(t, an.ParamsFor("PATCH", "/some/remote"), 2, "Expected 2 parameters (with default) expanded for this operation") _, ok = sp.Definitions["unused"] assert.False(t, ok, "Did not expect to find #/definitions/unused") bp = filepath.Join("fixtures", "parameters", "fixture-parameters.yaml") sp, err = loadSpec(bp) if !assert.NoError(t, err) { t.FailNow() return } var logCapture bytes.Buffer log.SetOutput(&logCapture) err = Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Verbose: true, Minimal: false, RemoveUnused: true}) if !assert.NoError(t, err) { t.FailNow() return } msg := logCapture.String() if !assert.Containsf(t, msg, "info: removing unused definition: unused", "Expected log message") { t.Logf("Captured log: %s", msg) } assert.Nil(t, sp.Parameters) assert.Nil(t, sp.Responses) _, ok = sp.Definitions["unused"] assert.Falsef(t, ok, "Did not expect to find #/definitions/unused") } func TestOperationIDs(t *testing.T) { bp := filepath.Join("fixtures", "operations", "fixture-operations.yaml") sp := loadOrFail(t, bp) an := New(sp) err := Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: false, Minimal: false, RemoveUnused: false}) assert.NoError(t, err) res := gatherOperations(New(sp), []string{"getSomeWhere", "getSomeWhereElse"}) _, ok := res["getSomeWhere"] assert.Truef(t, ok, "Expected to find operation") _, ok = res["getSomeWhereElse"] assert.Truef(t, ok, "Expected to find operation") _, ok = res["postSomeWhere"] assert.Falsef(t, ok, "Did not expect to find operation") op, ok := an.OperationFor("GET", "/some/where/else") assert.True(t, ok) assert.NotNil(t, op) assert.Len(t, an.ParametersFor("getSomeWhereElse"), 2) op, ok = an.OperationFor("POST", "/some/where/else") assert.True(t, ok) assert.NotNil(t, op) assert.Len(t, an.ParametersFor("postSomeWhereElse"), 1) op, ok = an.OperationFor("PUT", "/some/where/else") assert.True(t, ok) assert.NotNil(t, op) assert.Len(t, an.ParametersFor("putSomeWhereElse"), 1) op, ok = an.OperationFor("PATCH", "/some/where/else") assert.True(t, ok) assert.NotNil(t, op) assert.Len(t, an.ParametersFor("patchSomeWhereElse"), 1) op, ok = an.OperationFor("DELETE", "/some/where/else") assert.True(t, ok) assert.NotNil(t, op) assert.Len(t, an.ParametersFor("deleteSomeWhereElse"), 1) op, ok = an.OperationFor("HEAD", "/some/where/else") assert.True(t, ok) assert.NotNil(t, op) assert.Len(t, an.ParametersFor("headSomeWhereElse"), 1) op, ok = an.OperationFor("OPTIONS", "/some/where/else") assert.True(t, ok) assert.NotNil(t, op) assert.Len(t, an.ParametersFor("optionsSomeWhereElse"), 1) assert.Len(t, an.ParametersFor("outOfThisWorld"), 0) } func TestFlatten_Pointers(t *testing.T) { defer log.SetOutput(os.Stdout) bp := filepath.Join("fixtures", "pointers", "fixture-pointers.yaml") sp := loadOrFail(t, bp) var logCapture bytes.Buffer log.SetOutput(&logCapture) an := New(sp) err := Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, RemoveUnused: false}) if !assert.NoError(t, err) { t.FailNow() return } //bb, _ := json.MarshalIndent(sp, "", " ") //t.Log(string(bb)) msg := logCapture.String() if !assert.NotContains(t, msg, "warning") { t.Log(msg) } // re-analyse and check all $ref's point to #/definitions bn := New(sp) for _, r := range bn.AllRefs() { assert.True(t, path.Dir(r.String()) == definitionsPath) } } // unit test guards in flatten not easily testable with actual specs func TestFlatten_ErrorHandling(t *testing.T) { bp := filepath.Join("fixtures", "errors", "fixture-unexpandable.yaml") // invalid spec expansion sp := loadOrFail(t, bp) err := Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Expand: true}) if !assert.Errorf(t, err, "Expected a failure") { t.FailNow() return } // reload original spec sp = loadOrFail(t, bp) err = Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Expand: false}) if !assert.Errorf(t, err, "Expected a failure") { t.FailNow() return } bp = filepath.Join("fixtures", "errors", "fixture-unexpandable-2.yaml") sp = loadOrFail(t, bp) err = Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Expand: false}) if !assert.Errorf(t, err, "Expected a failure") { t.FailNow() return } // reload original spec sp = loadOrFail(t, bp) err = Flatten(FlattenOpts{Spec: New(sp), BasePath: bp, Minimal: true, Expand: false}) if !assert.Errorf(t, err, "Expected a failure") { t.FailNow() return } // reload original spec sp = loadOrFail(t, bp) err = rewriteSchemaToRef(sp, "#/invalidPointer/key", spec.Ref{}) if !assert.Errorf(t, err, "Expected a failure") { t.FailNow() return } err = rewriteParentRef(sp, "#/invalidPointer/key", spec.Ref{}) if !assert.Errorf(t, err, "Expected a failure") { t.FailNow() return } err = updateRef(sp, "#/invalidPointer/key", spec.Ref{}) if !assert.Errorf(t, err, "Expected a failure") { t.FailNow() return } err = updateRefWithSchema(sp, "#/invalidPointer/key", &spec.Schema{}) if !assert.Errorf(t, err, "Expected a failure") { t.FailNow() return } _, _, err = getPointerFromKey(sp, "#/invalidPointer/key") if !assert.Errorf(t, err, "Expected a failure") { t.FailNow() return } _, _, err = getPointerFromKey(sp, "--->#/invalidJsonPointer") if !assert.Errorf(t, err, "Expected a failure") { t.FailNow() return } _, _, _, err = getParentFromKey(sp, "#/invalidPointer/key") if !assert.Errorf(t, err, "Expected a failure") { t.FailNow() return } _, _, _, err = getParentFromKey(sp, "--->#/invalidJsonPointer") if !assert.Errorf(t, err, "Expected a failure") { t.FailNow() return } assert.NotPanics(t, saveNilSchema) } func saveNilSchema() { cwd, _ := os.Getwd() bp := filepath.Join(cwd, "fixtures", "errors", "fixture-unexpandable-2.yaml") sp, _ := loadSpec(bp) saveSchema(sp, "ThisNilSchema", nil) } func TestFlatten_UnitGuards(t *testing.T) { parts := keyParts("#/nowhere/arbitrary/pointer") res := genLocation(parts) assert.Equal(t, "", res) res = parts.DefinitionName() assert.Equal(t, "", res) res = parts.ResponseName() assert.Equal(t, "", res) b := parts.isKeyName(-1) assert.False(t, b) } func TestFlatten_PointersLoop(t *testing.T) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stdout) bp := filepath.Join("fixtures", "pointers", "fixture-pointers-loop.yaml") sp := loadOrFail(t, bp) an := New(sp) err := Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, RemoveUnused: false}) if !assert.Error(t, err) { t.FailNow() return } } func TestFlatten_Bitbucket(t *testing.T) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stdout) bp := filepath.Join("fixtures", "bugs", "bitbucket.json") sp := loadOrFail(t, bp) an := New(sp) err := Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, RemoveUnused: false}) if !assert.NoError(t, err) { t.FailNow() return } // reload original spec sp = loadOrFail(t, bp) an = New(sp) err = Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: false, RemoveUnused: false}) if !assert.NoError(t, err) { t.FailNow() return } // reload original spec sp = loadOrFail(t, bp) an = New(sp) err = Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Expand: true, RemoveUnused: false}) if !assert.NoError(t, err) { t.FailNow() return } // reload original spec sp = loadOrFail(t, bp) an = New(sp) err = Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Expand: true, RemoveUnused: true}) if !assert.NoError(t, err) { t.FailNow() } assert.Len(t, sp.Definitions, 2) // only 2 remaining refs after expansion: circular $ref _, ok := sp.Definitions["base_commit"] assert.True(t, ok) _, ok = sp.Definitions["repository"] assert.True(t, ok) //bbb, _ := json.MarshalIndent(an.spec, "", " ") //log.Printf(string(bbb)) } func TestFlatten_Issue_1602(t *testing.T) { // $ref in #/responses or #/parameters // minimal repro test case bp := filepath.Join("fixtures", "bugs", "1602", "fixture-1602-1.yaml") sp := loadOrFail(t, bp) an := New(sp) err := Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, Expand: false, RemoveUnused: false}) assert.Error(t, err) //t.Logf("%s: %v", bp, err) // reload spec sp = loadOrFail(t, bp) an = New(sp) err = Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: false, Minimal: false, Expand: false, RemoveUnused: false}) assert.Error(t, err) // reload spec // with prior expansion, a pseudo schema is produced sp = loadOrFail(t, bp) an = New(sp) err = Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: false, Minimal: false, Expand: true, RemoveUnused: false}) assert.NoError(t, err) // full testcase bp = filepath.Join("fixtures", "bugs", "1602", "fixture-1602-full.yaml") sp = loadOrFail(t, bp) an = New(sp) err = Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: false, Minimal: true, Expand: false, RemoveUnused: false}) assert.Error(t, err) bp = filepath.Join("fixtures", "bugs", "1602", "fixture-1602-1.yaml") sp = loadOrFail(t, bp) an = New(sp) err = Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, Expand: false, RemoveUnused: false}) assert.Error(t, err) //t.Logf("%s: %v", bp, err) bp = filepath.Join("fixtures", "bugs", "1602", "fixture-1602-2.yaml") sp = loadOrFail(t, bp) an = New(sp) err = Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, Expand: false, RemoveUnused: false}) assert.Error(t, err) //t.Logf("%s: %v", bp, err) bp = filepath.Join("fixtures", "bugs", "1602", "fixture-1602-3.yaml") sp = loadOrFail(t, bp) an = New(sp) err = Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, Expand: false, RemoveUnused: false}) assert.Error(t, err) //t.Logf("%s: %v", bp, err) bp = filepath.Join("fixtures", "bugs", "1602", "fixture-1602-4.yaml") sp = loadOrFail(t, bp) an = New(sp) err = Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, Expand: false, RemoveUnused: false}) assert.Error(t, err) //t.Logf("%s: %v", bp, err) bp = filepath.Join("fixtures", "bugs", "1602", "fixture-1602-5.yaml") sp = loadOrFail(t, bp) an = New(sp) err = Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: true, Minimal: true, Expand: false, RemoveUnused: false}) assert.Error(t, err) //t.Logf("%s: %v", bp, err) } golang-github-go-openapi-analysis-0.15.0/mixin.go000066400000000000000000000222371332100444700216600ustar00rootroot00000000000000// Copyright 2015 go-swagger maintainers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package analysis import ( "fmt" "reflect" "github.com/go-openapi/spec" ) // Mixin modifies the primary swagger spec by adding the paths and // definitions from the mixin specs. Top level parameters and // responses from the mixins are also carried over. Operation id // collisions are avoided by appending "Mixin" but only if // needed. // // The following parts of primary are never modified by merging: // - Info // - BasePath // - Host // - ExternalDocs // // Consider calling FixEmptyResponseDescriptions() on the modified primary // if you read them from storage and they are valid to start with. // // Entries in "paths", "definitions", "parameters" and "responses" are // added to the primary in the order of the given mixins. If the entry // already exists in primary it is skipped with a warning message. // // The count of skipped entries (from collisions) is returned so any // deviation from the number expected can flag a warning in your build // scripts. Carefully review the collisions before accepting them; // consider renaming things if possible. // // No key normalization takes place (paths, type defs, // etc). Ensure they are canonical if your downstream tools do // key normalization of any form. // // Merging schemes (http, https), and consumers/producers do not account for // collisions. func Mixin(primary *spec.Swagger, mixins ...*spec.Swagger) []string { skipped := make([]string, 0, len(mixins)) opIds := getOpIds(primary) initPrimary(primary) for i, m := range mixins { skipped = append(skipped, mergeConsumes(primary, m)...) skipped = append(skipped, mergeProduces(primary, m)...) skipped = append(skipped, mergeTags(primary, m)...) skipped = append(skipped, mergeSchemes(primary, m)...) skipped = append(skipped, mergeSecurityDefinitions(primary, m)...) skipped = append(skipped, mergeSecurityRequirements(primary, m)...) skipped = append(skipped, mergeDefinitions(primary, m)...) // merging paths requires a map of operationIDs to work with skipped = append(skipped, mergePaths(primary, m, opIds, i)...) skipped = append(skipped, mergeParameters(primary, m)...) skipped = append(skipped, mergeResponses(primary, m)...) } return skipped } // getOpIds extracts all the paths..operationIds from the given // spec and returns them as the keys in a map with 'true' values. func getOpIds(s *spec.Swagger) map[string]bool { rv := make(map[string]bool) if s.Paths == nil { return rv } for _, v := range s.Paths.Paths { piops := pathItemOps(v) for _, op := range piops { rv[op.ID] = true } } return rv } func pathItemOps(p spec.PathItem) []*spec.Operation { var rv []*spec.Operation rv = appendOp(rv, p.Get) rv = appendOp(rv, p.Put) rv = appendOp(rv, p.Post) rv = appendOp(rv, p.Delete) rv = appendOp(rv, p.Head) rv = appendOp(rv, p.Patch) return rv } func appendOp(ops []*spec.Operation, op *spec.Operation) []*spec.Operation { if op == nil { return ops } return append(ops, op) } func mergeSecurityDefinitions(primary *spec.Swagger, m *spec.Swagger) (skipped []string) { for k, v := range m.SecurityDefinitions { if _, exists := primary.SecurityDefinitions[k]; exists { warn := fmt.Sprintf( "SecurityDefinitions entry '%v' already exists in primary or higher priority mixin, skipping\n", k) skipped = append(skipped, warn) continue } primary.SecurityDefinitions[k] = v } return } func mergeSecurityRequirements(primary *spec.Swagger, m *spec.Swagger) (skipped []string) { for _, v := range m.Security { found := false for _, vv := range primary.Security { if reflect.DeepEqual(v, vv) { found = true break } } if found { warn := fmt.Sprintf( "Security requirement: '%v' already exists in primary or higher priority mixin, skipping\n", v) skipped = append(skipped, warn) continue } primary.Security = append(primary.Security, v) } return } func mergeDefinitions(primary *spec.Swagger, m *spec.Swagger) (skipped []string) { for k, v := range m.Definitions { // assume name collisions represent IDENTICAL type. careful. if _, exists := primary.Definitions[k]; exists { warn := fmt.Sprintf( "definitions entry '%v' already exists in primary or higher priority mixin, skipping\n", k) skipped = append(skipped, warn) continue } primary.Definitions[k] = v } return } func mergePaths(primary *spec.Swagger, m *spec.Swagger, opIds map[string]bool, mixIndex int) (skipped []string) { if m.Paths != nil { for k, v := range m.Paths.Paths { if _, exists := primary.Paths.Paths[k]; exists { warn := fmt.Sprintf( "paths entry '%v' already exists in primary or higher priority mixin, skipping\n", k) skipped = append(skipped, warn) continue } // Swagger requires that operationIds be // unique within a spec. If we find a // collision we append "Mixin0" to the // operatoinId we are adding, where 0 is mixin // index. We assume that operationIds with // all the proivded specs are already unique. piops := pathItemOps(v) for _, piop := range piops { if opIds[piop.ID] { piop.ID = fmt.Sprintf("%v%v%v", piop.ID, "Mixin", mixIndex) } opIds[piop.ID] = true } primary.Paths.Paths[k] = v } } return } func mergeParameters(primary *spec.Swagger, m *spec.Swagger) (skipped []string) { for k, v := range m.Parameters { // could try to rename on conflict but would // have to fix $refs in the mixin. Complain // for now if _, exists := primary.Parameters[k]; exists { warn := fmt.Sprintf( "top level parameters entry '%v' already exists in primary or higher priority mixin, skipping\n", k) skipped = append(skipped, warn) continue } primary.Parameters[k] = v } return } func mergeResponses(primary *spec.Swagger, m *spec.Swagger) (skipped []string) { for k, v := range m.Responses { // could try to rename on conflict but would // have to fix $refs in the mixin. Complain // for now if _, exists := primary.Responses[k]; exists { warn := fmt.Sprintf( "top level responses entry '%v' already exists in primary or higher priority mixin, skipping\n", k) skipped = append(skipped, warn) continue } primary.Responses[k] = v } return } func mergeConsumes(primary *spec.Swagger, m *spec.Swagger) (skipped []string) { for _, v := range m.Consumes { found := false for _, vv := range primary.Consumes { if v == vv { found = true break } } if found { // no warning here: we just skip it continue } primary.Consumes = append(primary.Consumes, v) } return } func mergeProduces(primary *spec.Swagger, m *spec.Swagger) (skipped []string) { for _, v := range m.Produces { found := false for _, vv := range primary.Produces { if v == vv { found = true break } } if found { // no warning here: we just skip it continue } primary.Produces = append(primary.Produces, v) } return } func mergeTags(primary *spec.Swagger, m *spec.Swagger) (skipped []string) { for _, v := range m.Tags { found := false for _, vv := range primary.Tags { if v.Name == vv.Name { found = true break } } if found { warn := fmt.Sprintf( "top level tags entry with name '%v' already exists in primary or higher priority mixin, skipping\n", v.Name) skipped = append(skipped, warn) continue } primary.Tags = append(primary.Tags, v) } return } func mergeSchemes(primary *spec.Swagger, m *spec.Swagger) (skipped []string) { for _, v := range m.Schemes { found := false for _, vv := range primary.Schemes { if v == vv { found = true break } } if found { // no warning here: we just skip it continue } primary.Schemes = append(primary.Schemes, v) } return } func initPrimary(primary *spec.Swagger) { if primary.SecurityDefinitions == nil { primary.SecurityDefinitions = make(map[string]*spec.SecurityScheme) } if primary.Security == nil { primary.Security = make([]map[string][]string, 0, 10) } if primary.Produces == nil { primary.Produces = make([]string, 0, 10) } if primary.Consumes == nil { primary.Consumes = make([]string, 0, 10) } if primary.Tags == nil { primary.Tags = make([]spec.Tag, 0, 10) } if primary.Schemes == nil { primary.Schemes = make([]string, 0, 10) } if primary.Paths == nil { primary.Paths = &spec.Paths{Paths: make(map[string]spec.PathItem)} } if primary.Paths.Paths == nil { primary.Paths.Paths = make(map[string]spec.PathItem) } if primary.Definitions == nil { primary.Definitions = make(spec.Definitions) } if primary.Parameters == nil { primary.Parameters = make(map[string]spec.Parameter) } if primary.Responses == nil { primary.Responses = make(map[string]spec.Response) } } golang-github-go-openapi-analysis-0.15.0/mixin_test.go000066400000000000000000000103261332100444700227130ustar00rootroot00000000000000// Copyright 2015 go-swagger maintainers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package analysis import ( "testing" ) const ( widgetFile = "fixtures/widget-crud.yml" fooFile = "fixtures/foo-crud.yml" barFile = "fixtures/bar-crud.yml" noPathsFile = "fixtures/no-paths.yml" emptyPathsFile = "fixtures/empty-paths.json" securityFile = "fixtures/securitydef.yml" otherMixin = "fixtures/other-mixin.yml" ) func TestMixin(t *testing.T) { primary, err := loadSpec(widgetFile) if err != nil { t.Fatalf("Could not load '%v': %v\n", widgetFile, err) } mixin1, err := loadSpec(fooFile) if err != nil { t.Fatalf("Could not load '%v': %v\n", fooFile, err) } mixin2, err := loadSpec(barFile) if err != nil { t.Fatalf("Could not load '%v': %v\n", barFile, err) } mixin3, err := loadSpec(noPathsFile) if err != nil { t.Fatalf("Could not load '%v': %v\n", noPathsFile, err) } mixin4, err := loadSpec(securityFile) if err != nil { t.Fatalf("Could not load '%v': %v\n", securityFile, err) } mixin5, err := loadSpec(otherMixin) if err != nil { t.Fatalf("Could not load '%v': %v\n", otherMixin, err) } collisions := Mixin(primary, mixin1, mixin2, mixin3, mixin4, mixin5) if len(collisions) != 19 { t.Errorf("TestMixin: Expected 19 collisions, got %v\n%v", len(collisions), collisions) } if len(primary.Paths.Paths) != 7 { t.Errorf("TestMixin: Expected 7 paths in merged, got %v\n", len(primary.Paths.Paths)) } if len(primary.Definitions) != 8 { t.Errorf("TestMixin: Expected 8 definitions in merged, got %v\n", len(primary.Definitions)) } if len(primary.Parameters) != 4 { t.Errorf("TestMixin: Expected 4 top level parameters in merged, got %v\n", len(primary.Parameters)) } if len(primary.Responses) != 2 { t.Errorf("TestMixin: Expected 2 top level responses in merged, got %v\n", len(primary.Responses)) } if len(primary.SecurityDefinitions) != 5 { t.Errorf("TestMixin: Expected 5 top level SecurityDefinitions in merged, got %v\n", len(primary.SecurityDefinitions)) } if len(primary.Security) != 3 { t.Errorf("TestMixin: Expected 3 top level Security requirements in merged, got %v\n", len(primary.Security)) } if len(primary.Tags) != 3 { t.Errorf("TestMixin: Expected 3 top level tags merged, got %v\n", len(primary.Security)) } if len(primary.Schemes) != 2 { t.Errorf("TestMixin: Expected 2 top level schemes merged, got %v\n", len(primary.Security)) } if len(primary.Consumes) != 2 { t.Errorf("TestMixin: Expected 2 top level Consumers merged, got %v\n", len(primary.Security)) } if len(primary.Produces) != 2 { t.Errorf("TestMixin: Expected 2 top level Producers merged, got %v\n", len(primary.Security)) } // test that adding paths to a primary with no paths works (was NPE) emptyPaths, err := loadSpec(emptyPathsFile) if err != nil { t.Fatalf("Could not load '%v': %v\n", emptyPathsFile, err) } collisions = Mixin(emptyPaths, primary) if len(collisions) != 0 { t.Errorf("TestMixin: Expected 0 collisions, got %v\n%v", len(collisions), collisions) } //bbb, _ := json.MarshalIndent(primary, "", " ") //t.Log(string(bbb)) } func TestMixinFromNilPath(t *testing.T) { primary, err := loadSpec(otherMixin) if err != nil { t.Fatalf("Could not load '%v': %v\n", otherMixin, err) } mixin1, err := loadSpec(widgetFile) if err != nil { t.Fatalf("Could not load '%v': %v\n", widgetFile, err) } collisions := Mixin(primary, mixin1) if len(collisions) != 1 { t.Errorf("TestMixin: Expected 1 collisions, got %v\n%v", len(collisions), collisions) } if len(primary.Paths.Paths) != 3 { t.Errorf("TestMixin: Expected 3 paths in merged, got %v\n", len(primary.Paths.Paths)) } //bbb, _ := json.MarshalIndent(primary.Paths.Paths, "", " ") //t.Log(string(bbb)) } golang-github-go-openapi-analysis-0.15.0/schema.go000066400000000000000000000140201332100444700217630ustar00rootroot00000000000000package analysis import ( "github.com/go-openapi/spec" "github.com/go-openapi/strfmt" ) // SchemaOpts configures the schema analyzer type SchemaOpts struct { Schema *spec.Schema Root interface{} BasePath string _ struct{} } // Schema analysis, will classify the schema according to known // patterns. func Schema(opts SchemaOpts) (*AnalyzedSchema, error) { a := &AnalyzedSchema{ schema: opts.Schema, root: opts.Root, basePath: opts.BasePath, } a.initializeFlags() a.inferKnownType() a.inferEnum() a.inferBaseType() if err := a.inferMap(); err != nil { return nil, err } if err := a.inferArray(); err != nil { return nil, err } if err := a.inferTuple(); err != nil { // NOTE(fredbi): currently, inferTuple() never returns an error return nil, err } if err := a.inferFromRef(); err != nil { return nil, err } a.inferSimpleSchema() return a, nil } // AnalyzedSchema indicates what the schema represents type AnalyzedSchema struct { schema *spec.Schema root interface{} basePath string hasProps bool hasAllOf bool hasItems bool hasAdditionalProps bool hasAdditionalItems bool hasRef bool IsKnownType bool IsSimpleSchema bool IsArray bool IsSimpleArray bool IsMap bool IsSimpleMap bool IsExtendedObject bool IsTuple bool IsTupleWithExtra bool IsBaseType bool IsEnum bool } // Inherits copies value fields from other onto this schema func (a *AnalyzedSchema) inherits(other *AnalyzedSchema) { if other == nil { return } a.hasProps = other.hasProps a.hasAllOf = other.hasAllOf a.hasItems = other.hasItems a.hasAdditionalItems = other.hasAdditionalItems a.hasAdditionalProps = other.hasAdditionalProps a.hasRef = other.hasRef a.IsKnownType = other.IsKnownType a.IsSimpleSchema = other.IsSimpleSchema a.IsArray = other.IsArray a.IsSimpleArray = other.IsSimpleArray a.IsMap = other.IsMap a.IsSimpleMap = other.IsSimpleMap a.IsExtendedObject = other.IsExtendedObject a.IsTuple = other.IsTuple a.IsTupleWithExtra = other.IsTupleWithExtra a.IsBaseType = other.IsBaseType a.IsEnum = other.IsEnum } func (a *AnalyzedSchema) inferFromRef() error { if a.hasRef { sch := new(spec.Schema) sch.Ref = a.schema.Ref err := spec.ExpandSchema(sch, a.root, nil) if err != nil { return err } if sch != nil { // NOTE(fredbi): currently the only cause for errors in // unresolved ref. Since spec.ExpandSchema() expands the // schema recursively, there is no chance to get there, // until we add more causes for error in this schema analysis. rsch, err := Schema(SchemaOpts{ Schema: sch, Root: a.root, BasePath: a.basePath, }) if err != nil { return err } a.inherits(rsch) } } return nil } func (a *AnalyzedSchema) inferSimpleSchema() { a.IsSimpleSchema = a.IsKnownType || a.IsSimpleArray || a.IsSimpleMap } func (a *AnalyzedSchema) inferKnownType() { tpe := a.schema.Type format := a.schema.Format a.IsKnownType = tpe.Contains("boolean") || tpe.Contains("integer") || tpe.Contains("number") || tpe.Contains("string") || (format != "" && strfmt.Default.ContainsName(format)) || (a.isObjectType() && !a.hasProps && !a.hasAllOf && !a.hasAdditionalProps && !a.hasAdditionalItems) } func (a *AnalyzedSchema) inferMap() error { if a.isObjectType() { hasExtra := a.hasProps || a.hasAllOf a.IsMap = a.hasAdditionalProps && !hasExtra a.IsExtendedObject = a.hasAdditionalProps && hasExtra if a.IsMap { if a.schema.AdditionalProperties.Schema != nil { msch, err := Schema(SchemaOpts{ Schema: a.schema.AdditionalProperties.Schema, Root: a.root, BasePath: a.basePath, }) if err != nil { return err } a.IsSimpleMap = msch.IsSimpleSchema } else if a.schema.AdditionalProperties.Allows { a.IsSimpleMap = true } } } return nil } func (a *AnalyzedSchema) inferArray() error { // an array has Items defined as an object schema, otherwise we qualify this JSON array as a tuple // (yes, even if the Items array contains only one element). // arrays in JSON schema may be unrestricted (i.e no Items specified). // Note that arrays in Swagger MUST have Items. Nonetheless, we analyze unrestricted arrays. // // NOTE: the spec package misses the distinction between: // items: [] and items: {}, so we consider both arrays here. a.IsArray = a.isArrayType() && (a.schema.Items == nil || a.schema.Items.Schemas == nil) if a.IsArray && a.hasItems { if a.schema.Items.Schema != nil { itsch, err := Schema(SchemaOpts{ Schema: a.schema.Items.Schema, Root: a.root, BasePath: a.basePath, }) if err != nil { return err } a.IsSimpleArray = itsch.IsSimpleSchema } } if a.IsArray && !a.hasItems { a.IsSimpleArray = true } return nil } func (a *AnalyzedSchema) inferTuple() error { tuple := a.hasItems && a.schema.Items.Schemas != nil a.IsTuple = tuple && !a.hasAdditionalItems a.IsTupleWithExtra = tuple && a.hasAdditionalItems return nil } func (a *AnalyzedSchema) inferBaseType() { if a.isObjectType() { a.IsBaseType = a.schema.Discriminator != "" } } func (a *AnalyzedSchema) inferEnum() { a.IsEnum = len(a.schema.Enum) > 0 } func (a *AnalyzedSchema) initializeFlags() { a.hasProps = len(a.schema.Properties) > 0 a.hasAllOf = len(a.schema.AllOf) > 0 a.hasRef = a.schema.Ref.String() != "" a.hasItems = a.schema.Items != nil && (a.schema.Items.Schema != nil || len(a.schema.Items.Schemas) > 0) a.hasAdditionalProps = a.schema.AdditionalProperties != nil && (a.schema.AdditionalProperties != nil || a.schema.AdditionalProperties.Allows) a.hasAdditionalItems = a.schema.AdditionalItems != nil && (a.schema.AdditionalItems.Schema != nil || a.schema.AdditionalItems.Allows) } func (a *AnalyzedSchema) isObjectType() bool { return !a.hasRef && (a.schema.Type == nil || a.schema.Type.Contains("") || a.schema.Type.Contains("object")) } func (a *AnalyzedSchema) isArrayType() bool { return !a.hasRef && (a.schema.Type != nil && a.schema.Type.Contains("array")) } golang-github-go-openapi-analysis-0.15.0/schema_test.go000066400000000000000000000270611332100444700230330ustar00rootroot00000000000000package analysis import ( "encoding/json" "fmt" "path" "path/filepath" "testing" "net/http" "net/http/httptest" "github.com/go-openapi/spec" "github.com/stretchr/testify/assert" ) var knownSchemas = []*spec.Schema{ spec.BoolProperty(), // 0 spec.StringProperty(), // 1 spec.Int8Property(), // 2 spec.Int16Property(), // 3 spec.Int32Property(), // 4 spec.Int64Property(), // 5 spec.Float32Property(), // 6 spec.Float64Property(), // 7 spec.DateProperty(), // 8 spec.DateTimeProperty(), // 9 (&spec.Schema{}), // 10 (&spec.Schema{}).Typed("object", ""), // 11 (&spec.Schema{}).Typed("", ""), // 12 (&spec.Schema{}).Typed("", "uuid"), // 13 } func newCObj() *spec.Schema { return (&spec.Schema{}).Typed("object", "").SetProperty("id", *spec.Int64Property()) } var complexObject = newCObj() var complexSchemas = []*spec.Schema{ complexObject, spec.ArrayProperty(complexObject), spec.MapProperty(complexObject), } func knownRefs(base string) []spec.Ref { urls := []string{"bool", "string", "integer", "float", "date", "object", "format"} result := make([]spec.Ref, 0, len(urls)) for _, u := range urls { result = append(result, spec.MustCreateRef(fmt.Sprintf("%s/%s", base, path.Join("known", u)))) } return result } func complexRefs(base string) []spec.Ref { urls := []string{"object", "array", "map"} result := make([]spec.Ref, 0, len(urls)) for _, u := range urls { result = append(result, spec.MustCreateRef(fmt.Sprintf("%s/%s", base, path.Join("complex", u)))) } return result } func refServer() *httptest.Server { mux := http.NewServeMux() mux.Handle("/known/bool", schemaHandler(knownSchemas[0])) mux.Handle("/known/string", schemaHandler(knownSchemas[1])) mux.Handle("/known/integer", schemaHandler(knownSchemas[5])) mux.Handle("/known/float", schemaHandler(knownSchemas[6])) mux.Handle("/known/date", schemaHandler(knownSchemas[8])) mux.Handle("/known/object", schemaHandler(knownSchemas[11])) mux.Handle("/known/format", schemaHandler(knownSchemas[13])) mux.Handle("/complex/object", schemaHandler(complexSchemas[0])) mux.Handle("/complex/array", schemaHandler(complexSchemas[1])) mux.Handle("/complex/map", schemaHandler(complexSchemas[2])) return httptest.NewServer(mux) } func refSchema(ref spec.Ref) *spec.Schema { return &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}} } func schemaHandler(schema *spec.Schema) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { writeJSON(w, schema) }) } func writeJSON(w http.ResponseWriter, data interface{}) { w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) enc := json.NewEncoder(w) if err := enc.Encode(data); err != nil { panic(err) } } func TestSchemaAnalysis_KnownTypes(t *testing.T) { for i, v := range knownSchemas { sch, err := Schema(SchemaOpts{Schema: v}) if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { assert.True(t, sch.IsKnownType, "item at %d should be a known type", i) } } for i, v := range complexSchemas { sch, err := Schema(SchemaOpts{Schema: v}) if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { assert.False(t, sch.IsKnownType, "item at %d should not be a known type", i) } } serv := refServer() defer serv.Close() for i, ref := range knownRefs(serv.URL) { sch, err := Schema(SchemaOpts{Schema: refSchema(ref)}) if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { assert.True(t, sch.IsKnownType, "item at %d should be a known type", i) } } for i, ref := range complexRefs(serv.URL) { sch, err := Schema(SchemaOpts{Schema: refSchema(ref)}) if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { assert.False(t, sch.IsKnownType, "item at %d should not be a known type", i) } } } func TestSchemaAnalysis_Array(t *testing.T) { for i, v := range append(knownSchemas, (&spec.Schema{}).Typed("array", "")) { sch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(v)}) if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { assert.True(t, sch.IsArray, "item at %d should be an array type", i) assert.True(t, sch.IsSimpleArray, "item at %d should be a simple array type", i) } } for i, v := range complexSchemas { sch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(v)}) if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { assert.True(t, sch.IsArray, "item at %d should be an array type", i) assert.False(t, sch.IsSimpleArray, "item at %d should not be a simple array type", i) } } serv := refServer() defer serv.Close() for i, ref := range knownRefs(serv.URL) { sch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(refSchema(ref))}) if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { assert.True(t, sch.IsArray, "item at %d should be an array type", i) assert.True(t, sch.IsSimpleArray, "item at %d should be a simple array type", i) } } for i, ref := range complexRefs(serv.URL) { sch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(refSchema(ref))}) if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { assert.False(t, sch.IsKnownType, "item at %d should not be a known type", i) assert.True(t, sch.IsArray, "item at %d should be an array type", i) assert.False(t, sch.IsSimpleArray, "item at %d should not be a simple array type", i) } } // edge case: unrestricted array (beyond Swagger) at := spec.ArrayProperty(nil) at.Items = nil sch, err := Schema(SchemaOpts{Schema: at}) if assert.NoError(t, err) { assert.True(t, sch.IsArray) assert.False(t, sch.IsTuple) assert.False(t, sch.IsKnownType) assert.True(t, sch.IsSimpleSchema) } // unrestricted array with explicit empty schema at = spec.ArrayProperty(nil) at.Items = &spec.SchemaOrArray{} sch, err = Schema(SchemaOpts{Schema: at}) if assert.NoError(t, err) { assert.True(t, sch.IsArray) assert.False(t, sch.IsTuple) assert.False(t, sch.IsKnownType) assert.True(t, sch.IsSimpleSchema) } } func TestSchemaAnalysis_Map(t *testing.T) { for i, v := range append(knownSchemas, spec.MapProperty(nil)) { sch, err := Schema(SchemaOpts{Schema: spec.MapProperty(v)}) if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { assert.True(t, sch.IsMap, "item at %d should be a map type", i) assert.True(t, sch.IsSimpleMap, "item at %d should be a simple map type", i) } } for i, v := range complexSchemas { sch, err := Schema(SchemaOpts{Schema: spec.MapProperty(v)}) if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { assert.True(t, sch.IsMap, "item at %d should be a map type", i) assert.False(t, sch.IsSimpleMap, "item at %d should not be a simple map type", i) } } } func TestSchemaAnalysis_ExtendedObject(t *testing.T) { for i, v := range knownSchemas { wex := spec.MapProperty(v).SetProperty("name", *spec.StringProperty()) sch, err := Schema(SchemaOpts{Schema: wex}) if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { assert.True(t, sch.IsExtendedObject, "item at %d should be an extended map object type", i) assert.False(t, sch.IsMap, "item at %d should not be a map type", i) assert.False(t, sch.IsSimpleMap, "item at %d should not be a simple map type", i) } } } func TestSchemaAnalysis_Tuple(t *testing.T) { at := spec.ArrayProperty(nil) at.Items = &spec.SchemaOrArray{} at.Items.Schemas = append(at.Items.Schemas, *spec.StringProperty(), *spec.Int64Property()) sch, err := Schema(SchemaOpts{Schema: at}) if assert.NoError(t, err) { assert.True(t, sch.IsTuple) assert.False(t, sch.IsTupleWithExtra) assert.False(t, sch.IsKnownType) assert.False(t, sch.IsSimpleSchema) } // edge case: tuple with a single element at.Items = &spec.SchemaOrArray{} at.Items.Schemas = append(at.Items.Schemas, *spec.StringProperty()) sch, err = Schema(SchemaOpts{Schema: at}) if assert.NoError(t, err) { assert.True(t, sch.IsTuple) assert.False(t, sch.IsTupleWithExtra) assert.False(t, sch.IsKnownType) assert.False(t, sch.IsSimpleSchema) } } func TestSchemaAnalysis_TupleWithExtra(t *testing.T) { at := spec.ArrayProperty(nil) at.Items = &spec.SchemaOrArray{} at.Items.Schemas = append(at.Items.Schemas, *spec.StringProperty(), *spec.Int64Property()) at.AdditionalItems = &spec.SchemaOrBool{Allows: true} at.AdditionalItems.Schema = spec.Int32Property() sch, err := Schema(SchemaOpts{Schema: at}) if assert.NoError(t, err) { assert.False(t, sch.IsTuple) assert.True(t, sch.IsTupleWithExtra) assert.False(t, sch.IsKnownType) assert.False(t, sch.IsSimpleSchema) } } func TestSchemaAnalysis_BaseType(t *testing.T) { cl := (&spec.Schema{}).Typed("object", "").SetProperty("type", *spec.StringProperty()).WithDiscriminator("type") sch, err := Schema(SchemaOpts{Schema: cl}) if assert.NoError(t, err) { assert.True(t, sch.IsBaseType) assert.False(t, sch.IsKnownType) assert.False(t, sch.IsSimpleSchema) } } func TestSchemaAnalysis_SimpleSchema(t *testing.T) { for i, v := range append(knownSchemas, spec.ArrayProperty(nil), spec.MapProperty(nil)) { sch, err := Schema(SchemaOpts{Schema: v}) if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { assert.True(t, sch.IsSimpleSchema, "item at %d should be a simple schema", i) } asch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(v)}) if assert.NoError(t, err, "failed to analyze array schema at %d: %v", i, err) { assert.True(t, asch.IsSimpleSchema, "array item at %d should be a simple schema", i) } msch, err := Schema(SchemaOpts{Schema: spec.MapProperty(v)}) if assert.NoError(t, err, "failed to analyze map schema at %d: %v", i, err) { assert.True(t, msch.IsSimpleSchema, "map item at %d should be a simple schema", i) } } for i, v := range complexSchemas { sch, err := Schema(SchemaOpts{Schema: v}) if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { assert.False(t, sch.IsSimpleSchema, "item at %d should not be a simple schema", i) } } } func TestSchemaAnalys_InvalidSchema(t *testing.T) { // explore error cases in schema analysis: // the only cause for failure is a wrong $ref at some place bp := filepath.Join("fixtures", "bugs", "1602", "other-invalid-pointers.yaml") sp := loadOrFail(t, bp) // invalid ref not detected (no digging further) def := sp.Definitions["invalidRefInObject"] _, err := Schema(SchemaOpts{Schema: &def, Root: sp, BasePath: bp}) assert.NoError(t, err, "did not expect an error here, in spite of the underlying invalid $ref") def = sp.Definitions["invalidRefInTuple"] _, err = Schema(SchemaOpts{Schema: &def, Root: sp, BasePath: bp}) assert.NoError(t, err, "did not expect an error here, in spite of the underlying invalid $ref") // invalid ref detected (digging) schema := refSchema(spec.MustCreateRef("#/definitions/noWhere")) _, err = Schema(SchemaOpts{Schema: schema, Root: sp, BasePath: bp}) assert.Error(t, err, "expected an error here") def = sp.Definitions["invalidRefInMap"] _, err = Schema(SchemaOpts{Schema: &def, Root: sp, BasePath: bp}) assert.Error(t, err, "expected an error here") def = sp.Definitions["invalidRefInArray"] _, err = Schema(SchemaOpts{Schema: &def, Root: sp, BasePath: bp}) assert.Error(t, err, "expected an error here") def = sp.Definitions["indirectToInvalidRef"] _, err = Schema(SchemaOpts{Schema: &def, Root: sp, BasePath: bp}) assert.Error(t, err, "expected an error here") //bbb, _ := json.MarshalIndent(def, "", " ") //log.Printf(string(bbb)) }