Merge branch 'main' of https://bdgit.educoder.net/psbipctq7/lvgl
commit
fc68d6759c
@ -0,0 +1,74 @@
|
||||
# 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/
|
||||
@ -0,0 +1,202 @@
|
||||
|
||||
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.
|
||||
@ -0,0 +1,19 @@
|
||||
# gojsonpointer [](https://github.com/go-openapi/jsonpointer/actions?query=workflow%3A"go+test") [](https://codecov.io/gh/go-openapi/jsonpointer)
|
||||
|
||||
[](https://slackin.goswagger.io)
|
||||
[](https://raw.githubusercontent.com/go-openapi/jsonpointer/master/LICENSE)
|
||||
[](https://pkg.go.dev/github.com/go-openapi/jsonpointer)
|
||||
[](https://goreportcard.com/report/github.com/go-openapi/jsonpointer)
|
||||
|
||||
An implementation of JSON Pointer - Go language
|
||||
|
||||
## Status
|
||||
Completed YES
|
||||
|
||||
Tested YES
|
||||
|
||||
## References
|
||||
http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07
|
||||
|
||||
### Note
|
||||
The 4.Evaluation part of the previous reference, starting with 'If the currently referenced value is a JSON array, the reference token MUST contain either...' is not implemented.
|
||||
@ -0,0 +1,531 @@
|
||||
// Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
|
||||
//
|
||||
// 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.
|
||||
|
||||
// author sigu-399
|
||||
// author-github https://github.com/sigu-399
|
||||
// author-mail sigu.399@gmail.com
|
||||
//
|
||||
// repository-name jsonpointer
|
||||
// repository-desc An implementation of JSON Pointer - Go language
|
||||
//
|
||||
// description Main and unique file.
|
||||
//
|
||||
// created 25-02-2013
|
||||
|
||||
package jsonpointer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
const (
|
||||
emptyPointer = ``
|
||||
pointerSeparator = `/`
|
||||
|
||||
invalidStart = `JSON pointer must be empty or start with a "` + pointerSeparator
|
||||
notFound = `Can't find the pointer in the document`
|
||||
)
|
||||
|
||||
var jsonPointableType = reflect.TypeOf(new(JSONPointable)).Elem()
|
||||
var jsonSetableType = reflect.TypeOf(new(JSONSetable)).Elem()
|
||||
|
||||
// JSONPointable is an interface for structs to implement when they need to customize the
|
||||
// json pointer process
|
||||
type JSONPointable interface {
|
||||
JSONLookup(string) (any, error)
|
||||
}
|
||||
|
||||
// JSONSetable is an interface for structs to implement when they need to customize the
|
||||
// json pointer process
|
||||
type JSONSetable interface {
|
||||
JSONSet(string, any) error
|
||||
}
|
||||
|
||||
// New creates a new json pointer for the given string
|
||||
func New(jsonPointerString string) (Pointer, error) {
|
||||
|
||||
var p Pointer
|
||||
err := p.parse(jsonPointerString)
|
||||
return p, err
|
||||
|
||||
}
|
||||
|
||||
// Pointer the json pointer reprsentation
|
||||
type Pointer struct {
|
||||
referenceTokens []string
|
||||
}
|
||||
|
||||
// "Constructor", parses the given string JSON pointer
|
||||
func (p *Pointer) parse(jsonPointerString string) error {
|
||||
|
||||
var err error
|
||||
|
||||
if jsonPointerString != emptyPointer {
|
||||
if !strings.HasPrefix(jsonPointerString, pointerSeparator) {
|
||||
err = errors.New(invalidStart)
|
||||
} else {
|
||||
referenceTokens := strings.Split(jsonPointerString, pointerSeparator)
|
||||
p.referenceTokens = append(p.referenceTokens, referenceTokens[1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Get uses the pointer to retrieve a value from a JSON document
|
||||
func (p *Pointer) Get(document any) (any, reflect.Kind, error) {
|
||||
return p.get(document, swag.DefaultJSONNameProvider)
|
||||
}
|
||||
|
||||
// Set uses the pointer to set a value from a JSON document
|
||||
func (p *Pointer) Set(document any, value any) (any, error) {
|
||||
return document, p.set(document, value, swag.DefaultJSONNameProvider)
|
||||
}
|
||||
|
||||
// GetForToken gets a value for a json pointer token 1 level deep
|
||||
func GetForToken(document any, decodedToken string) (any, reflect.Kind, error) {
|
||||
return getSingleImpl(document, decodedToken, swag.DefaultJSONNameProvider)
|
||||
}
|
||||
|
||||
// SetForToken gets a value for a json pointer token 1 level deep
|
||||
func SetForToken(document any, decodedToken string, value any) (any, error) {
|
||||
return document, setSingleImpl(document, value, decodedToken, swag.DefaultJSONNameProvider)
|
||||
}
|
||||
|
||||
func isNil(input any) bool {
|
||||
if input == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
kind := reflect.TypeOf(input).Kind()
|
||||
switch kind { //nolint:exhaustive
|
||||
case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan:
|
||||
return reflect.ValueOf(input).IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func getSingleImpl(node any, decodedToken string, nameProvider *swag.NameProvider) (any, reflect.Kind, error) {
|
||||
rValue := reflect.Indirect(reflect.ValueOf(node))
|
||||
kind := rValue.Kind()
|
||||
if isNil(node) {
|
||||
return nil, kind, fmt.Errorf("nil value has not field %q", decodedToken)
|
||||
}
|
||||
|
||||
switch typed := node.(type) {
|
||||
case JSONPointable:
|
||||
r, err := typed.JSONLookup(decodedToken)
|
||||
if err != nil {
|
||||
return nil, kind, err
|
||||
}
|
||||
return r, kind, nil
|
||||
case *any: // case of a pointer to interface, that is not resolved by reflect.Indirect
|
||||
return getSingleImpl(*typed, decodedToken, nameProvider)
|
||||
}
|
||||
|
||||
switch kind { //nolint:exhaustive
|
||||
case reflect.Struct:
|
||||
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
|
||||
if !ok {
|
||||
return nil, kind, fmt.Errorf("object has no field %q", decodedToken)
|
||||
}
|
||||
fld := rValue.FieldByName(nm)
|
||||
return fld.Interface(), kind, nil
|
||||
|
||||
case reflect.Map:
|
||||
kv := reflect.ValueOf(decodedToken)
|
||||
mv := rValue.MapIndex(kv)
|
||||
|
||||
if mv.IsValid() {
|
||||
return mv.Interface(), kind, nil
|
||||
}
|
||||
return nil, kind, fmt.Errorf("object has no key %q", decodedToken)
|
||||
|
||||
case reflect.Slice:
|
||||
tokenIndex, err := strconv.Atoi(decodedToken)
|
||||
if err != nil {
|
||||
return nil, kind, err
|
||||
}
|
||||
sLength := rValue.Len()
|
||||
if tokenIndex < 0 || tokenIndex >= sLength {
|
||||
return nil, kind, fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength-1, tokenIndex)
|
||||
}
|
||||
|
||||
elem := rValue.Index(tokenIndex)
|
||||
return elem.Interface(), kind, nil
|
||||
|
||||
default:
|
||||
return nil, kind, fmt.Errorf("invalid token reference %q", decodedToken)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func setSingleImpl(node, data any, decodedToken string, nameProvider *swag.NameProvider) error {
|
||||
rValue := reflect.Indirect(reflect.ValueOf(node))
|
||||
|
||||
if ns, ok := node.(JSONSetable); ok { // pointer impl
|
||||
return ns.JSONSet(decodedToken, data)
|
||||
}
|
||||
|
||||
if rValue.Type().Implements(jsonSetableType) {
|
||||
return node.(JSONSetable).JSONSet(decodedToken, data)
|
||||
}
|
||||
|
||||
switch rValue.Kind() { //nolint:exhaustive
|
||||
case reflect.Struct:
|
||||
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
|
||||
if !ok {
|
||||
return fmt.Errorf("object has no field %q", decodedToken)
|
||||
}
|
||||
fld := rValue.FieldByName(nm)
|
||||
if fld.IsValid() {
|
||||
fld.Set(reflect.ValueOf(data))
|
||||
}
|
||||
return nil
|
||||
|
||||
case reflect.Map:
|
||||
kv := reflect.ValueOf(decodedToken)
|
||||
rValue.SetMapIndex(kv, reflect.ValueOf(data))
|
||||
return nil
|
||||
|
||||
case reflect.Slice:
|
||||
tokenIndex, err := strconv.Atoi(decodedToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sLength := rValue.Len()
|
||||
if tokenIndex < 0 || tokenIndex >= sLength {
|
||||
return fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength, tokenIndex)
|
||||
}
|
||||
|
||||
elem := rValue.Index(tokenIndex)
|
||||
if !elem.CanSet() {
|
||||
return fmt.Errorf("can't set slice index %s to %v", decodedToken, data)
|
||||
}
|
||||
elem.Set(reflect.ValueOf(data))
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid token reference %q", decodedToken)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (p *Pointer) get(node any, nameProvider *swag.NameProvider) (any, reflect.Kind, error) {
|
||||
|
||||
if nameProvider == nil {
|
||||
nameProvider = swag.DefaultJSONNameProvider
|
||||
}
|
||||
|
||||
kind := reflect.Invalid
|
||||
|
||||
// Full document when empty
|
||||
if len(p.referenceTokens) == 0 {
|
||||
return node, kind, nil
|
||||
}
|
||||
|
||||
for _, token := range p.referenceTokens {
|
||||
|
||||
decodedToken := Unescape(token)
|
||||
|
||||
r, knd, err := getSingleImpl(node, decodedToken, nameProvider)
|
||||
if err != nil {
|
||||
return nil, knd, err
|
||||
}
|
||||
node = r
|
||||
}
|
||||
|
||||
rValue := reflect.ValueOf(node)
|
||||
kind = rValue.Kind()
|
||||
|
||||
return node, kind, nil
|
||||
}
|
||||
|
||||
func (p *Pointer) set(node, data any, nameProvider *swag.NameProvider) error {
|
||||
knd := reflect.ValueOf(node).Kind()
|
||||
|
||||
if knd != reflect.Ptr && knd != reflect.Struct && knd != reflect.Map && knd != reflect.Slice && knd != reflect.Array {
|
||||
return errors.New("only structs, pointers, maps and slices are supported for setting values")
|
||||
}
|
||||
|
||||
if nameProvider == nil {
|
||||
nameProvider = swag.DefaultJSONNameProvider
|
||||
}
|
||||
|
||||
// Full document when empty
|
||||
if len(p.referenceTokens) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
lastI := len(p.referenceTokens) - 1
|
||||
for i, token := range p.referenceTokens {
|
||||
isLastToken := i == lastI
|
||||
decodedToken := Unescape(token)
|
||||
|
||||
if isLastToken {
|
||||
|
||||
return setSingleImpl(node, data, decodedToken, nameProvider)
|
||||
}
|
||||
|
||||
rValue := reflect.Indirect(reflect.ValueOf(node))
|
||||
kind := rValue.Kind()
|
||||
|
||||
if rValue.Type().Implements(jsonPointableType) {
|
||||
r, err := node.(JSONPointable).JSONLookup(decodedToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fld := reflect.ValueOf(r)
|
||||
if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Ptr {
|
||||
node = fld.Addr().Interface()
|
||||
continue
|
||||
}
|
||||
node = r
|
||||
continue
|
||||
}
|
||||
|
||||
switch kind { //nolint:exhaustive
|
||||
case reflect.Struct:
|
||||
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
|
||||
if !ok {
|
||||
return fmt.Errorf("object has no field %q", decodedToken)
|
||||
}
|
||||
fld := rValue.FieldByName(nm)
|
||||
if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Ptr {
|
||||
node = fld.Addr().Interface()
|
||||
continue
|
||||
}
|
||||
node = fld.Interface()
|
||||
|
||||
case reflect.Map:
|
||||
kv := reflect.ValueOf(decodedToken)
|
||||
mv := rValue.MapIndex(kv)
|
||||
|
||||
if !mv.IsValid() {
|
||||
return fmt.Errorf("object has no key %q", decodedToken)
|
||||
}
|
||||
if mv.CanAddr() && mv.Kind() != reflect.Interface && mv.Kind() != reflect.Map && mv.Kind() != reflect.Slice && mv.Kind() != reflect.Ptr {
|
||||
node = mv.Addr().Interface()
|
||||
continue
|
||||
}
|
||||
node = mv.Interface()
|
||||
|
||||
case reflect.Slice:
|
||||
tokenIndex, err := strconv.Atoi(decodedToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sLength := rValue.Len()
|
||||
if tokenIndex < 0 || tokenIndex >= sLength {
|
||||
return fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength, tokenIndex)
|
||||
}
|
||||
|
||||
elem := rValue.Index(tokenIndex)
|
||||
if elem.CanAddr() && elem.Kind() != reflect.Interface && elem.Kind() != reflect.Map && elem.Kind() != reflect.Slice && elem.Kind() != reflect.Ptr {
|
||||
node = elem.Addr().Interface()
|
||||
continue
|
||||
}
|
||||
node = elem.Interface()
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid token reference %q", decodedToken)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodedTokens returns the decoded tokens
|
||||
func (p *Pointer) DecodedTokens() []string {
|
||||
result := make([]string, 0, len(p.referenceTokens))
|
||||
for _, t := range p.referenceTokens {
|
||||
result = append(result, Unescape(t))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// IsEmpty returns true if this is an empty json pointer
|
||||
// this indicates that it points to the root document
|
||||
func (p *Pointer) IsEmpty() bool {
|
||||
return len(p.referenceTokens) == 0
|
||||
}
|
||||
|
||||
// Pointer to string representation function
|
||||
func (p *Pointer) String() string {
|
||||
|
||||
if len(p.referenceTokens) == 0 {
|
||||
return emptyPointer
|
||||
}
|
||||
|
||||
pointerString := pointerSeparator + strings.Join(p.referenceTokens, pointerSeparator)
|
||||
|
||||
return pointerString
|
||||
}
|
||||
|
||||
func (p *Pointer) Offset(document string) (int64, error) {
|
||||
dec := json.NewDecoder(strings.NewReader(document))
|
||||
var offset int64
|
||||
for _, ttk := range p.DecodedTokens() {
|
||||
tk, err := dec.Token()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch tk := tk.(type) {
|
||||
case json.Delim:
|
||||
switch tk {
|
||||
case '{':
|
||||
offset, err = offsetSingleObject(dec, ttk)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case '[':
|
||||
offset, err = offsetSingleArray(dec, ttk)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid token %#v", tk)
|
||||
}
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid token %#v", tk)
|
||||
}
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
func offsetSingleObject(dec *json.Decoder, decodedToken string) (int64, error) {
|
||||
for dec.More() {
|
||||
offset := dec.InputOffset()
|
||||
tk, err := dec.Token()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch tk := tk.(type) {
|
||||
case json.Delim:
|
||||
switch tk {
|
||||
case '{':
|
||||
if err = drainSingle(dec); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case '[':
|
||||
if err = drainSingle(dec); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
case string:
|
||||
if tk == decodedToken {
|
||||
return offset, nil
|
||||
}
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid token %#v", tk)
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("token reference %q not found", decodedToken)
|
||||
}
|
||||
|
||||
func offsetSingleArray(dec *json.Decoder, decodedToken string) (int64, error) {
|
||||
idx, err := strconv.Atoi(decodedToken)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("token reference %q is not a number: %v", decodedToken, err)
|
||||
}
|
||||
var i int
|
||||
for i = 0; i < idx && dec.More(); i++ {
|
||||
tk, err := dec.Token()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if delim, isDelim := tk.(json.Delim); isDelim {
|
||||
switch delim {
|
||||
case '{':
|
||||
if err = drainSingle(dec); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case '[':
|
||||
if err = drainSingle(dec); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !dec.More() {
|
||||
return 0, fmt.Errorf("token reference %q not found", decodedToken)
|
||||
}
|
||||
return dec.InputOffset(), nil
|
||||
}
|
||||
|
||||
// drainSingle drains a single level of object or array.
|
||||
// The decoder has to guarantee the beginning delim (i.e. '{' or '[') has been consumed.
|
||||
func drainSingle(dec *json.Decoder) error {
|
||||
for dec.More() {
|
||||
tk, err := dec.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if delim, isDelim := tk.(json.Delim); isDelim {
|
||||
switch delim {
|
||||
case '{':
|
||||
if err = drainSingle(dec); err != nil {
|
||||
return err
|
||||
}
|
||||
case '[':
|
||||
if err = drainSingle(dec); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Consumes the ending delim
|
||||
if _, err := dec.Token(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Specific JSON pointer encoding here
|
||||
// ~0 => ~
|
||||
// ~1 => /
|
||||
// ... and vice versa
|
||||
|
||||
const (
|
||||
encRefTok0 = `~0`
|
||||
encRefTok1 = `~1`
|
||||
decRefTok0 = `~`
|
||||
decRefTok1 = `/`
|
||||
)
|
||||
|
||||
// Unescape unescapes a json pointer reference token string to the original representation
|
||||
func Unescape(token string) string {
|
||||
step1 := strings.ReplaceAll(token, encRefTok1, decRefTok1)
|
||||
step2 := strings.ReplaceAll(step1, encRefTok0, decRefTok0)
|
||||
return step2
|
||||
}
|
||||
|
||||
// Escape escapes a pointer reference token string
|
||||
func Escape(token string) string {
|
||||
step1 := strings.ReplaceAll(token, decRefTok0, encRefTok0)
|
||||
step2 := strings.ReplaceAll(step1, decRefTok1, encRefTok1)
|
||||
return step2
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
secrets.yml
|
||||
@ -0,0 +1,61 @@
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
golint:
|
||||
min-confidence: 0
|
||||
gocyclo:
|
||||
min-complexity: 45
|
||||
maligned:
|
||||
suggest-new: true
|
||||
dupl:
|
||||
threshold: 200
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 3
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- maligned
|
||||
- unparam
|
||||
- lll
|
||||
- gochecknoinits
|
||||
- gochecknoglobals
|
||||
- funlen
|
||||
- godox
|
||||
- gocognit
|
||||
- whitespace
|
||||
- wsl
|
||||
- wrapcheck
|
||||
- testpackage
|
||||
- nlreturn
|
||||
- gomnd
|
||||
- exhaustivestruct
|
||||
- goerr113
|
||||
- errorlint
|
||||
- nestif
|
||||
- godot
|
||||
- gofumpt
|
||||
- paralleltest
|
||||
- tparallel
|
||||
- thelper
|
||||
- ifshort
|
||||
- exhaustruct
|
||||
- varnamelen
|
||||
- gci
|
||||
- depguard
|
||||
- errchkjson
|
||||
- inamedparam
|
||||
- nonamedreturns
|
||||
- musttag
|
||||
- ireturn
|
||||
- forcetypeassert
|
||||
- cyclop
|
||||
# deprecated linters
|
||||
- deadcode
|
||||
- interfacer
|
||||
- scopelint
|
||||
- varcheck
|
||||
- structcheck
|
||||
- golint
|
||||
- nosnakecase
|
||||
@ -0,0 +1,74 @@
|
||||
# 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/
|
||||
@ -0,0 +1,202 @@
|
||||
|
||||
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.
|
||||
@ -0,0 +1,19 @@
|
||||
# gojsonreference [](https://github.com/go-openapi/jsonreference/actions?query=workflow%3A"go+test") [](https://codecov.io/gh/go-openapi/jsonreference)
|
||||
|
||||
[](https://slackin.goswagger.io)
|
||||
[](https://raw.githubusercontent.com/go-openapi/jsonreference/master/LICENSE)
|
||||
[](https://pkg.go.dev/github.com/go-openapi/jsonreference)
|
||||
[](https://goreportcard.com/report/github.com/go-openapi/jsonreference)
|
||||
|
||||
An implementation of JSON Reference - Go language
|
||||
|
||||
## Status
|
||||
Feature complete. Stable API
|
||||
|
||||
## Dependencies
|
||||
* https://github.com/go-openapi/jsonpointer
|
||||
|
||||
## References
|
||||
|
||||
* http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07
|
||||
* http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03
|
||||
@ -0,0 +1,69 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHTTPPort = ":80"
|
||||
defaultHTTPSPort = ":443"
|
||||
)
|
||||
|
||||
// Regular expressions used by the normalizations
|
||||
var rxPort = regexp.MustCompile(`(:\d+)/?$`)
|
||||
var rxDupSlashes = regexp.MustCompile(`/{2,}`)
|
||||
|
||||
// NormalizeURL will normalize the specified URL
|
||||
// This was added to replace a previous call to the no longer maintained purell library:
|
||||
// The call that was used looked like the following:
|
||||
//
|
||||
// url.Parse(purell.NormalizeURL(parsed, purell.FlagsSafe|purell.FlagRemoveDuplicateSlashes))
|
||||
//
|
||||
// To explain all that was included in the call above, purell.FlagsSafe was really just the following:
|
||||
// - FlagLowercaseScheme
|
||||
// - FlagLowercaseHost
|
||||
// - FlagRemoveDefaultPort
|
||||
// - FlagRemoveDuplicateSlashes (and this was mixed in with the |)
|
||||
//
|
||||
// This also normalizes the URL into its urlencoded form by removing RawPath and RawFragment.
|
||||
func NormalizeURL(u *url.URL) {
|
||||
lowercaseScheme(u)
|
||||
lowercaseHost(u)
|
||||
removeDefaultPort(u)
|
||||
removeDuplicateSlashes(u)
|
||||
|
||||
u.RawPath = ""
|
||||
u.RawFragment = ""
|
||||
}
|
||||
|
||||
func lowercaseScheme(u *url.URL) {
|
||||
if len(u.Scheme) > 0 {
|
||||
u.Scheme = strings.ToLower(u.Scheme)
|
||||
}
|
||||
}
|
||||
|
||||
func lowercaseHost(u *url.URL) {
|
||||
if len(u.Host) > 0 {
|
||||
u.Host = strings.ToLower(u.Host)
|
||||
}
|
||||
}
|
||||
|
||||
func removeDefaultPort(u *url.URL) {
|
||||
if len(u.Host) > 0 {
|
||||
scheme := strings.ToLower(u.Scheme)
|
||||
u.Host = rxPort.ReplaceAllStringFunc(u.Host, func(val string) string {
|
||||
if (scheme == "http" && val == defaultHTTPPort) || (scheme == "https" && val == defaultHTTPSPort) {
|
||||
return ""
|
||||
}
|
||||
return val
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func removeDuplicateSlashes(u *url.URL) {
|
||||
if len(u.Path) > 0 {
|
||||
u.Path = rxDupSlashes.ReplaceAllString(u.Path, "/")
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,158 @@
|
||||
// Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
|
||||
//
|
||||
// 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.
|
||||
|
||||
// author sigu-399
|
||||
// author-github https://github.com/sigu-399
|
||||
// author-mail sigu.399@gmail.com
|
||||
//
|
||||
// repository-name jsonreference
|
||||
// repository-desc An implementation of JSON Reference - Go language
|
||||
//
|
||||
// description Main and unique file.
|
||||
//
|
||||
// created 26-02-2013
|
||||
|
||||
package jsonreference
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
"github.com/go-openapi/jsonreference/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
fragmentRune = `#`
|
||||
)
|
||||
|
||||
// New creates a new reference for the given string
|
||||
func New(jsonReferenceString string) (Ref, error) {
|
||||
|
||||
var r Ref
|
||||
err := r.parse(jsonReferenceString)
|
||||
return r, err
|
||||
|
||||
}
|
||||
|
||||
// MustCreateRef parses the ref string and panics when it's invalid.
|
||||
// Use the New method for a version that returns an error
|
||||
func MustCreateRef(ref string) Ref {
|
||||
r, err := New(ref)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Ref represents a json reference object
|
||||
type Ref struct {
|
||||
referenceURL *url.URL
|
||||
referencePointer jsonpointer.Pointer
|
||||
|
||||
HasFullURL bool
|
||||
HasURLPathOnly bool
|
||||
HasFragmentOnly bool
|
||||
HasFileScheme bool
|
||||
HasFullFilePath bool
|
||||
}
|
||||
|
||||
// GetURL gets the URL for this reference
|
||||
func (r *Ref) GetURL() *url.URL {
|
||||
return r.referenceURL
|
||||
}
|
||||
|
||||
// GetPointer gets the json pointer for this reference
|
||||
func (r *Ref) GetPointer() *jsonpointer.Pointer {
|
||||
return &r.referencePointer
|
||||
}
|
||||
|
||||
// String returns the best version of the url for this reference
|
||||
func (r *Ref) String() string {
|
||||
|
||||
if r.referenceURL != nil {
|
||||
return r.referenceURL.String()
|
||||
}
|
||||
|
||||
if r.HasFragmentOnly {
|
||||
return fragmentRune + r.referencePointer.String()
|
||||
}
|
||||
|
||||
return r.referencePointer.String()
|
||||
}
|
||||
|
||||
// IsRoot returns true if this reference is a root document
|
||||
func (r *Ref) IsRoot() bool {
|
||||
return r.referenceURL != nil &&
|
||||
!r.IsCanonical() &&
|
||||
!r.HasURLPathOnly &&
|
||||
r.referenceURL.Fragment == ""
|
||||
}
|
||||
|
||||
// IsCanonical returns true when this pointer starts with http(s):// or file://
|
||||
func (r *Ref) IsCanonical() bool {
|
||||
return (r.HasFileScheme && r.HasFullFilePath) || (!r.HasFileScheme && r.HasFullURL)
|
||||
}
|
||||
|
||||
// "Constructor", parses the given string JSON reference
|
||||
func (r *Ref) parse(jsonReferenceString string) error {
|
||||
|
||||
parsed, err := url.Parse(jsonReferenceString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
internal.NormalizeURL(parsed)
|
||||
|
||||
r.referenceURL = parsed
|
||||
refURL := r.referenceURL
|
||||
|
||||
if refURL.Scheme != "" && refURL.Host != "" {
|
||||
r.HasFullURL = true
|
||||
} else {
|
||||
if refURL.Path != "" {
|
||||
r.HasURLPathOnly = true
|
||||
} else if refURL.RawQuery == "" && refURL.Fragment != "" {
|
||||
r.HasFragmentOnly = true
|
||||
}
|
||||
}
|
||||
|
||||
r.HasFileScheme = refURL.Scheme == "file"
|
||||
r.HasFullFilePath = strings.HasPrefix(refURL.Path, "/")
|
||||
|
||||
// invalid json-pointer error means url has no json-pointer fragment. simply ignore error
|
||||
r.referencePointer, _ = jsonpointer.New(refURL.Fragment)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Inherits creates a new reference from a parent and a child
|
||||
// If the child cannot inherit from the parent, an error is returned
|
||||
func (r *Ref) Inherits(child Ref) (*Ref, error) {
|
||||
childURL := child.GetURL()
|
||||
parentURL := r.GetURL()
|
||||
if childURL == nil {
|
||||
return nil, errors.New("child url is nil")
|
||||
}
|
||||
if parentURL == nil {
|
||||
return &child, nil
|
||||
}
|
||||
|
||||
ref, err := New(parentURL.ResolveReference(childURL).String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ref, nil
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Set default charset
|
||||
[*.{js,py,go,scala,rb,java,html,css,less,sass,md}]
|
||||
charset = utf-8
|
||||
|
||||
# Tab indentation (no size specified)
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Matches the exact files either package.json or .travis.yml
|
||||
[{package.json,.travis.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@ -0,0 +1 @@
|
||||
*.out
|
||||
@ -0,0 +1,61 @@
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
golint:
|
||||
min-confidence: 0
|
||||
gocyclo:
|
||||
min-complexity: 45
|
||||
maligned:
|
||||
suggest-new: true
|
||||
dupl:
|
||||
threshold: 200
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 3
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- maligned
|
||||
- unparam
|
||||
- lll
|
||||
- gochecknoinits
|
||||
- gochecknoglobals
|
||||
- funlen
|
||||
- godox
|
||||
- gocognit
|
||||
- whitespace
|
||||
- wsl
|
||||
- wrapcheck
|
||||
- testpackage
|
||||
- nlreturn
|
||||
- gomnd
|
||||
- exhaustivestruct
|
||||
- goerr113
|
||||
- errorlint
|
||||
- nestif
|
||||
- godot
|
||||
- gofumpt
|
||||
- paralleltest
|
||||
- tparallel
|
||||
- thelper
|
||||
- ifshort
|
||||
- exhaustruct
|
||||
- varnamelen
|
||||
- gci
|
||||
- depguard
|
||||
- errchkjson
|
||||
- inamedparam
|
||||
- nonamedreturns
|
||||
- musttag
|
||||
- ireturn
|
||||
- forcetypeassert
|
||||
- cyclop
|
||||
# deprecated linters
|
||||
- deadcode
|
||||
- interfacer
|
||||
- scopelint
|
||||
- varcheck
|
||||
- structcheck
|
||||
- golint
|
||||
- nosnakecase
|
||||
@ -0,0 +1,74 @@
|
||||
# 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/
|
||||
@ -0,0 +1,202 @@
|
||||
|
||||
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.
|
||||
@ -0,0 +1,54 @@
|
||||
# OpenAPI v2 object model [](https://github.com/go-openapi/spec/actions?query=workflow%3A"go+test") [](https://codecov.io/gh/go-openapi/spec)
|
||||
|
||||
[](https://slackin.goswagger.io)
|
||||
[](https://raw.githubusercontent.com/go-openapi/spec/master/LICENSE)
|
||||
[](https://pkg.go.dev/github.com/go-openapi/spec)
|
||||
[](https://goreportcard.com/report/github.com/go-openapi/spec)
|
||||
|
||||
The object model for OpenAPI specification documents.
|
||||
|
||||
### FAQ
|
||||
|
||||
* What does this do?
|
||||
|
||||
> 1. This package knows how to marshal and unmarshal Swagger API specifications into a golang object model
|
||||
> 2. It knows how to resolve $ref and expand them to make a single root document
|
||||
|
||||
* How does it play with the rest of the go-openapi packages ?
|
||||
|
||||
> 1. This package is at the core of the go-openapi suite of packages and [code generator](https://github.com/go-swagger/go-swagger)
|
||||
> 2. There is a [spec loading package](https://github.com/go-openapi/loads) to fetch specs as JSON or YAML from local or remote locations
|
||||
> 3. There is a [spec validation package](https://github.com/go-openapi/validate) built on top of it
|
||||
> 4. There is a [spec analysis package](https://github.com/go-openapi/analysis) built on top of it, to analyze, flatten, fix and merge spec documents
|
||||
|
||||
* Does this library support OpenAPI 3?
|
||||
|
||||
> No.
|
||||
> This package currently only supports OpenAPI 2.0 (aka Swagger 2.0).
|
||||
> There is no plan to make it evolve toward supporting OpenAPI 3.x.
|
||||
> This [discussion thread](https://github.com/go-openapi/spec/issues/21) relates the full story.
|
||||
>
|
||||
> An early attempt to support Swagger 3 may be found at: https://github.com/go-openapi/spec3
|
||||
|
||||
* Does the unmarshaling support YAML?
|
||||
|
||||
> Not directly. The exposed types know only how to unmarshal from JSON.
|
||||
>
|
||||
> In order to load a YAML document as a Swagger spec, you need to use the loaders provided by
|
||||
> github.com/go-openapi/loads
|
||||
>
|
||||
> Take a look at the example there: https://pkg.go.dev/github.com/go-openapi/loads#example-Spec
|
||||
>
|
||||
> See also https://github.com/go-openapi/spec/issues/164
|
||||
|
||||
* How can I validate a spec?
|
||||
|
||||
> Validation is provided by [the validate package](http://github.com/go-openapi/validate)
|
||||
|
||||
* Why do we have an `ID` field for `Schema` which is not part of the swagger spec?
|
||||
|
||||
> We found jsonschema compatibility more important: since `id` in jsonschema influences
|
||||
> how `$ref` are resolved.
|
||||
> This `id` does not conflict with any property named `id`.
|
||||
>
|
||||
> See also https://github.com/go-openapi/spec/issues/23
|
||||
@ -0,0 +1,98 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ResolutionCache a cache for resolving urls
|
||||
type ResolutionCache interface {
|
||||
Get(string) (interface{}, bool)
|
||||
Set(string, interface{})
|
||||
}
|
||||
|
||||
type simpleCache struct {
|
||||
lock sync.RWMutex
|
||||
store map[string]interface{}
|
||||
}
|
||||
|
||||
func (s *simpleCache) ShallowClone() ResolutionCache {
|
||||
store := make(map[string]interface{}, len(s.store))
|
||||
s.lock.RLock()
|
||||
for k, v := range s.store {
|
||||
store[k] = v
|
||||
}
|
||||
s.lock.RUnlock()
|
||||
|
||||
return &simpleCache{
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves a cached URI
|
||||
func (s *simpleCache) Get(uri string) (interface{}, bool) {
|
||||
s.lock.RLock()
|
||||
v, ok := s.store[uri]
|
||||
|
||||
s.lock.RUnlock()
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Set caches a URI
|
||||
func (s *simpleCache) Set(uri string, data interface{}) {
|
||||
s.lock.Lock()
|
||||
s.store[uri] = data
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
var (
|
||||
// resCache is a package level cache for $ref resolution and expansion.
|
||||
// It is initialized lazily by methods that have the need for it: no
|
||||
// memory is allocated unless some expander methods are called.
|
||||
//
|
||||
// It is initialized with JSON schema and swagger schema,
|
||||
// which do not mutate during normal operations.
|
||||
//
|
||||
// All subsequent utilizations of this cache are produced from a shallow
|
||||
// clone of this initial version.
|
||||
resCache *simpleCache
|
||||
onceCache sync.Once
|
||||
|
||||
_ ResolutionCache = &simpleCache{}
|
||||
)
|
||||
|
||||
// initResolutionCache initializes the URI resolution cache. To be wrapped in a sync.Once.Do call.
|
||||
func initResolutionCache() {
|
||||
resCache = defaultResolutionCache()
|
||||
}
|
||||
|
||||
func defaultResolutionCache() *simpleCache {
|
||||
return &simpleCache{store: map[string]interface{}{
|
||||
"http://swagger.io/v2/schema.json": MustLoadSwagger20Schema(),
|
||||
"http://json-schema.org/draft-04/schema": MustLoadJSONSchemaDraft04(),
|
||||
}}
|
||||
}
|
||||
|
||||
func cacheOrDefault(cache ResolutionCache) ResolutionCache {
|
||||
onceCache.Do(initResolutionCache)
|
||||
|
||||
if cache != nil {
|
||||
return cache
|
||||
}
|
||||
|
||||
// get a shallow clone of the base cache with swagger and json schema
|
||||
return resCache.ShallowClone()
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// ContactInfo contact information for the exposed API.
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#contactObject
|
||||
type ContactInfo struct {
|
||||
ContactInfoProps
|
||||
VendorExtensible
|
||||
}
|
||||
|
||||
// ContactInfoProps hold the properties of a ContactInfo object
|
||||
type ContactInfoProps struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON hydrates ContactInfo from json
|
||||
func (c *ContactInfo) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &c.ContactInfoProps); err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, &c.VendorExtensible)
|
||||
}
|
||||
|
||||
// MarshalJSON produces ContactInfo as json
|
||||
func (c ContactInfo) MarshalJSON() ([]byte, error) {
|
||||
b1, err := json.Marshal(c.ContactInfoProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b2, err := json.Marshal(c.VendorExtensible)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return swag.ConcatJSON(b1, b2), nil
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Debug is true when the SWAGGER_DEBUG env var is not empty.
|
||||
//
|
||||
// It enables a more verbose logging of this package.
|
||||
var Debug = os.Getenv("SWAGGER_DEBUG") != ""
|
||||
|
||||
var (
|
||||
// specLogger is a debug logger for this package
|
||||
specLogger *log.Logger
|
||||
)
|
||||
|
||||
func init() {
|
||||
debugOptions()
|
||||
}
|
||||
|
||||
func debugOptions() {
|
||||
specLogger = log.New(os.Stdout, "spec:", 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)
|
||||
specLogger.Printf("%s:%d: %s", path.Base(file1), pos1, fmt.Sprintf(msg, args...))
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"path"
|
||||
)
|
||||
|
||||
//go:embed schemas/*.json schemas/*/*.json
|
||||
var assets embed.FS
|
||||
|
||||
func jsonschemaDraft04JSONBytes() ([]byte, error) {
|
||||
return assets.ReadFile(path.Join("schemas", "jsonschema-draft-04.json"))
|
||||
}
|
||||
|
||||
func v2SchemaJSONBytes() ([]byte, error) {
|
||||
return assets.ReadFile(path.Join("schemas", "v2", "schema.json"))
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package spec
|
||||
|
||||
import "errors"
|
||||
|
||||
// Error codes
|
||||
var (
|
||||
// ErrUnknownTypeForReference indicates that a resolved reference was found in an unsupported container type
|
||||
ErrUnknownTypeForReference = errors.New("unknown type for the resolved reference")
|
||||
|
||||
// ErrResolveRefNeedsAPointer indicates that a $ref target must be a valid JSON pointer
|
||||
ErrResolveRefNeedsAPointer = errors.New("resolve ref: target needs to be a pointer")
|
||||
|
||||
// ErrDerefUnsupportedType indicates that a resolved reference was found in an unsupported container type.
|
||||
// At the moment, $ref are supported only inside: schemas, parameters, responses, path items
|
||||
ErrDerefUnsupportedType = errors.New("deref: unsupported type")
|
||||
|
||||
// ErrExpandUnsupportedType indicates that $ref expansion is attempted on some invalid type
|
||||
ErrExpandUnsupportedType = errors.New("expand: unsupported type. Input should be of type *Parameter or *Response")
|
||||
)
|
||||
@ -0,0 +1,607 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ExpandOptions provides options for the spec expander.
|
||||
//
|
||||
// RelativeBase is the path to the root document. This can be a remote URL or a path to a local file.
|
||||
//
|
||||
// If left empty, the root document is assumed to be located in the current working directory:
|
||||
// all relative $ref's will be resolved from there.
|
||||
//
|
||||
// PathLoader injects a document loading method. By default, this resolves to the function provided by the SpecLoader package variable.
|
||||
type ExpandOptions struct {
|
||||
RelativeBase string // the path to the root document to expand. This is a file, not a directory
|
||||
SkipSchemas bool // do not expand schemas, just paths, parameters and responses
|
||||
ContinueOnError bool // continue expanding even after and error is found
|
||||
PathLoader func(string) (json.RawMessage, error) `json:"-"` // the document loading method that takes a path as input and yields a json document
|
||||
AbsoluteCircularRef bool // circular $ref remaining after expansion remain absolute URLs
|
||||
}
|
||||
|
||||
func optionsOrDefault(opts *ExpandOptions) *ExpandOptions {
|
||||
if opts != nil {
|
||||
clone := *opts // shallow clone to avoid internal changes to be propagated to the caller
|
||||
if clone.RelativeBase != "" {
|
||||
clone.RelativeBase = normalizeBase(clone.RelativeBase)
|
||||
}
|
||||
// if the relative base is empty, let the schema loader choose a pseudo root document
|
||||
return &clone
|
||||
}
|
||||
return &ExpandOptions{}
|
||||
}
|
||||
|
||||
// ExpandSpec expands the references in a swagger spec
|
||||
func ExpandSpec(spec *Swagger, options *ExpandOptions) error {
|
||||
options = optionsOrDefault(options)
|
||||
resolver := defaultSchemaLoader(spec, options, nil, nil)
|
||||
|
||||
specBasePath := options.RelativeBase
|
||||
|
||||
if !options.SkipSchemas {
|
||||
for key, definition := range spec.Definitions {
|
||||
parentRefs := make([]string, 0, 10)
|
||||
parentRefs = append(parentRefs, "#/definitions/"+key)
|
||||
|
||||
def, err := expandSchema(definition, parentRefs, resolver, specBasePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
}
|
||||
if def != nil {
|
||||
spec.Definitions[key] = *def
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key := range spec.Parameters {
|
||||
parameter := spec.Parameters[key]
|
||||
if err := expandParameterOrResponse(¶meter, resolver, specBasePath); resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
}
|
||||
spec.Parameters[key] = parameter
|
||||
}
|
||||
|
||||
for key := range spec.Responses {
|
||||
response := spec.Responses[key]
|
||||
if err := expandParameterOrResponse(&response, resolver, specBasePath); resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
}
|
||||
spec.Responses[key] = response
|
||||
}
|
||||
|
||||
if spec.Paths != nil {
|
||||
for key := range spec.Paths.Paths {
|
||||
pth := spec.Paths.Paths[key]
|
||||
if err := expandPathItem(&pth, resolver, specBasePath); resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
}
|
||||
spec.Paths.Paths[key] = pth
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const rootBase = ".root"
|
||||
|
||||
// baseForRoot loads in the cache the root document and produces a fake ".root" base path entry
|
||||
// for further $ref resolution
|
||||
func baseForRoot(root interface{}, cache ResolutionCache) string {
|
||||
// cache the root document to resolve $ref's
|
||||
normalizedBase := normalizeBase(rootBase)
|
||||
|
||||
if root == nil {
|
||||
// ensure that we never leave a nil root: always cache the root base pseudo-document
|
||||
cachedRoot, found := cache.Get(normalizedBase)
|
||||
if found && cachedRoot != nil {
|
||||
// the cache is already preloaded with a root
|
||||
return normalizedBase
|
||||
}
|
||||
|
||||
root = map[string]interface{}{}
|
||||
}
|
||||
|
||||
cache.Set(normalizedBase, root)
|
||||
|
||||
return normalizedBase
|
||||
}
|
||||
|
||||
// ExpandSchema expands the refs in the schema object with reference to the root object.
|
||||
//
|
||||
// go-openapi/validate uses this function.
|
||||
//
|
||||
// Notice that it is impossible to reference a json schema in a different document other than root
|
||||
// (use ExpandSchemaWithBasePath to resolve external references).
|
||||
//
|
||||
// Setting the cache is optional and this parameter may safely be left to nil.
|
||||
func ExpandSchema(schema *Schema, root interface{}, cache ResolutionCache) error {
|
||||
cache = cacheOrDefault(cache)
|
||||
if root == nil {
|
||||
root = schema
|
||||
}
|
||||
|
||||
opts := &ExpandOptions{
|
||||
// when a root is specified, cache the root as an in-memory document for $ref retrieval
|
||||
RelativeBase: baseForRoot(root, cache),
|
||||
SkipSchemas: false,
|
||||
ContinueOnError: false,
|
||||
}
|
||||
|
||||
return ExpandSchemaWithBasePath(schema, cache, opts)
|
||||
}
|
||||
|
||||
// ExpandSchemaWithBasePath expands the refs in the schema object, base path configured through expand options.
|
||||
//
|
||||
// Setting the cache is optional and this parameter may safely be left to nil.
|
||||
func ExpandSchemaWithBasePath(schema *Schema, cache ResolutionCache, opts *ExpandOptions) error {
|
||||
if schema == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cache = cacheOrDefault(cache)
|
||||
|
||||
opts = optionsOrDefault(opts)
|
||||
|
||||
resolver := defaultSchemaLoader(nil, opts, cache, nil)
|
||||
|
||||
parentRefs := make([]string, 0, 10)
|
||||
s, err := expandSchema(*schema, parentRefs, resolver, opts.RelativeBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s != nil {
|
||||
// guard for when continuing on error
|
||||
*schema = *s
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func expandItems(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
|
||||
if target.Items == nil {
|
||||
return &target, nil
|
||||
}
|
||||
|
||||
// array
|
||||
if target.Items.Schema != nil {
|
||||
t, err := expandSchema(*target.Items.Schema, parentRefs, resolver, basePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*target.Items.Schema = *t
|
||||
}
|
||||
|
||||
// tuple
|
||||
for i := range target.Items.Schemas {
|
||||
t, err := expandSchema(target.Items.Schemas[i], parentRefs, resolver, basePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target.Items.Schemas[i] = *t
|
||||
}
|
||||
|
||||
return &target, nil
|
||||
}
|
||||
|
||||
func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
|
||||
if target.Ref.String() == "" && target.Ref.IsRoot() {
|
||||
newRef := normalizeRef(&target.Ref, basePath)
|
||||
target.Ref = *newRef
|
||||
return &target, nil
|
||||
}
|
||||
|
||||
// change the base path of resolution when an ID is encountered
|
||||
// otherwise the basePath should inherit the parent's
|
||||
if target.ID != "" {
|
||||
basePath, _ = resolver.setSchemaID(target, target.ID, basePath)
|
||||
}
|
||||
|
||||
if target.Ref.String() != "" {
|
||||
if !resolver.options.SkipSchemas {
|
||||
return expandSchemaRef(target, parentRefs, resolver, basePath)
|
||||
}
|
||||
|
||||
// when "expand" with SkipSchema, we just rebase the existing $ref without replacing
|
||||
// the full schema.
|
||||
rebasedRef, err := NewRef(normalizeURI(target.Ref.String(), basePath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target.Ref = denormalizeRef(&rebasedRef, resolver.context.basePath, resolver.context.rootID)
|
||||
|
||||
return &target, nil
|
||||
}
|
||||
|
||||
for k := range target.Definitions {
|
||||
tt, err := expandSchema(target.Definitions[k], parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return &target, err
|
||||
}
|
||||
if tt != nil {
|
||||
target.Definitions[k] = *tt
|
||||
}
|
||||
}
|
||||
|
||||
t, err := expandItems(target, parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return &target, err
|
||||
}
|
||||
if t != nil {
|
||||
target = *t
|
||||
}
|
||||
|
||||
for i := range target.AllOf {
|
||||
t, err := expandSchema(target.AllOf[i], parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return &target, err
|
||||
}
|
||||
if t != nil {
|
||||
target.AllOf[i] = *t
|
||||
}
|
||||
}
|
||||
|
||||
for i := range target.AnyOf {
|
||||
t, err := expandSchema(target.AnyOf[i], parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return &target, err
|
||||
}
|
||||
if t != nil {
|
||||
target.AnyOf[i] = *t
|
||||
}
|
||||
}
|
||||
|
||||
for i := range target.OneOf {
|
||||
t, err := expandSchema(target.OneOf[i], parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return &target, err
|
||||
}
|
||||
if t != nil {
|
||||
target.OneOf[i] = *t
|
||||
}
|
||||
}
|
||||
|
||||
if target.Not != nil {
|
||||
t, err := expandSchema(*target.Not, parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return &target, err
|
||||
}
|
||||
if t != nil {
|
||||
*target.Not = *t
|
||||
}
|
||||
}
|
||||
|
||||
for k := range target.Properties {
|
||||
t, err := expandSchema(target.Properties[k], parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return &target, err
|
||||
}
|
||||
if t != nil {
|
||||
target.Properties[k] = *t
|
||||
}
|
||||
}
|
||||
|
||||
if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil {
|
||||
t, err := expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return &target, err
|
||||
}
|
||||
if t != nil {
|
||||
*target.AdditionalProperties.Schema = *t
|
||||
}
|
||||
}
|
||||
|
||||
for k := range target.PatternProperties {
|
||||
t, err := expandSchema(target.PatternProperties[k], parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return &target, err
|
||||
}
|
||||
if t != nil {
|
||||
target.PatternProperties[k] = *t
|
||||
}
|
||||
}
|
||||
|
||||
for k := range target.Dependencies {
|
||||
if target.Dependencies[k].Schema != nil {
|
||||
t, err := expandSchema(*target.Dependencies[k].Schema, parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return &target, err
|
||||
}
|
||||
if t != nil {
|
||||
*target.Dependencies[k].Schema = *t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil {
|
||||
t, err := expandSchema(*target.AdditionalItems.Schema, parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return &target, err
|
||||
}
|
||||
if t != nil {
|
||||
*target.AdditionalItems.Schema = *t
|
||||
}
|
||||
}
|
||||
return &target, nil
|
||||
}
|
||||
|
||||
func expandSchemaRef(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
|
||||
// if a Ref is found, all sibling fields are skipped
|
||||
// Ref also changes the resolution scope of children expandSchema
|
||||
|
||||
// here the resolution scope is changed because a $ref was encountered
|
||||
normalizedRef := normalizeRef(&target.Ref, basePath)
|
||||
normalizedBasePath := normalizedRef.RemoteURI()
|
||||
|
||||
if resolver.isCircular(normalizedRef, basePath, parentRefs...) {
|
||||
// this means there is a cycle in the recursion tree: return the Ref
|
||||
// - circular refs cannot be expanded. We leave them as ref.
|
||||
// - denormalization means that a new local file ref is set relative to the original basePath
|
||||
debugLog("short circuit circular ref: basePath: %s, normalizedPath: %s, normalized ref: %s",
|
||||
basePath, normalizedBasePath, normalizedRef.String())
|
||||
if !resolver.options.AbsoluteCircularRef {
|
||||
target.Ref = denormalizeRef(normalizedRef, resolver.context.basePath, resolver.context.rootID)
|
||||
} else {
|
||||
target.Ref = *normalizedRef
|
||||
}
|
||||
return &target, nil
|
||||
}
|
||||
|
||||
var t *Schema
|
||||
err := resolver.Resolve(&target.Ref, &t, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t == nil {
|
||||
// guard for when continuing on error
|
||||
return &target, nil
|
||||
}
|
||||
|
||||
parentRefs = append(parentRefs, normalizedRef.String())
|
||||
transitiveResolver := resolver.transitiveResolver(basePath, target.Ref)
|
||||
|
||||
basePath = resolver.updateBasePath(transitiveResolver, normalizedBasePath)
|
||||
|
||||
return expandSchema(*t, parentRefs, transitiveResolver, basePath)
|
||||
}
|
||||
|
||||
func expandPathItem(pathItem *PathItem, resolver *schemaLoader, basePath string) error {
|
||||
if pathItem == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
parentRefs := make([]string, 0, 10)
|
||||
if err := resolver.deref(pathItem, parentRefs, basePath); resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if pathItem.Ref.String() != "" {
|
||||
transitiveResolver := resolver.transitiveResolver(basePath, pathItem.Ref)
|
||||
basePath = transitiveResolver.updateBasePath(resolver, basePath)
|
||||
resolver = transitiveResolver
|
||||
}
|
||||
|
||||
pathItem.Ref = Ref{}
|
||||
for i := range pathItem.Parameters {
|
||||
if err := expandParameterOrResponse(&(pathItem.Parameters[i]), resolver, basePath); resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ops := []*Operation{
|
||||
pathItem.Get,
|
||||
pathItem.Head,
|
||||
pathItem.Options,
|
||||
pathItem.Put,
|
||||
pathItem.Post,
|
||||
pathItem.Patch,
|
||||
pathItem.Delete,
|
||||
}
|
||||
for _, op := range ops {
|
||||
if err := expandOperation(op, resolver, basePath); resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func expandOperation(op *Operation, resolver *schemaLoader, basePath string) error {
|
||||
if op == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := range op.Parameters {
|
||||
param := op.Parameters[i]
|
||||
if err := expandParameterOrResponse(¶m, resolver, basePath); resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
}
|
||||
op.Parameters[i] = param
|
||||
}
|
||||
|
||||
if op.Responses == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
responses := op.Responses
|
||||
if err := expandParameterOrResponse(responses.Default, resolver, basePath); resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
for code := range responses.StatusCodeResponses {
|
||||
response := responses.StatusCodeResponses[code]
|
||||
if err := expandParameterOrResponse(&response, resolver, basePath); resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
}
|
||||
responses.StatusCodeResponses[code] = response
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExpandResponseWithRoot expands a response based on a root document, not a fetchable document
|
||||
//
|
||||
// Notice that it is impossible to reference a json schema in a different document other than root
|
||||
// (use ExpandResponse to resolve external references).
|
||||
//
|
||||
// Setting the cache is optional and this parameter may safely be left to nil.
|
||||
func ExpandResponseWithRoot(response *Response, root interface{}, cache ResolutionCache) error {
|
||||
cache = cacheOrDefault(cache)
|
||||
opts := &ExpandOptions{
|
||||
RelativeBase: baseForRoot(root, cache),
|
||||
}
|
||||
resolver := defaultSchemaLoader(root, opts, cache, nil)
|
||||
|
||||
return expandParameterOrResponse(response, resolver, opts.RelativeBase)
|
||||
}
|
||||
|
||||
// ExpandResponse expands a response based on a basepath
|
||||
//
|
||||
// All refs inside response will be resolved relative to basePath
|
||||
func ExpandResponse(response *Response, basePath string) error {
|
||||
opts := optionsOrDefault(&ExpandOptions{
|
||||
RelativeBase: basePath,
|
||||
})
|
||||
resolver := defaultSchemaLoader(nil, opts, nil, nil)
|
||||
|
||||
return expandParameterOrResponse(response, resolver, opts.RelativeBase)
|
||||
}
|
||||
|
||||
// ExpandParameterWithRoot expands a parameter based on a root document, not a fetchable document.
|
||||
//
|
||||
// Notice that it is impossible to reference a json schema in a different document other than root
|
||||
// (use ExpandParameter to resolve external references).
|
||||
func ExpandParameterWithRoot(parameter *Parameter, root interface{}, cache ResolutionCache) error {
|
||||
cache = cacheOrDefault(cache)
|
||||
|
||||
opts := &ExpandOptions{
|
||||
RelativeBase: baseForRoot(root, cache),
|
||||
}
|
||||
resolver := defaultSchemaLoader(root, opts, cache, nil)
|
||||
|
||||
return expandParameterOrResponse(parameter, resolver, opts.RelativeBase)
|
||||
}
|
||||
|
||||
// ExpandParameter expands a parameter based on a basepath.
|
||||
// This is the exported version of expandParameter
|
||||
// all refs inside parameter will be resolved relative to basePath
|
||||
func ExpandParameter(parameter *Parameter, basePath string) error {
|
||||
opts := optionsOrDefault(&ExpandOptions{
|
||||
RelativeBase: basePath,
|
||||
})
|
||||
resolver := defaultSchemaLoader(nil, opts, nil, nil)
|
||||
|
||||
return expandParameterOrResponse(parameter, resolver, opts.RelativeBase)
|
||||
}
|
||||
|
||||
func getRefAndSchema(input interface{}) (*Ref, *Schema, error) {
|
||||
var (
|
||||
ref *Ref
|
||||
sch *Schema
|
||||
)
|
||||
|
||||
switch refable := input.(type) {
|
||||
case *Parameter:
|
||||
if refable == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
ref = &refable.Ref
|
||||
sch = refable.Schema
|
||||
case *Response:
|
||||
if refable == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
ref = &refable.Ref
|
||||
sch = refable.Schema
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unsupported type: %T: %w", input, ErrExpandUnsupportedType)
|
||||
}
|
||||
|
||||
return ref, sch, nil
|
||||
}
|
||||
|
||||
func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePath string) error {
|
||||
ref, sch, err := getRefAndSchema(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ref == nil && sch == nil { // nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
parentRefs := make([]string, 0, 10)
|
||||
if ref != nil {
|
||||
// dereference this $ref
|
||||
if err = resolver.deref(input, parentRefs, basePath); resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
ref, sch, _ = getRefAndSchema(input)
|
||||
}
|
||||
|
||||
if ref.String() != "" {
|
||||
transitiveResolver := resolver.transitiveResolver(basePath, *ref)
|
||||
basePath = resolver.updateBasePath(transitiveResolver, basePath)
|
||||
resolver = transitiveResolver
|
||||
}
|
||||
|
||||
if sch == nil {
|
||||
// nothing to be expanded
|
||||
if ref != nil {
|
||||
*ref = Ref{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if sch.Ref.String() != "" {
|
||||
rebasedRef, ern := NewRef(normalizeURI(sch.Ref.String(), basePath))
|
||||
if ern != nil {
|
||||
return ern
|
||||
}
|
||||
|
||||
if resolver.isCircular(&rebasedRef, basePath, parentRefs...) {
|
||||
// this is a circular $ref: stop expansion
|
||||
if !resolver.options.AbsoluteCircularRef {
|
||||
sch.Ref = denormalizeRef(&rebasedRef, resolver.context.basePath, resolver.context.rootID)
|
||||
} else {
|
||||
sch.Ref = rebasedRef
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// $ref expansion or rebasing is performed by expandSchema below
|
||||
if ref != nil {
|
||||
*ref = Ref{}
|
||||
}
|
||||
|
||||
// expand schema
|
||||
// yes, we do it even if options.SkipSchema is true: we have to go down that rabbit hole and rebase nested $ref)
|
||||
s, err := expandSchema(*sch, parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if s != nil { // guard for when continuing on error
|
||||
*sch = *s
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
// 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 spec
|
||||
|
||||
// ExternalDocumentation allows referencing an external resource for
|
||||
// extended documentation.
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#externalDocumentationObject
|
||||
type ExternalDocumentation struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
@ -0,0 +1,203 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
const (
|
||||
jsonArray = "array"
|
||||
)
|
||||
|
||||
// HeaderProps describes a response header
|
||||
type HeaderProps struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// Header describes a header for a response of the API
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#headerObject
|
||||
type Header struct {
|
||||
CommonValidations
|
||||
SimpleSchema
|
||||
VendorExtensible
|
||||
HeaderProps
|
||||
}
|
||||
|
||||
// ResponseHeader creates a new header instance for use in a response
|
||||
func ResponseHeader() *Header {
|
||||
return new(Header)
|
||||
}
|
||||
|
||||
// WithDescription sets the description on this response, allows for chaining
|
||||
func (h *Header) WithDescription(description string) *Header {
|
||||
h.Description = description
|
||||
return h
|
||||
}
|
||||
|
||||
// Typed a fluent builder method for the type of parameter
|
||||
func (h *Header) Typed(tpe, format string) *Header {
|
||||
h.Type = tpe
|
||||
h.Format = format
|
||||
return h
|
||||
}
|
||||
|
||||
// CollectionOf a fluent builder method for an array item
|
||||
func (h *Header) CollectionOf(items *Items, format string) *Header {
|
||||
h.Type = jsonArray
|
||||
h.Items = items
|
||||
h.CollectionFormat = format
|
||||
return h
|
||||
}
|
||||
|
||||
// WithDefault sets the default value on this item
|
||||
func (h *Header) WithDefault(defaultValue interface{}) *Header {
|
||||
h.Default = defaultValue
|
||||
return h
|
||||
}
|
||||
|
||||
// WithMaxLength sets a max length value
|
||||
func (h *Header) WithMaxLength(max int64) *Header {
|
||||
h.MaxLength = &max
|
||||
return h
|
||||
}
|
||||
|
||||
// WithMinLength sets a min length value
|
||||
func (h *Header) WithMinLength(min int64) *Header {
|
||||
h.MinLength = &min
|
||||
return h
|
||||
}
|
||||
|
||||
// WithPattern sets a pattern value
|
||||
func (h *Header) WithPattern(pattern string) *Header {
|
||||
h.Pattern = pattern
|
||||
return h
|
||||
}
|
||||
|
||||
// WithMultipleOf sets a multiple of value
|
||||
func (h *Header) WithMultipleOf(number float64) *Header {
|
||||
h.MultipleOf = &number
|
||||
return h
|
||||
}
|
||||
|
||||
// WithMaximum sets a maximum number value
|
||||
func (h *Header) WithMaximum(max float64, exclusive bool) *Header {
|
||||
h.Maximum = &max
|
||||
h.ExclusiveMaximum = exclusive
|
||||
return h
|
||||
}
|
||||
|
||||
// WithMinimum sets a minimum number value
|
||||
func (h *Header) WithMinimum(min float64, exclusive bool) *Header {
|
||||
h.Minimum = &min
|
||||
h.ExclusiveMinimum = exclusive
|
||||
return h
|
||||
}
|
||||
|
||||
// WithEnum sets a the enum values (replace)
|
||||
func (h *Header) WithEnum(values ...interface{}) *Header {
|
||||
h.Enum = append([]interface{}{}, values...)
|
||||
return h
|
||||
}
|
||||
|
||||
// WithMaxItems sets the max items
|
||||
func (h *Header) WithMaxItems(size int64) *Header {
|
||||
h.MaxItems = &size
|
||||
return h
|
||||
}
|
||||
|
||||
// WithMinItems sets the min items
|
||||
func (h *Header) WithMinItems(size int64) *Header {
|
||||
h.MinItems = &size
|
||||
return h
|
||||
}
|
||||
|
||||
// UniqueValues dictates that this array can only have unique items
|
||||
func (h *Header) UniqueValues() *Header {
|
||||
h.UniqueItems = true
|
||||
return h
|
||||
}
|
||||
|
||||
// AllowDuplicates this array can have duplicates
|
||||
func (h *Header) AllowDuplicates() *Header {
|
||||
h.UniqueItems = false
|
||||
return h
|
||||
}
|
||||
|
||||
// WithValidations is a fluent method to set header validations
|
||||
func (h *Header) WithValidations(val CommonValidations) *Header {
|
||||
h.SetValidations(SchemaValidations{CommonValidations: val})
|
||||
return h
|
||||
}
|
||||
|
||||
// MarshalJSON marshal this to JSON
|
||||
func (h Header) MarshalJSON() ([]byte, error) {
|
||||
b1, err := json.Marshal(h.CommonValidations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b2, err := json.Marshal(h.SimpleSchema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b3, err := json.Marshal(h.HeaderProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return swag.ConcatJSON(b1, b2, b3), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals this header from JSON
|
||||
func (h *Header) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &h.CommonValidations); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(data, &h.SimpleSchema); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(data, &h.VendorExtensible); err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, &h.HeaderProps)
|
||||
}
|
||||
|
||||
// JSONLookup look up a value by the json property name
|
||||
func (h Header) JSONLookup(token string) (interface{}, error) {
|
||||
if ex, ok := h.Extensions[token]; ok {
|
||||
return &ex, nil
|
||||
}
|
||||
|
||||
r, _, err := jsonpointer.GetForToken(h.CommonValidations, token)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
|
||||
return nil, err
|
||||
}
|
||||
if r != nil {
|
||||
return r, nil
|
||||
}
|
||||
r, _, err = jsonpointer.GetForToken(h.SimpleSchema, token)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
|
||||
return nil, err
|
||||
}
|
||||
if r != nil {
|
||||
return r, nil
|
||||
}
|
||||
r, _, err = jsonpointer.GetForToken(h.HeaderProps, token)
|
||||
return r, err
|
||||
}
|
||||
@ -0,0 +1,184 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// Extensions vendor specific extensions
|
||||
type Extensions map[string]interface{}
|
||||
|
||||
// Add adds a value to these extensions
|
||||
func (e Extensions) Add(key string, value interface{}) {
|
||||
realKey := strings.ToLower(key)
|
||||
e[realKey] = value
|
||||
}
|
||||
|
||||
// GetString gets a string value from the extensions
|
||||
func (e Extensions) GetString(key string) (string, bool) {
|
||||
if v, ok := e[strings.ToLower(key)]; ok {
|
||||
str, ok := v.(string)
|
||||
return str, ok
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// GetInt gets a int value from the extensions
|
||||
func (e Extensions) GetInt(key string) (int, bool) {
|
||||
realKey := strings.ToLower(key)
|
||||
|
||||
if v, ok := e.GetString(realKey); ok {
|
||||
if r, err := strconv.Atoi(v); err == nil {
|
||||
return r, true
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := e[realKey]; ok {
|
||||
if r, rOk := v.(float64); rOk {
|
||||
return int(r), true
|
||||
}
|
||||
}
|
||||
return -1, false
|
||||
}
|
||||
|
||||
// GetBool gets a string value from the extensions
|
||||
func (e Extensions) GetBool(key string) (bool, bool) {
|
||||
if v, ok := e[strings.ToLower(key)]; ok {
|
||||
str, ok := v.(bool)
|
||||
return str, ok
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
// GetStringSlice gets a string value from the extensions
|
||||
func (e Extensions) GetStringSlice(key string) ([]string, bool) {
|
||||
if v, ok := e[strings.ToLower(key)]; ok {
|
||||
arr, isSlice := v.([]interface{})
|
||||
if !isSlice {
|
||||
return nil, false
|
||||
}
|
||||
var strs []string
|
||||
for _, iface := range arr {
|
||||
str, isString := iface.(string)
|
||||
if !isString {
|
||||
return nil, false
|
||||
}
|
||||
strs = append(strs, str)
|
||||
}
|
||||
return strs, ok
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// VendorExtensible composition block.
|
||||
type VendorExtensible struct {
|
||||
Extensions Extensions
|
||||
}
|
||||
|
||||
// AddExtension adds an extension to this extensible object
|
||||
func (v *VendorExtensible) AddExtension(key string, value interface{}) {
|
||||
if value == nil {
|
||||
return
|
||||
}
|
||||
if v.Extensions == nil {
|
||||
v.Extensions = make(map[string]interface{})
|
||||
}
|
||||
v.Extensions.Add(key, value)
|
||||
}
|
||||
|
||||
// MarshalJSON marshals the extensions to json
|
||||
func (v VendorExtensible) MarshalJSON() ([]byte, error) {
|
||||
toser := make(map[string]interface{})
|
||||
for k, v := range v.Extensions {
|
||||
lk := strings.ToLower(k)
|
||||
if strings.HasPrefix(lk, "x-") {
|
||||
toser[k] = v
|
||||
}
|
||||
}
|
||||
return json.Marshal(toser)
|
||||
}
|
||||
|
||||
// UnmarshalJSON for this extensible object
|
||||
func (v *VendorExtensible) UnmarshalJSON(data []byte) error {
|
||||
var d map[string]interface{}
|
||||
if err := json.Unmarshal(data, &d); err != nil {
|
||||
return err
|
||||
}
|
||||
for k, vv := range d {
|
||||
lk := strings.ToLower(k)
|
||||
if strings.HasPrefix(lk, "x-") {
|
||||
if v.Extensions == nil {
|
||||
v.Extensions = map[string]interface{}{}
|
||||
}
|
||||
v.Extensions[k] = vv
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InfoProps the properties for an info definition
|
||||
type InfoProps struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
TermsOfService string `json:"termsOfService,omitempty"`
|
||||
Contact *ContactInfo `json:"contact,omitempty"`
|
||||
License *License `json:"license,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// Info object provides metadata about the API.
|
||||
// The metadata can be used by the clients if needed, and can be presented in the Swagger-UI for convenience.
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#infoObject
|
||||
type Info struct {
|
||||
VendorExtensible
|
||||
InfoProps
|
||||
}
|
||||
|
||||
// JSONLookup look up a value by the json property name
|
||||
func (i Info) JSONLookup(token string) (interface{}, error) {
|
||||
if ex, ok := i.Extensions[token]; ok {
|
||||
return &ex, nil
|
||||
}
|
||||
r, _, err := jsonpointer.GetForToken(i.InfoProps, token)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// MarshalJSON marshal this to JSON
|
||||
func (i Info) MarshalJSON() ([]byte, error) {
|
||||
b1, err := json.Marshal(i.InfoProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b2, err := json.Marshal(i.VendorExtensible)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return swag.ConcatJSON(b1, b2), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON marshal this from JSON
|
||||
func (i *Info) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &i.InfoProps); err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, &i.VendorExtensible)
|
||||
}
|
||||
@ -0,0 +1,234 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
const (
|
||||
jsonRef = "$ref"
|
||||
)
|
||||
|
||||
// SimpleSchema describe swagger simple schemas for parameters and headers
|
||||
type SimpleSchema struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Nullable bool `json:"nullable,omitempty"`
|
||||
Format string `json:"format,omitempty"`
|
||||
Items *Items `json:"items,omitempty"`
|
||||
CollectionFormat string `json:"collectionFormat,omitempty"`
|
||||
Default interface{} `json:"default,omitempty"`
|
||||
Example interface{} `json:"example,omitempty"`
|
||||
}
|
||||
|
||||
// TypeName return the type (or format) of a simple schema
|
||||
func (s *SimpleSchema) TypeName() string {
|
||||
if s.Format != "" {
|
||||
return s.Format
|
||||
}
|
||||
return s.Type
|
||||
}
|
||||
|
||||
// ItemsTypeName yields the type of items in a simple schema array
|
||||
func (s *SimpleSchema) ItemsTypeName() string {
|
||||
if s.Items == nil {
|
||||
return ""
|
||||
}
|
||||
return s.Items.TypeName()
|
||||
}
|
||||
|
||||
// Items a limited subset of JSON-Schema's items object.
|
||||
// It is used by parameter definitions that are not located in "body".
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#items-object
|
||||
type Items struct {
|
||||
Refable
|
||||
CommonValidations
|
||||
SimpleSchema
|
||||
VendorExtensible
|
||||
}
|
||||
|
||||
// NewItems creates a new instance of items
|
||||
func NewItems() *Items {
|
||||
return &Items{}
|
||||
}
|
||||
|
||||
// Typed a fluent builder method for the type of item
|
||||
func (i *Items) Typed(tpe, format string) *Items {
|
||||
i.Type = tpe
|
||||
i.Format = format
|
||||
return i
|
||||
}
|
||||
|
||||
// AsNullable flags this schema as nullable.
|
||||
func (i *Items) AsNullable() *Items {
|
||||
i.Nullable = true
|
||||
return i
|
||||
}
|
||||
|
||||
// CollectionOf a fluent builder method for an array item
|
||||
func (i *Items) CollectionOf(items *Items, format string) *Items {
|
||||
i.Type = jsonArray
|
||||
i.Items = items
|
||||
i.CollectionFormat = format
|
||||
return i
|
||||
}
|
||||
|
||||
// WithDefault sets the default value on this item
|
||||
func (i *Items) WithDefault(defaultValue interface{}) *Items {
|
||||
i.Default = defaultValue
|
||||
return i
|
||||
}
|
||||
|
||||
// WithMaxLength sets a max length value
|
||||
func (i *Items) WithMaxLength(max int64) *Items {
|
||||
i.MaxLength = &max
|
||||
return i
|
||||
}
|
||||
|
||||
// WithMinLength sets a min length value
|
||||
func (i *Items) WithMinLength(min int64) *Items {
|
||||
i.MinLength = &min
|
||||
return i
|
||||
}
|
||||
|
||||
// WithPattern sets a pattern value
|
||||
func (i *Items) WithPattern(pattern string) *Items {
|
||||
i.Pattern = pattern
|
||||
return i
|
||||
}
|
||||
|
||||
// WithMultipleOf sets a multiple of value
|
||||
func (i *Items) WithMultipleOf(number float64) *Items {
|
||||
i.MultipleOf = &number
|
||||
return i
|
||||
}
|
||||
|
||||
// WithMaximum sets a maximum number value
|
||||
func (i *Items) WithMaximum(max float64, exclusive bool) *Items {
|
||||
i.Maximum = &max
|
||||
i.ExclusiveMaximum = exclusive
|
||||
return i
|
||||
}
|
||||
|
||||
// WithMinimum sets a minimum number value
|
||||
func (i *Items) WithMinimum(min float64, exclusive bool) *Items {
|
||||
i.Minimum = &min
|
||||
i.ExclusiveMinimum = exclusive
|
||||
return i
|
||||
}
|
||||
|
||||
// WithEnum sets a the enum values (replace)
|
||||
func (i *Items) WithEnum(values ...interface{}) *Items {
|
||||
i.Enum = append([]interface{}{}, values...)
|
||||
return i
|
||||
}
|
||||
|
||||
// WithMaxItems sets the max items
|
||||
func (i *Items) WithMaxItems(size int64) *Items {
|
||||
i.MaxItems = &size
|
||||
return i
|
||||
}
|
||||
|
||||
// WithMinItems sets the min items
|
||||
func (i *Items) WithMinItems(size int64) *Items {
|
||||
i.MinItems = &size
|
||||
return i
|
||||
}
|
||||
|
||||
// UniqueValues dictates that this array can only have unique items
|
||||
func (i *Items) UniqueValues() *Items {
|
||||
i.UniqueItems = true
|
||||
return i
|
||||
}
|
||||
|
||||
// AllowDuplicates this array can have duplicates
|
||||
func (i *Items) AllowDuplicates() *Items {
|
||||
i.UniqueItems = false
|
||||
return i
|
||||
}
|
||||
|
||||
// WithValidations is a fluent method to set Items validations
|
||||
func (i *Items) WithValidations(val CommonValidations) *Items {
|
||||
i.SetValidations(SchemaValidations{CommonValidations: val})
|
||||
return i
|
||||
}
|
||||
|
||||
// UnmarshalJSON hydrates this items instance with the data from JSON
|
||||
func (i *Items) UnmarshalJSON(data []byte) error {
|
||||
var validations CommonValidations
|
||||
if err := json.Unmarshal(data, &validations); err != nil {
|
||||
return err
|
||||
}
|
||||
var ref Refable
|
||||
if err := json.Unmarshal(data, &ref); err != nil {
|
||||
return err
|
||||
}
|
||||
var simpleSchema SimpleSchema
|
||||
if err := json.Unmarshal(data, &simpleSchema); err != nil {
|
||||
return err
|
||||
}
|
||||
var vendorExtensible VendorExtensible
|
||||
if err := json.Unmarshal(data, &vendorExtensible); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Refable = ref
|
||||
i.CommonValidations = validations
|
||||
i.SimpleSchema = simpleSchema
|
||||
i.VendorExtensible = vendorExtensible
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON converts this items object to JSON
|
||||
func (i Items) MarshalJSON() ([]byte, error) {
|
||||
b1, err := json.Marshal(i.CommonValidations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b2, err := json.Marshal(i.SimpleSchema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b3, err := json.Marshal(i.Refable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b4, err := json.Marshal(i.VendorExtensible)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return swag.ConcatJSON(b4, b3, b1, b2), nil
|
||||
}
|
||||
|
||||
// JSONLookup look up a value by the json property name
|
||||
func (i Items) JSONLookup(token string) (interface{}, error) {
|
||||
if token == jsonRef {
|
||||
return &i.Ref, nil
|
||||
}
|
||||
|
||||
r, _, err := jsonpointer.GetForToken(i.CommonValidations, token)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
|
||||
return nil, err
|
||||
}
|
||||
if r != nil {
|
||||
return r, nil
|
||||
}
|
||||
r, _, err = jsonpointer.GetForToken(i.SimpleSchema, token)
|
||||
return r, err
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// License information for the exposed API.
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#licenseObject
|
||||
type License struct {
|
||||
LicenseProps
|
||||
VendorExtensible
|
||||
}
|
||||
|
||||
// LicenseProps holds the properties of a License object
|
||||
type LicenseProps struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON hydrates License from json
|
||||
func (l *License) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &l.LicenseProps); err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, &l.VendorExtensible)
|
||||
}
|
||||
|
||||
// MarshalJSON produces License as json
|
||||
func (l License) MarshalJSON() ([]byte, error) {
|
||||
b1, err := json.Marshal(l.LicenseProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b2, err := json.Marshal(l.VendorExtensible)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return swag.ConcatJSON(b1, b2), nil
|
||||
}
|
||||
@ -0,0 +1,202 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package spec
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const fileScheme = "file"
|
||||
|
||||
// normalizeURI ensures that all $ref paths used internally by the expander are canonicalized.
|
||||
//
|
||||
// NOTE(windows): there is a tolerance over the strict URI format on windows.
|
||||
//
|
||||
// The normalizer accepts relative file URLs like 'Path\File.JSON' as well as absolute file URLs like
|
||||
// 'C:\Path\file.Yaml'.
|
||||
//
|
||||
// Both are canonicalized with a "file://" scheme, slashes and a lower-cased path:
|
||||
// 'file:///c:/path/file.yaml'
|
||||
//
|
||||
// URLs can be specified with a file scheme, like in 'file:///folder/file.json' or
|
||||
// 'file:///c:\folder\File.json'.
|
||||
//
|
||||
// URLs like file://C:\folder are considered invalid (i.e. there is no host 'c:\folder') and a "repair"
|
||||
// is attempted.
|
||||
//
|
||||
// The base path argument is assumed to be canonicalized (e.g. using normalizeBase()).
|
||||
func normalizeURI(refPath, base string) string {
|
||||
refURL, err := parseURL(refPath)
|
||||
if err != nil {
|
||||
specLogger.Printf("warning: invalid URI in $ref %q: %v", refPath, err)
|
||||
refURL, refPath = repairURI(refPath)
|
||||
}
|
||||
|
||||
fixWindowsURI(refURL, refPath) // noop on non-windows OS
|
||||
|
||||
refURL.Path = path.Clean(refURL.Path)
|
||||
if refURL.Path == "." {
|
||||
refURL.Path = ""
|
||||
}
|
||||
|
||||
r := MustCreateRef(refURL.String())
|
||||
if r.IsCanonical() {
|
||||
return refURL.String()
|
||||
}
|
||||
|
||||
baseURL, _ := parseURL(base)
|
||||
if path.IsAbs(refURL.Path) {
|
||||
baseURL.Path = refURL.Path
|
||||
} else if refURL.Path != "" {
|
||||
baseURL.Path = path.Join(path.Dir(baseURL.Path), refURL.Path)
|
||||
}
|
||||
// copying fragment from ref to base
|
||||
baseURL.Fragment = refURL.Fragment
|
||||
|
||||
return baseURL.String()
|
||||
}
|
||||
|
||||
// denormalizeRef returns the simplest notation for a normalized $ref, given the path of the original root document.
|
||||
//
|
||||
// When calling this, we assume that:
|
||||
// * $ref is a canonical URI
|
||||
// * originalRelativeBase is a canonical URI
|
||||
//
|
||||
// denormalizeRef is currently used when we rewrite a $ref after a circular $ref has been detected.
|
||||
// In this case, expansion stops and normally renders the internal canonical $ref.
|
||||
//
|
||||
// This internal $ref is eventually rebased to the original RelativeBase used for the expansion.
|
||||
//
|
||||
// There is a special case for schemas that are anchored with an "id":
|
||||
// in that case, the rebasing is performed // against the id only if this is an anchor for the initial root document.
|
||||
// All other intermediate "id"'s found along the way are ignored for the purpose of rebasing.
|
||||
func denormalizeRef(ref *Ref, originalRelativeBase, id string) Ref {
|
||||
debugLog("denormalizeRef called:\n$ref: %q\noriginal: %s\nroot ID:%s", ref.String(), originalRelativeBase, id)
|
||||
|
||||
if ref.String() == "" || ref.IsRoot() || ref.HasFragmentOnly {
|
||||
// short circuit: $ref to current doc
|
||||
return *ref
|
||||
}
|
||||
|
||||
if id != "" {
|
||||
idBaseURL, err := parseURL(id)
|
||||
if err == nil { // if the schema id is not usable as a URI, ignore it
|
||||
if ref, ok := rebase(ref, idBaseURL, true); ok { // rebase, but keep references to root unchaged (do not want $ref: "")
|
||||
// $ref relative to the ID of the schema in the root document
|
||||
return ref
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
originalRelativeBaseURL, _ := parseURL(originalRelativeBase)
|
||||
|
||||
r, _ := rebase(ref, originalRelativeBaseURL, false)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func rebase(ref *Ref, v *url.URL, notEqual bool) (Ref, bool) {
|
||||
var newBase url.URL
|
||||
|
||||
u := ref.GetURL()
|
||||
|
||||
if u.Scheme != v.Scheme || u.Host != v.Host {
|
||||
return *ref, false
|
||||
}
|
||||
|
||||
docPath := v.Path
|
||||
v.Path = path.Dir(v.Path)
|
||||
|
||||
if v.Path == "." {
|
||||
v.Path = ""
|
||||
} else if !strings.HasSuffix(v.Path, "/") {
|
||||
v.Path += "/"
|
||||
}
|
||||
|
||||
newBase.Fragment = u.Fragment
|
||||
|
||||
if strings.HasPrefix(u.Path, docPath) {
|
||||
newBase.Path = strings.TrimPrefix(u.Path, docPath)
|
||||
} else {
|
||||
newBase.Path = strings.TrimPrefix(u.Path, v.Path)
|
||||
}
|
||||
|
||||
if notEqual && newBase.Path == "" && newBase.Fragment == "" {
|
||||
// do not want rebasing to end up in an empty $ref
|
||||
return *ref, false
|
||||
}
|
||||
|
||||
if path.IsAbs(newBase.Path) {
|
||||
// whenever we end up with an absolute path, specify the scheme and host
|
||||
newBase.Scheme = v.Scheme
|
||||
newBase.Host = v.Host
|
||||
}
|
||||
|
||||
return MustCreateRef(newBase.String()), true
|
||||
}
|
||||
|
||||
// normalizeRef canonicalize a Ref, using a canonical relativeBase as its absolute anchor
|
||||
func normalizeRef(ref *Ref, relativeBase string) *Ref {
|
||||
r := MustCreateRef(normalizeURI(ref.String(), relativeBase))
|
||||
return &r
|
||||
}
|
||||
|
||||
// normalizeBase performs a normalization of the input base path.
|
||||
//
|
||||
// This always yields a canonical URI (absolute), usable for the document cache.
|
||||
//
|
||||
// It ensures that all further internal work on basePath may safely assume
|
||||
// a non-empty, cross-platform, canonical URI (i.e. absolute).
|
||||
//
|
||||
// This normalization tolerates windows paths (e.g. C:\x\y\File.dat) and transform this
|
||||
// in a file:// URL with lower cased drive letter and path.
|
||||
//
|
||||
// See also: https://en.wikipedia.org/wiki/File_URI_scheme
|
||||
func normalizeBase(in string) string {
|
||||
u, err := parseURL(in)
|
||||
if err != nil {
|
||||
specLogger.Printf("warning: invalid URI in RelativeBase %q: %v", in, err)
|
||||
u, in = repairURI(in)
|
||||
}
|
||||
|
||||
u.Fragment = "" // any fragment in the base is irrelevant
|
||||
|
||||
fixWindowsURI(u, in) // noop on non-windows OS
|
||||
|
||||
u.Path = path.Clean(u.Path)
|
||||
if u.Path == "." { // empty after Clean()
|
||||
u.Path = ""
|
||||
}
|
||||
|
||||
if u.Scheme != "" {
|
||||
if path.IsAbs(u.Path) || u.Scheme != fileScheme {
|
||||
// this is absolute or explicitly not a local file: we're good
|
||||
return u.String()
|
||||
}
|
||||
}
|
||||
|
||||
// no scheme or file scheme with relative path: assume file and make it absolute
|
||||
// enforce scheme file://... with absolute path.
|
||||
//
|
||||
// If the input path is relative, we anchor the path to the current working directory.
|
||||
// NOTE: we may end up with a host component. Leave it unchanged: e.g. file://host/folder/file.json
|
||||
|
||||
u.Scheme = fileScheme
|
||||
u.Path = absPath(u.Path) // platform-dependent
|
||||
u.RawQuery = "" // any query component is irrelevant for a base
|
||||
return u.String()
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// absPath makes a file path absolute and compatible with a URI path component.
|
||||
//
|
||||
// The parameter must be a path, not an URI.
|
||||
func absPath(in string) string {
|
||||
anchored, err := filepath.Abs(in)
|
||||
if err != nil {
|
||||
specLogger.Printf("warning: could not resolve current working directory: %v", err)
|
||||
return in
|
||||
}
|
||||
return anchored
|
||||
}
|
||||
|
||||
func repairURI(in string) (*url.URL, string) {
|
||||
u, _ := parseURL("")
|
||||
debugLog("repaired URI: original: %q, repaired: %q", in, "")
|
||||
return u, ""
|
||||
}
|
||||
|
||||
func fixWindowsURI(_ *url.URL, _ string) {
|
||||
}
|
||||
@ -0,0 +1,154 @@
|
||||
// -build windows
|
||||
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// absPath makes a file path absolute and compatible with a URI path component
|
||||
//
|
||||
// The parameter must be a path, not an URI.
|
||||
func absPath(in string) string {
|
||||
// NOTE(windows): filepath.Abs exhibits a special behavior on windows for empty paths.
|
||||
// See https://github.com/golang/go/issues/24441
|
||||
if in == "" {
|
||||
in = "."
|
||||
}
|
||||
|
||||
anchored, err := filepath.Abs(in)
|
||||
if err != nil {
|
||||
specLogger.Printf("warning: could not resolve current working directory: %v", err)
|
||||
return in
|
||||
}
|
||||
|
||||
pth := strings.ReplaceAll(strings.ToLower(anchored), `\`, `/`)
|
||||
if !strings.HasPrefix(pth, "/") {
|
||||
pth = "/" + pth
|
||||
}
|
||||
|
||||
return path.Clean(pth)
|
||||
}
|
||||
|
||||
// repairURI tolerates invalid file URIs with common typos
|
||||
// such as 'file://E:\folder\file', that break the regular URL parser.
|
||||
//
|
||||
// Adopting the same defaults as for unixes (e.g. return an empty path) would
|
||||
// result into a counter-intuitive result for that case (e.g. E:\folder\file is
|
||||
// eventually resolved as the current directory). The repair will detect the missing "/".
|
||||
//
|
||||
// Note that this only works for the file scheme.
|
||||
func repairURI(in string) (*url.URL, string) {
|
||||
const prefix = fileScheme + "://"
|
||||
if !strings.HasPrefix(in, prefix) {
|
||||
// giving up: resolve to empty path
|
||||
u, _ := parseURL("")
|
||||
|
||||
return u, ""
|
||||
}
|
||||
|
||||
// attempt the repair, stripping the scheme should be sufficient
|
||||
u, _ := parseURL(strings.TrimPrefix(in, prefix))
|
||||
debugLog("repaired URI: original: %q, repaired: %q", in, u.String())
|
||||
|
||||
return u, u.String()
|
||||
}
|
||||
|
||||
// fixWindowsURI tolerates an absolute file path on windows such as C:\Base\File.yaml or \\host\share\Base\File.yaml
|
||||
// and makes it a canonical URI: file:///c:/base/file.yaml
|
||||
//
|
||||
// Catch 22 notes for Windows:
|
||||
//
|
||||
// * There may be a drive letter on windows (it is lower-cased)
|
||||
// * There may be a share UNC, e.g. \\server\folder\data.xml
|
||||
// * Paths are case insensitive
|
||||
// * Paths may already contain slashes
|
||||
// * Paths must be slashed
|
||||
//
|
||||
// NOTE: there is no escaping. "/" may be valid separators just like "\".
|
||||
// We don't use ToSlash() (which escapes everything) because windows now also
|
||||
// tolerates the use of "/". Hence, both C:\File.yaml and C:/File.yaml will work.
|
||||
func fixWindowsURI(u *url.URL, in string) {
|
||||
drive := filepath.VolumeName(in)
|
||||
|
||||
if len(drive) > 0 {
|
||||
if len(u.Scheme) == 1 && strings.EqualFold(u.Scheme, drive[:1]) { // a path with a drive letter
|
||||
u.Scheme = fileScheme
|
||||
u.Host = ""
|
||||
u.Path = strings.Join([]string{drive, u.Opaque, u.Path}, `/`) // reconstruct the full path component (no fragment, no query)
|
||||
} else if u.Host == "" && strings.HasPrefix(u.Path, drive) { // a path with a \\host volume
|
||||
// NOTE: the special host@port syntax for UNC is not supported (yet)
|
||||
u.Scheme = fileScheme
|
||||
|
||||
// this is a modified version of filepath.Dir() to apply on the VolumeName itself
|
||||
i := len(drive) - 1
|
||||
for i >= 0 && !os.IsPathSeparator(drive[i]) {
|
||||
i--
|
||||
}
|
||||
host := drive[:i] // \\host\share => host
|
||||
|
||||
u.Path = strings.TrimPrefix(u.Path, host)
|
||||
u.Host = strings.TrimPrefix(host, `\\`)
|
||||
}
|
||||
|
||||
u.Opaque = ""
|
||||
u.Path = strings.ReplaceAll(strings.ToLower(u.Path), `\`, `/`)
|
||||
|
||||
// ensure we form an absolute path
|
||||
if !strings.HasPrefix(u.Path, "/") {
|
||||
u.Path = "/" + u.Path
|
||||
}
|
||||
|
||||
u.Path = path.Clean(u.Path)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if u.Scheme == fileScheme {
|
||||
// Handle dodgy cases for file://{...} URIs on windows.
|
||||
// A canonical URI should always be followed by an absolute path.
|
||||
//
|
||||
// Examples:
|
||||
// * file:///folder/file => valid, unchanged
|
||||
// * file:///c:\folder\file => slashed
|
||||
// * file:///./folder/file => valid, cleaned to remove the dot
|
||||
// * file:///.\folder\file => remapped to cwd
|
||||
// * file:///. => dodgy, remapped to / (consistent with the behavior on unix)
|
||||
// * file:///.. => dodgy, remapped to / (consistent with the behavior on unix)
|
||||
if (!path.IsAbs(u.Path) && !filepath.IsAbs(u.Path)) || (strings.HasPrefix(u.Path, `/.`) && strings.Contains(u.Path, `\`)) {
|
||||
// ensure we form an absolute path
|
||||
u.Path, _ = filepath.Abs(strings.TrimLeft(u.Path, `/`))
|
||||
if !strings.HasPrefix(u.Path, "/") {
|
||||
u.Path = "/" + u.Path
|
||||
}
|
||||
}
|
||||
u.Path = strings.ToLower(u.Path)
|
||||
}
|
||||
|
||||
// NOTE: lower case normalization does not propagate to inner resources,
|
||||
// generated when rebasing: when joining a relative URI with a file to an absolute base,
|
||||
// only the base is currently lower-cased.
|
||||
//
|
||||
// For now, we assume this is good enough for most use cases
|
||||
// and try not to generate too many differences
|
||||
// between the output produced on different platforms.
|
||||
u.Path = path.Clean(strings.ReplaceAll(u.Path, `\`, `/`))
|
||||
}
|
||||
@ -0,0 +1,400 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(map[string]interface{}{})
|
||||
gob.Register([]interface{}{})
|
||||
}
|
||||
|
||||
// OperationProps describes an operation
|
||||
//
|
||||
// NOTES:
|
||||
// - schemes, when present must be from [http, https, ws, wss]: see validate
|
||||
// - Security is handled as a special case: see MarshalJSON function
|
||||
type OperationProps struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Consumes []string `json:"consumes,omitempty"`
|
||||
Produces []string `json:"produces,omitempty"`
|
||||
Schemes []string `json:"schemes,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"`
|
||||
ID string `json:"operationId,omitempty"`
|
||||
Deprecated bool `json:"deprecated,omitempty"`
|
||||
Security []map[string][]string `json:"security,omitempty"`
|
||||
Parameters []Parameter `json:"parameters,omitempty"`
|
||||
Responses *Responses `json:"responses,omitempty"`
|
||||
}
|
||||
|
||||
// MarshalJSON takes care of serializing operation properties to JSON
|
||||
//
|
||||
// We use a custom marhaller here to handle a special cases related to
|
||||
// the Security field. We need to preserve zero length slice
|
||||
// while omitting the field when the value is nil/unset.
|
||||
func (op OperationProps) MarshalJSON() ([]byte, error) {
|
||||
type Alias OperationProps
|
||||
if op.Security == nil {
|
||||
return json.Marshal(&struct {
|
||||
Security []map[string][]string `json:"security,omitempty"`
|
||||
*Alias
|
||||
}{
|
||||
Security: op.Security,
|
||||
Alias: (*Alias)(&op),
|
||||
})
|
||||
}
|
||||
return json.Marshal(&struct {
|
||||
Security []map[string][]string `json:"security"`
|
||||
*Alias
|
||||
}{
|
||||
Security: op.Security,
|
||||
Alias: (*Alias)(&op),
|
||||
})
|
||||
}
|
||||
|
||||
// Operation describes a single API operation on a path.
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#operationObject
|
||||
type Operation struct {
|
||||
VendorExtensible
|
||||
OperationProps
|
||||
}
|
||||
|
||||
// SuccessResponse gets a success response model
|
||||
func (o *Operation) SuccessResponse() (*Response, int, bool) {
|
||||
if o.Responses == nil {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
responseCodes := make([]int, 0, len(o.Responses.StatusCodeResponses))
|
||||
for k := range o.Responses.StatusCodeResponses {
|
||||
if k >= 200 && k < 300 {
|
||||
responseCodes = append(responseCodes, k)
|
||||
}
|
||||
}
|
||||
if len(responseCodes) > 0 {
|
||||
sort.Ints(responseCodes)
|
||||
v := o.Responses.StatusCodeResponses[responseCodes[0]]
|
||||
return &v, responseCodes[0], true
|
||||
}
|
||||
|
||||
return o.Responses.Default, 0, false
|
||||
}
|
||||
|
||||
// JSONLookup look up a value by the json property name
|
||||
func (o Operation) JSONLookup(token string) (interface{}, error) {
|
||||
if ex, ok := o.Extensions[token]; ok {
|
||||
return &ex, nil
|
||||
}
|
||||
r, _, err := jsonpointer.GetForToken(o.OperationProps, token)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// UnmarshalJSON hydrates this items instance with the data from JSON
|
||||
func (o *Operation) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &o.OperationProps); err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, &o.VendorExtensible)
|
||||
}
|
||||
|
||||
// MarshalJSON converts this items object to JSON
|
||||
func (o Operation) MarshalJSON() ([]byte, error) {
|
||||
b1, err := json.Marshal(o.OperationProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b2, err := json.Marshal(o.VendorExtensible)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
concated := swag.ConcatJSON(b1, b2)
|
||||
return concated, nil
|
||||
}
|
||||
|
||||
// NewOperation creates a new operation instance.
|
||||
// It expects an ID as parameter but not passing an ID is also valid.
|
||||
func NewOperation(id string) *Operation {
|
||||
op := new(Operation)
|
||||
op.ID = id
|
||||
return op
|
||||
}
|
||||
|
||||
// WithID sets the ID property on this operation, allows for chaining.
|
||||
func (o *Operation) WithID(id string) *Operation {
|
||||
o.ID = id
|
||||
return o
|
||||
}
|
||||
|
||||
// WithDescription sets the description on this operation, allows for chaining
|
||||
func (o *Operation) WithDescription(description string) *Operation {
|
||||
o.Description = description
|
||||
return o
|
||||
}
|
||||
|
||||
// WithSummary sets the summary on this operation, allows for chaining
|
||||
func (o *Operation) WithSummary(summary string) *Operation {
|
||||
o.Summary = summary
|
||||
return o
|
||||
}
|
||||
|
||||
// WithExternalDocs sets/removes the external docs for/from this operation.
|
||||
// When you pass empty strings as params the external documents will be removed.
|
||||
// When you pass non-empty string as one value then those values will be used on the external docs object.
|
||||
// So when you pass a non-empty description, you should also pass the url and vice versa.
|
||||
func (o *Operation) WithExternalDocs(description, url string) *Operation {
|
||||
if description == "" && url == "" {
|
||||
o.ExternalDocs = nil
|
||||
return o
|
||||
}
|
||||
|
||||
if o.ExternalDocs == nil {
|
||||
o.ExternalDocs = &ExternalDocumentation{}
|
||||
}
|
||||
o.ExternalDocs.Description = description
|
||||
o.ExternalDocs.URL = url
|
||||
return o
|
||||
}
|
||||
|
||||
// Deprecate marks the operation as deprecated
|
||||
func (o *Operation) Deprecate() *Operation {
|
||||
o.Deprecated = true
|
||||
return o
|
||||
}
|
||||
|
||||
// Undeprecate marks the operation as not deprected
|
||||
func (o *Operation) Undeprecate() *Operation {
|
||||
o.Deprecated = false
|
||||
return o
|
||||
}
|
||||
|
||||
// WithConsumes adds media types for incoming body values
|
||||
func (o *Operation) WithConsumes(mediaTypes ...string) *Operation {
|
||||
o.Consumes = append(o.Consumes, mediaTypes...)
|
||||
return o
|
||||
}
|
||||
|
||||
// WithProduces adds media types for outgoing body values
|
||||
func (o *Operation) WithProduces(mediaTypes ...string) *Operation {
|
||||
o.Produces = append(o.Produces, mediaTypes...)
|
||||
return o
|
||||
}
|
||||
|
||||
// WithTags adds tags for this operation
|
||||
func (o *Operation) WithTags(tags ...string) *Operation {
|
||||
o.Tags = append(o.Tags, tags...)
|
||||
return o
|
||||
}
|
||||
|
||||
// AddParam adds a parameter to this operation, when a parameter for that location
|
||||
// and with that name already exists it will be replaced
|
||||
func (o *Operation) AddParam(param *Parameter) *Operation {
|
||||
if param == nil {
|
||||
return o
|
||||
}
|
||||
|
||||
for i, p := range o.Parameters {
|
||||
if p.Name == param.Name && p.In == param.In {
|
||||
params := make([]Parameter, 0, len(o.Parameters)+1)
|
||||
params = append(params, o.Parameters[:i]...)
|
||||
params = append(params, *param)
|
||||
params = append(params, o.Parameters[i+1:]...)
|
||||
o.Parameters = params
|
||||
|
||||
return o
|
||||
}
|
||||
}
|
||||
|
||||
o.Parameters = append(o.Parameters, *param)
|
||||
return o
|
||||
}
|
||||
|
||||
// RemoveParam removes a parameter from the operation
|
||||
func (o *Operation) RemoveParam(name, in string) *Operation {
|
||||
for i, p := range o.Parameters {
|
||||
if p.Name == name && p.In == in {
|
||||
o.Parameters = append(o.Parameters[:i], o.Parameters[i+1:]...)
|
||||
return o
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// SecuredWith adds a security scope to this operation.
|
||||
func (o *Operation) SecuredWith(name string, scopes ...string) *Operation {
|
||||
o.Security = append(o.Security, map[string][]string{name: scopes})
|
||||
return o
|
||||
}
|
||||
|
||||
// WithDefaultResponse adds a default response to the operation.
|
||||
// Passing a nil value will remove the response
|
||||
func (o *Operation) WithDefaultResponse(response *Response) *Operation {
|
||||
return o.RespondsWith(0, response)
|
||||
}
|
||||
|
||||
// RespondsWith adds a status code response to the operation.
|
||||
// When the code is 0 the value of the response will be used as default response value.
|
||||
// When the value of the response is nil it will be removed from the operation
|
||||
func (o *Operation) RespondsWith(code int, response *Response) *Operation {
|
||||
if o.Responses == nil {
|
||||
o.Responses = new(Responses)
|
||||
}
|
||||
if code == 0 {
|
||||
o.Responses.Default = response
|
||||
return o
|
||||
}
|
||||
if response == nil {
|
||||
delete(o.Responses.StatusCodeResponses, code)
|
||||
return o
|
||||
}
|
||||
if o.Responses.StatusCodeResponses == nil {
|
||||
o.Responses.StatusCodeResponses = make(map[int]Response)
|
||||
}
|
||||
o.Responses.StatusCodeResponses[code] = *response
|
||||
return o
|
||||
}
|
||||
|
||||
type opsAlias OperationProps
|
||||
|
||||
type gobAlias struct {
|
||||
Security []map[string]struct {
|
||||
List []string
|
||||
Pad bool
|
||||
}
|
||||
Alias *opsAlias
|
||||
SecurityIsEmpty bool
|
||||
}
|
||||
|
||||
// GobEncode provides a safe gob encoder for Operation, including empty security requirements
|
||||
func (o Operation) GobEncode() ([]byte, error) {
|
||||
raw := struct {
|
||||
Ext VendorExtensible
|
||||
Props OperationProps
|
||||
}{
|
||||
Ext: o.VendorExtensible,
|
||||
Props: o.OperationProps,
|
||||
}
|
||||
var b bytes.Buffer
|
||||
err := gob.NewEncoder(&b).Encode(raw)
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
// GobDecode provides a safe gob decoder for Operation, including empty security requirements
|
||||
func (o *Operation) GobDecode(b []byte) error {
|
||||
var raw struct {
|
||||
Ext VendorExtensible
|
||||
Props OperationProps
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(b)
|
||||
err := gob.NewDecoder(buf).Decode(&raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.VendorExtensible = raw.Ext
|
||||
o.OperationProps = raw.Props
|
||||
return nil
|
||||
}
|
||||
|
||||
// GobEncode provides a safe gob encoder for Operation, including empty security requirements
|
||||
func (op OperationProps) GobEncode() ([]byte, error) {
|
||||
raw := gobAlias{
|
||||
Alias: (*opsAlias)(&op),
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
if op.Security == nil {
|
||||
// nil security requirement
|
||||
err := gob.NewEncoder(&b).Encode(raw)
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
if len(op.Security) == 0 {
|
||||
// empty, but non-nil security requirement
|
||||
raw.SecurityIsEmpty = true
|
||||
raw.Alias.Security = nil
|
||||
err := gob.NewEncoder(&b).Encode(raw)
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
raw.Security = make([]map[string]struct {
|
||||
List []string
|
||||
Pad bool
|
||||
}, 0, len(op.Security))
|
||||
for _, req := range op.Security {
|
||||
v := make(map[string]struct {
|
||||
List []string
|
||||
Pad bool
|
||||
}, len(req))
|
||||
for k, val := range req {
|
||||
v[k] = struct {
|
||||
List []string
|
||||
Pad bool
|
||||
}{
|
||||
List: val,
|
||||
}
|
||||
}
|
||||
raw.Security = append(raw.Security, v)
|
||||
}
|
||||
|
||||
err := gob.NewEncoder(&b).Encode(raw)
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
// GobDecode provides a safe gob decoder for Operation, including empty security requirements
|
||||
func (op *OperationProps) GobDecode(b []byte) error {
|
||||
var raw gobAlias
|
||||
|
||||
buf := bytes.NewBuffer(b)
|
||||
err := gob.NewDecoder(buf).Decode(&raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if raw.Alias == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case raw.SecurityIsEmpty:
|
||||
// empty, but non-nil security requirement
|
||||
raw.Alias.Security = []map[string][]string{}
|
||||
case len(raw.Alias.Security) == 0:
|
||||
// nil security requirement
|
||||
raw.Alias.Security = nil
|
||||
default:
|
||||
raw.Alias.Security = make([]map[string][]string, 0, len(raw.Security))
|
||||
for _, req := range raw.Security {
|
||||
v := make(map[string][]string, len(req))
|
||||
for k, val := range req {
|
||||
v[k] = make([]string, 0, len(val.List))
|
||||
v[k] = append(v[k], val.List...)
|
||||
}
|
||||
raw.Alias.Security = append(raw.Alias.Security, v)
|
||||
}
|
||||
}
|
||||
|
||||
*op = *(*OperationProps)(raw.Alias)
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,326 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// QueryParam creates a query parameter
|
||||
func QueryParam(name string) *Parameter {
|
||||
return &Parameter{ParamProps: ParamProps{Name: name, In: "query"}}
|
||||
}
|
||||
|
||||
// HeaderParam creates a header parameter, this is always required by default
|
||||
func HeaderParam(name string) *Parameter {
|
||||
return &Parameter{ParamProps: ParamProps{Name: name, In: "header", Required: true}}
|
||||
}
|
||||
|
||||
// PathParam creates a path parameter, this is always required
|
||||
func PathParam(name string) *Parameter {
|
||||
return &Parameter{ParamProps: ParamProps{Name: name, In: "path", Required: true}}
|
||||
}
|
||||
|
||||
// BodyParam creates a body parameter
|
||||
func BodyParam(name string, schema *Schema) *Parameter {
|
||||
return &Parameter{ParamProps: ParamProps{Name: name, In: "body", Schema: schema}}
|
||||
}
|
||||
|
||||
// FormDataParam creates a body parameter
|
||||
func FormDataParam(name string) *Parameter {
|
||||
return &Parameter{ParamProps: ParamProps{Name: name, In: "formData"}}
|
||||
}
|
||||
|
||||
// FileParam creates a body parameter
|
||||
func FileParam(name string) *Parameter {
|
||||
return &Parameter{ParamProps: ParamProps{Name: name, In: "formData"},
|
||||
SimpleSchema: SimpleSchema{Type: "file"}}
|
||||
}
|
||||
|
||||
// SimpleArrayParam creates a param for a simple array (string, int, date etc)
|
||||
func SimpleArrayParam(name, tpe, fmt string) *Parameter {
|
||||
return &Parameter{ParamProps: ParamProps{Name: name},
|
||||
SimpleSchema: SimpleSchema{Type: jsonArray, CollectionFormat: "csv",
|
||||
Items: &Items{SimpleSchema: SimpleSchema{Type: tpe, Format: fmt}}}}
|
||||
}
|
||||
|
||||
// ParamRef creates a parameter that's a json reference
|
||||
func ParamRef(uri string) *Parameter {
|
||||
p := new(Parameter)
|
||||
p.Ref = MustCreateRef(uri)
|
||||
return p
|
||||
}
|
||||
|
||||
// ParamProps describes the specific attributes of an operation parameter
|
||||
//
|
||||
// NOTE:
|
||||
// - Schema is defined when "in" == "body": see validate
|
||||
// - AllowEmptyValue is allowed where "in" == "query" || "formData"
|
||||
type ParamProps struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
In string `json:"in,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
Schema *Schema `json:"schema,omitempty"`
|
||||
AllowEmptyValue bool `json:"allowEmptyValue,omitempty"`
|
||||
}
|
||||
|
||||
// Parameter a unique parameter is defined by a combination of a [name](#parameterName) and [location](#parameterIn).
|
||||
//
|
||||
// There are five possible parameter types.
|
||||
// - Path - Used together with [Path Templating](#pathTemplating), where the parameter value is actually part
|
||||
// of the operation's URL. This does not include the host or base path of the API. For example, in `/items/{itemId}`,
|
||||
// the path parameter is `itemId`.
|
||||
// - Query - Parameters that are appended to the URL. For example, in `/items?id=###`, the query parameter is `id`.
|
||||
// - Header - Custom headers that are expected as part of the request.
|
||||
// - Body - The payload that's appended to the HTTP request. Since there can only be one payload, there can only be
|
||||
// _one_ body parameter. The name of the body parameter has no effect on the parameter itself and is used for
|
||||
// documentation purposes only. Since Form parameters are also in the payload, body and form parameters cannot exist
|
||||
// together for the same operation.
|
||||
// - Form - Used to describe the payload of an HTTP request when either `application/x-www-form-urlencoded` or
|
||||
// `multipart/form-data` are used as the content type of the request (in Swagger's definition,
|
||||
// the [`consumes`](#operationConsumes) property of an operation). This is the only parameter type that can be used
|
||||
// to send files, thus supporting the `file` type. Since form parameters are sent in the payload, they cannot be
|
||||
// declared together with a body parameter for the same operation. Form parameters have a different format based on
|
||||
// the content-type used (for further details, consult http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4).
|
||||
// - `application/x-www-form-urlencoded` - Similar to the format of Query parameters but as a payload.
|
||||
// For example, `foo=1&bar=swagger` - both `foo` and `bar` are form parameters. This is normally used for simple
|
||||
// parameters that are being transferred.
|
||||
// - `multipart/form-data` - each parameter takes a section in the payload with an internal header.
|
||||
// For example, for the header `Content-Disposition: form-data; name="submit-name"` the name of the parameter is
|
||||
// `submit-name`. This type of form parameters is more commonly used for file transfers.
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#parameterObject
|
||||
type Parameter struct {
|
||||
Refable
|
||||
CommonValidations
|
||||
SimpleSchema
|
||||
VendorExtensible
|
||||
ParamProps
|
||||
}
|
||||
|
||||
// JSONLookup look up a value by the json property name
|
||||
func (p Parameter) JSONLookup(token string) (interface{}, error) {
|
||||
if ex, ok := p.Extensions[token]; ok {
|
||||
return &ex, nil
|
||||
}
|
||||
if token == jsonRef {
|
||||
return &p.Ref, nil
|
||||
}
|
||||
|
||||
r, _, err := jsonpointer.GetForToken(p.CommonValidations, token)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
|
||||
return nil, err
|
||||
}
|
||||
if r != nil {
|
||||
return r, nil
|
||||
}
|
||||
r, _, err = jsonpointer.GetForToken(p.SimpleSchema, token)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
|
||||
return nil, err
|
||||
}
|
||||
if r != nil {
|
||||
return r, nil
|
||||
}
|
||||
r, _, err = jsonpointer.GetForToken(p.ParamProps, token)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// WithDescription a fluent builder method for the description of the parameter
|
||||
func (p *Parameter) WithDescription(description string) *Parameter {
|
||||
p.Description = description
|
||||
return p
|
||||
}
|
||||
|
||||
// Named a fluent builder method to override the name of the parameter
|
||||
func (p *Parameter) Named(name string) *Parameter {
|
||||
p.Name = name
|
||||
return p
|
||||
}
|
||||
|
||||
// WithLocation a fluent builder method to override the location of the parameter
|
||||
func (p *Parameter) WithLocation(in string) *Parameter {
|
||||
p.In = in
|
||||
return p
|
||||
}
|
||||
|
||||
// Typed a fluent builder method for the type of the parameter value
|
||||
func (p *Parameter) Typed(tpe, format string) *Parameter {
|
||||
p.Type = tpe
|
||||
p.Format = format
|
||||
return p
|
||||
}
|
||||
|
||||
// CollectionOf a fluent builder method for an array parameter
|
||||
func (p *Parameter) CollectionOf(items *Items, format string) *Parameter {
|
||||
p.Type = jsonArray
|
||||
p.Items = items
|
||||
p.CollectionFormat = format
|
||||
return p
|
||||
}
|
||||
|
||||
// WithDefault sets the default value on this parameter
|
||||
func (p *Parameter) WithDefault(defaultValue interface{}) *Parameter {
|
||||
p.AsOptional() // with default implies optional
|
||||
p.Default = defaultValue
|
||||
return p
|
||||
}
|
||||
|
||||
// AllowsEmptyValues flags this parameter as being ok with empty values
|
||||
func (p *Parameter) AllowsEmptyValues() *Parameter {
|
||||
p.AllowEmptyValue = true
|
||||
return p
|
||||
}
|
||||
|
||||
// NoEmptyValues flags this parameter as not liking empty values
|
||||
func (p *Parameter) NoEmptyValues() *Parameter {
|
||||
p.AllowEmptyValue = false
|
||||
return p
|
||||
}
|
||||
|
||||
// AsOptional flags this parameter as optional
|
||||
func (p *Parameter) AsOptional() *Parameter {
|
||||
p.Required = false
|
||||
return p
|
||||
}
|
||||
|
||||
// AsRequired flags this parameter as required
|
||||
func (p *Parameter) AsRequired() *Parameter {
|
||||
if p.Default != nil { // with a default required makes no sense
|
||||
return p
|
||||
}
|
||||
p.Required = true
|
||||
return p
|
||||
}
|
||||
|
||||
// WithMaxLength sets a max length value
|
||||
func (p *Parameter) WithMaxLength(max int64) *Parameter {
|
||||
p.MaxLength = &max
|
||||
return p
|
||||
}
|
||||
|
||||
// WithMinLength sets a min length value
|
||||
func (p *Parameter) WithMinLength(min int64) *Parameter {
|
||||
p.MinLength = &min
|
||||
return p
|
||||
}
|
||||
|
||||
// WithPattern sets a pattern value
|
||||
func (p *Parameter) WithPattern(pattern string) *Parameter {
|
||||
p.Pattern = pattern
|
||||
return p
|
||||
}
|
||||
|
||||
// WithMultipleOf sets a multiple of value
|
||||
func (p *Parameter) WithMultipleOf(number float64) *Parameter {
|
||||
p.MultipleOf = &number
|
||||
return p
|
||||
}
|
||||
|
||||
// WithMaximum sets a maximum number value
|
||||
func (p *Parameter) WithMaximum(max float64, exclusive bool) *Parameter {
|
||||
p.Maximum = &max
|
||||
p.ExclusiveMaximum = exclusive
|
||||
return p
|
||||
}
|
||||
|
||||
// WithMinimum sets a minimum number value
|
||||
func (p *Parameter) WithMinimum(min float64, exclusive bool) *Parameter {
|
||||
p.Minimum = &min
|
||||
p.ExclusiveMinimum = exclusive
|
||||
return p
|
||||
}
|
||||
|
||||
// WithEnum sets a the enum values (replace)
|
||||
func (p *Parameter) WithEnum(values ...interface{}) *Parameter {
|
||||
p.Enum = append([]interface{}{}, values...)
|
||||
return p
|
||||
}
|
||||
|
||||
// WithMaxItems sets the max items
|
||||
func (p *Parameter) WithMaxItems(size int64) *Parameter {
|
||||
p.MaxItems = &size
|
||||
return p
|
||||
}
|
||||
|
||||
// WithMinItems sets the min items
|
||||
func (p *Parameter) WithMinItems(size int64) *Parameter {
|
||||
p.MinItems = &size
|
||||
return p
|
||||
}
|
||||
|
||||
// UniqueValues dictates that this array can only have unique items
|
||||
func (p *Parameter) UniqueValues() *Parameter {
|
||||
p.UniqueItems = true
|
||||
return p
|
||||
}
|
||||
|
||||
// AllowDuplicates this array can have duplicates
|
||||
func (p *Parameter) AllowDuplicates() *Parameter {
|
||||
p.UniqueItems = false
|
||||
return p
|
||||
}
|
||||
|
||||
// WithValidations is a fluent method to set parameter validations
|
||||
func (p *Parameter) WithValidations(val CommonValidations) *Parameter {
|
||||
p.SetValidations(SchemaValidations{CommonValidations: val})
|
||||
return p
|
||||
}
|
||||
|
||||
// UnmarshalJSON hydrates this items instance with the data from JSON
|
||||
func (p *Parameter) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &p.CommonValidations); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(data, &p.Refable); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(data, &p.SimpleSchema); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(data, &p.VendorExtensible); err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, &p.ParamProps)
|
||||
}
|
||||
|
||||
// MarshalJSON converts this items object to JSON
|
||||
func (p Parameter) MarshalJSON() ([]byte, error) {
|
||||
b1, err := json.Marshal(p.CommonValidations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b2, err := json.Marshal(p.SimpleSchema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b3, err := json.Marshal(p.Refable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b4, err := json.Marshal(p.VendorExtensible)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b5, err := json.Marshal(p.ParamProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return swag.ConcatJSON(b3, b1, b2, b4, b5), nil
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// PathItemProps the path item specific properties
|
||||
type PathItemProps struct {
|
||||
Get *Operation `json:"get,omitempty"`
|
||||
Put *Operation `json:"put,omitempty"`
|
||||
Post *Operation `json:"post,omitempty"`
|
||||
Delete *Operation `json:"delete,omitempty"`
|
||||
Options *Operation `json:"options,omitempty"`
|
||||
Head *Operation `json:"head,omitempty"`
|
||||
Patch *Operation `json:"patch,omitempty"`
|
||||
Parameters []Parameter `json:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
// PathItem describes the operations available on a single path.
|
||||
// A Path Item may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
|
||||
// The path itself is still exposed to the documentation viewer but they will
|
||||
// not know which operations and parameters are available.
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#pathItemObject
|
||||
type PathItem struct {
|
||||
Refable
|
||||
VendorExtensible
|
||||
PathItemProps
|
||||
}
|
||||
|
||||
// JSONLookup look up a value by the json property name
|
||||
func (p PathItem) JSONLookup(token string) (interface{}, error) {
|
||||
if ex, ok := p.Extensions[token]; ok {
|
||||
return &ex, nil
|
||||
}
|
||||
if token == jsonRef {
|
||||
return &p.Ref, nil
|
||||
}
|
||||
r, _, err := jsonpointer.GetForToken(p.PathItemProps, token)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// UnmarshalJSON hydrates this items instance with the data from JSON
|
||||
func (p *PathItem) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &p.Refable); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(data, &p.VendorExtensible); err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, &p.PathItemProps)
|
||||
}
|
||||
|
||||
// MarshalJSON converts this items object to JSON
|
||||
func (p PathItem) MarshalJSON() ([]byte, error) {
|
||||
b3, err := json.Marshal(p.Refable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b4, err := json.Marshal(p.VendorExtensible)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b5, err := json.Marshal(p.PathItemProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
concated := swag.ConcatJSON(b3, b4, b5)
|
||||
return concated, nil
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// Paths holds the relative paths to the individual endpoints.
|
||||
// The path is appended to the [`basePath`](http://goo.gl/8us55a#swaggerBasePath) in order
|
||||
// to construct the full URL.
|
||||
// The Paths may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#pathsObject
|
||||
type Paths struct {
|
||||
VendorExtensible
|
||||
Paths map[string]PathItem `json:"-"` // custom serializer to flatten this, each entry must start with "/"
|
||||
}
|
||||
|
||||
// JSONLookup look up a value by the json property name
|
||||
func (p Paths) JSONLookup(token string) (interface{}, error) {
|
||||
if pi, ok := p.Paths[token]; ok {
|
||||
return &pi, nil
|
||||
}
|
||||
if ex, ok := p.Extensions[token]; ok {
|
||||
return &ex, nil
|
||||
}
|
||||
return nil, fmt.Errorf("object has no field %q", token)
|
||||
}
|
||||
|
||||
// UnmarshalJSON hydrates this items instance with the data from JSON
|
||||
func (p *Paths) UnmarshalJSON(data []byte) error {
|
||||
var res map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range res {
|
||||
if strings.HasPrefix(strings.ToLower(k), "x-") {
|
||||
if p.Extensions == nil {
|
||||
p.Extensions = make(map[string]interface{})
|
||||
}
|
||||
var d interface{}
|
||||
if err := json.Unmarshal(v, &d); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Extensions[k] = d
|
||||
}
|
||||
if strings.HasPrefix(k, "/") {
|
||||
if p.Paths == nil {
|
||||
p.Paths = make(map[string]PathItem)
|
||||
}
|
||||
var pi PathItem
|
||||
if err := json.Unmarshal(v, &pi); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Paths[k] = pi
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON converts this items object to JSON
|
||||
func (p Paths) MarshalJSON() ([]byte, error) {
|
||||
b1, err := json.Marshal(p.VendorExtensible)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pths := make(map[string]PathItem)
|
||||
for k, v := range p.Paths {
|
||||
if strings.HasPrefix(k, "/") {
|
||||
pths[k] = v
|
||||
}
|
||||
}
|
||||
b2, err := json.Marshal(pths)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
concated := swag.ConcatJSON(b1, b2)
|
||||
return concated, nil
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// OrderSchemaItem holds a named schema (e.g. from a property of an object)
|
||||
type OrderSchemaItem struct {
|
||||
Name string
|
||||
Schema
|
||||
}
|
||||
|
||||
// OrderSchemaItems is a sortable slice of named schemas.
|
||||
// The ordering is defined by the x-order schema extension.
|
||||
type OrderSchemaItems []OrderSchemaItem
|
||||
|
||||
// MarshalJSON produces a json object with keys defined by the name schemas
|
||||
// of the OrderSchemaItems slice, keeping the original order of the slice.
|
||||
func (items OrderSchemaItems) MarshalJSON() ([]byte, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.WriteString("{")
|
||||
for i := range items {
|
||||
if i > 0 {
|
||||
buf.WriteString(",")
|
||||
}
|
||||
buf.WriteString("\"")
|
||||
buf.WriteString(items[i].Name)
|
||||
buf.WriteString("\":")
|
||||
bs, err := json.Marshal(&items[i].Schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf.Write(bs)
|
||||
}
|
||||
buf.WriteString("}")
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (items OrderSchemaItems) Len() int { return len(items) }
|
||||
func (items OrderSchemaItems) Swap(i, j int) { items[i], items[j] = items[j], items[i] }
|
||||
func (items OrderSchemaItems) Less(i, j int) (ret bool) {
|
||||
ii, oki := items[i].Extensions.GetInt("x-order")
|
||||
ij, okj := items[j].Extensions.GetInt("x-order")
|
||||
if oki {
|
||||
if okj {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
defer func() {
|
||||
if err = recover(); err != nil {
|
||||
ret = items[i].Name < items[j].Name
|
||||
}
|
||||
}()
|
||||
ret = reflect.ValueOf(ii).String() < reflect.ValueOf(ij).String()
|
||||
}
|
||||
}()
|
||||
return ii < ij
|
||||
}
|
||||
return true
|
||||
} else if okj {
|
||||
return false
|
||||
}
|
||||
return items[i].Name < items[j].Name
|
||||
}
|
||||
|
||||
// SchemaProperties is a map representing the properties of a Schema object.
|
||||
// It knows how to transform its keys into an ordered slice.
|
||||
type SchemaProperties map[string]Schema
|
||||
|
||||
// ToOrderedSchemaItems transforms the map of properties into a sortable slice
|
||||
func (properties SchemaProperties) ToOrderedSchemaItems() OrderSchemaItems {
|
||||
items := make(OrderSchemaItems, 0, len(properties))
|
||||
for k, v := range properties {
|
||||
items = append(items, OrderSchemaItem{
|
||||
Name: k,
|
||||
Schema: v,
|
||||
})
|
||||
}
|
||||
sort.Sort(items)
|
||||
return items
|
||||
}
|
||||
|
||||
// MarshalJSON produces properties as json, keeping their order.
|
||||
func (properties SchemaProperties) MarshalJSON() ([]byte, error) {
|
||||
if properties == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return json.Marshal(properties.ToOrderedSchemaItems())
|
||||
}
|
||||
@ -0,0 +1,193 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-openapi/jsonreference"
|
||||
)
|
||||
|
||||
// Refable is a struct for things that accept a $ref property
|
||||
type Refable struct {
|
||||
Ref Ref
|
||||
}
|
||||
|
||||
// MarshalJSON marshals the ref to json
|
||||
func (r Refable) MarshalJSON() ([]byte, error) {
|
||||
return r.Ref.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshalss the ref from json
|
||||
func (r *Refable) UnmarshalJSON(d []byte) error {
|
||||
return json.Unmarshal(d, &r.Ref)
|
||||
}
|
||||
|
||||
// Ref represents a json reference that is potentially resolved
|
||||
type Ref struct {
|
||||
jsonreference.Ref
|
||||
}
|
||||
|
||||
// RemoteURI gets the remote uri part of the ref
|
||||
func (r *Ref) RemoteURI() string {
|
||||
if r.String() == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
u := *r.GetURL()
|
||||
u.Fragment = ""
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// IsValidURI returns true when the url the ref points to can be found
|
||||
func (r *Ref) IsValidURI(basepaths ...string) bool {
|
||||
if r.String() == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
v := r.RemoteURI()
|
||||
if v == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
if r.HasFullURL {
|
||||
//nolint:noctx,gosec
|
||||
rr, err := http.Get(v)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer rr.Body.Close()
|
||||
|
||||
return rr.StatusCode/100 == 2
|
||||
}
|
||||
|
||||
if !(r.HasFileScheme || r.HasFullFilePath || r.HasURLPathOnly) {
|
||||
return false
|
||||
}
|
||||
|
||||
// check for local file
|
||||
pth := v
|
||||
if r.HasURLPathOnly {
|
||||
base := "."
|
||||
if len(basepaths) > 0 {
|
||||
base = filepath.Dir(filepath.Join(basepaths...))
|
||||
}
|
||||
p, e := filepath.Abs(filepath.ToSlash(filepath.Join(base, pth)))
|
||||
if e != nil {
|
||||
return false
|
||||
}
|
||||
pth = p
|
||||
}
|
||||
|
||||
fi, err := os.Stat(filepath.ToSlash(pth))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return !fi.IsDir()
|
||||
}
|
||||
|
||||
// Inherits creates a new reference from a parent and a child
|
||||
// If the child cannot inherit from the parent, an error is returned
|
||||
func (r *Ref) Inherits(child Ref) (*Ref, error) {
|
||||
ref, err := r.Ref.Inherits(child.Ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Ref{Ref: *ref}, nil
|
||||
}
|
||||
|
||||
// NewRef creates a new instance of a ref object
|
||||
// returns an error when the reference uri is an invalid uri
|
||||
func NewRef(refURI string) (Ref, error) {
|
||||
ref, err := jsonreference.New(refURI)
|
||||
if err != nil {
|
||||
return Ref{}, err
|
||||
}
|
||||
return Ref{Ref: ref}, nil
|
||||
}
|
||||
|
||||
// MustCreateRef creates a ref object but panics when refURI is invalid.
|
||||
// Use the NewRef method for a version that returns an error.
|
||||
func MustCreateRef(refURI string) Ref {
|
||||
return Ref{Ref: jsonreference.MustCreateRef(refURI)}
|
||||
}
|
||||
|
||||
// MarshalJSON marshals this ref into a JSON object
|
||||
func (r Ref) MarshalJSON() ([]byte, error) {
|
||||
str := r.String()
|
||||
if str == "" {
|
||||
if r.IsRoot() {
|
||||
return []byte(`{"$ref":""}`), nil
|
||||
}
|
||||
return []byte("{}"), nil
|
||||
}
|
||||
v := map[string]interface{}{"$ref": str}
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals this ref from a JSON object
|
||||
func (r *Ref) UnmarshalJSON(d []byte) error {
|
||||
var v map[string]interface{}
|
||||
if err := json.Unmarshal(d, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.fromMap(v)
|
||||
}
|
||||
|
||||
// GobEncode provides a safe gob encoder for Ref
|
||||
func (r Ref) GobEncode() ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
raw, err := r.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = gob.NewEncoder(&b).Encode(raw)
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
// GobDecode provides a safe gob decoder for Ref
|
||||
func (r *Ref) GobDecode(b []byte) error {
|
||||
var raw []byte
|
||||
buf := bytes.NewBuffer(b)
|
||||
err := gob.NewDecoder(buf).Decode(&raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(raw, r)
|
||||
}
|
||||
|
||||
func (r *Ref) fromMap(v map[string]interface{}) error {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if vv, ok := v["$ref"]; ok {
|
||||
if str, ok := vv.(string); ok {
|
||||
ref, err := jsonreference.New(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*r = Ref{Ref: ref}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,127 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
func resolveAnyWithBase(root interface{}, ref *Ref, result interface{}, options *ExpandOptions) error {
|
||||
options = optionsOrDefault(options)
|
||||
resolver := defaultSchemaLoader(root, options, nil, nil)
|
||||
|
||||
if err := resolver.Resolve(ref, result, options.RelativeBase); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResolveRefWithBase resolves a reference against a context root with preservation of base path
|
||||
func ResolveRefWithBase(root interface{}, ref *Ref, options *ExpandOptions) (*Schema, error) {
|
||||
result := new(Schema)
|
||||
|
||||
if err := resolveAnyWithBase(root, ref, result, options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ResolveRef resolves a reference for a schema against a context root
|
||||
// ref is guaranteed to be in root (no need to go to external files)
|
||||
//
|
||||
// ResolveRef is ONLY called from the code generation module
|
||||
func ResolveRef(root interface{}, ref *Ref) (*Schema, error) {
|
||||
res, _, err := ref.GetPointer().Get(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch sch := res.(type) {
|
||||
case Schema:
|
||||
return &sch, nil
|
||||
case *Schema:
|
||||
return sch, nil
|
||||
case map[string]interface{}:
|
||||
newSch := new(Schema)
|
||||
if err = swag.DynamicJSONToStruct(sch, newSch); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newSch, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("type: %T: %w", sch, ErrUnknownTypeForReference)
|
||||
}
|
||||
}
|
||||
|
||||
// ResolveParameterWithBase resolves a parameter reference against a context root and base path
|
||||
func ResolveParameterWithBase(root interface{}, ref Ref, options *ExpandOptions) (*Parameter, error) {
|
||||
result := new(Parameter)
|
||||
|
||||
if err := resolveAnyWithBase(root, &ref, result, options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ResolveParameter resolves a parameter reference against a context root
|
||||
func ResolveParameter(root interface{}, ref Ref) (*Parameter, error) {
|
||||
return ResolveParameterWithBase(root, ref, nil)
|
||||
}
|
||||
|
||||
// ResolveResponseWithBase resolves response a reference against a context root and base path
|
||||
func ResolveResponseWithBase(root interface{}, ref Ref, options *ExpandOptions) (*Response, error) {
|
||||
result := new(Response)
|
||||
|
||||
err := resolveAnyWithBase(root, &ref, result, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ResolveResponse resolves response a reference against a context root
|
||||
func ResolveResponse(root interface{}, ref Ref) (*Response, error) {
|
||||
return ResolveResponseWithBase(root, ref, nil)
|
||||
}
|
||||
|
||||
// ResolvePathItemWithBase resolves response a path item against a context root and base path
|
||||
func ResolvePathItemWithBase(root interface{}, ref Ref, options *ExpandOptions) (*PathItem, error) {
|
||||
result := new(PathItem)
|
||||
|
||||
if err := resolveAnyWithBase(root, &ref, result, options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ResolvePathItem resolves response a path item against a context root and base path
|
||||
//
|
||||
// Deprecated: use ResolvePathItemWithBase instead
|
||||
func ResolvePathItem(root interface{}, ref Ref, options *ExpandOptions) (*PathItem, error) {
|
||||
return ResolvePathItemWithBase(root, ref, options)
|
||||
}
|
||||
|
||||
// ResolveItemsWithBase resolves parameter items reference against a context root and base path.
|
||||
//
|
||||
// NOTE: stricly speaking, this construct is not supported by Swagger 2.0.
|
||||
// Similarly, $ref are forbidden in response headers.
|
||||
func ResolveItemsWithBase(root interface{}, ref Ref, options *ExpandOptions) (*Items, error) {
|
||||
result := new(Items)
|
||||
|
||||
if err := resolveAnyWithBase(root, &ref, result, options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ResolveItems resolves parameter items reference against a context root and base path.
|
||||
//
|
||||
// Deprecated: use ResolveItemsWithBase instead
|
||||
func ResolveItems(root interface{}, ref Ref, options *ExpandOptions) (*Items, error) {
|
||||
return ResolveItemsWithBase(root, ref, options)
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// ResponseProps properties specific to a response
|
||||
type ResponseProps struct {
|
||||
Description string `json:"description"`
|
||||
Schema *Schema `json:"schema,omitempty"`
|
||||
Headers map[string]Header `json:"headers,omitempty"`
|
||||
Examples map[string]interface{} `json:"examples,omitempty"`
|
||||
}
|
||||
|
||||
// Response describes a single response from an API Operation.
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#responseObject
|
||||
type Response struct {
|
||||
Refable
|
||||
ResponseProps
|
||||
VendorExtensible
|
||||
}
|
||||
|
||||
// JSONLookup look up a value by the json property name
|
||||
func (r Response) JSONLookup(token string) (interface{}, error) {
|
||||
if ex, ok := r.Extensions[token]; ok {
|
||||
return &ex, nil
|
||||
}
|
||||
if token == "$ref" {
|
||||
return &r.Ref, nil
|
||||
}
|
||||
ptr, _, err := jsonpointer.GetForToken(r.ResponseProps, token)
|
||||
return ptr, err
|
||||
}
|
||||
|
||||
// UnmarshalJSON hydrates this items instance with the data from JSON
|
||||
func (r *Response) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &r.ResponseProps); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(data, &r.Refable); err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, &r.VendorExtensible)
|
||||
}
|
||||
|
||||
// MarshalJSON converts this items object to JSON
|
||||
func (r Response) MarshalJSON() ([]byte, error) {
|
||||
var (
|
||||
b1 []byte
|
||||
err error
|
||||
)
|
||||
|
||||
if r.Ref.String() == "" {
|
||||
// when there is no $ref, empty description is rendered as an empty string
|
||||
b1, err = json.Marshal(r.ResponseProps)
|
||||
} else {
|
||||
// when there is $ref inside the schema, description should be omitempty-ied
|
||||
b1, err = json.Marshal(struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Schema *Schema `json:"schema,omitempty"`
|
||||
Headers map[string]Header `json:"headers,omitempty"`
|
||||
Examples map[string]interface{} `json:"examples,omitempty"`
|
||||
}{
|
||||
Description: r.ResponseProps.Description,
|
||||
Schema: r.ResponseProps.Schema,
|
||||
Examples: r.ResponseProps.Examples,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b2, err := json.Marshal(r.Refable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b3, err := json.Marshal(r.VendorExtensible)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return swag.ConcatJSON(b1, b2, b3), nil
|
||||
}
|
||||
|
||||
// NewResponse creates a new response instance
|
||||
func NewResponse() *Response {
|
||||
return new(Response)
|
||||
}
|
||||
|
||||
// ResponseRef creates a response as a json reference
|
||||
func ResponseRef(url string) *Response {
|
||||
resp := NewResponse()
|
||||
resp.Ref = MustCreateRef(url)
|
||||
return resp
|
||||
}
|
||||
|
||||
// WithDescription sets the description on this response, allows for chaining
|
||||
func (r *Response) WithDescription(description string) *Response {
|
||||
r.Description = description
|
||||
return r
|
||||
}
|
||||
|
||||
// WithSchema sets the schema on this response, allows for chaining.
|
||||
// Passing a nil argument removes the schema from this response
|
||||
func (r *Response) WithSchema(schema *Schema) *Response {
|
||||
r.Schema = schema
|
||||
return r
|
||||
}
|
||||
|
||||
// AddHeader adds a header to this response
|
||||
func (r *Response) AddHeader(name string, header *Header) *Response {
|
||||
if header == nil {
|
||||
return r.RemoveHeader(name)
|
||||
}
|
||||
if r.Headers == nil {
|
||||
r.Headers = make(map[string]Header)
|
||||
}
|
||||
r.Headers[name] = *header
|
||||
return r
|
||||
}
|
||||
|
||||
// RemoveHeader removes a header from this response
|
||||
func (r *Response) RemoveHeader(name string) *Response {
|
||||
delete(r.Headers, name)
|
||||
return r
|
||||
}
|
||||
|
||||
// AddExample adds an example to this response
|
||||
func (r *Response) AddExample(mediaType string, example interface{}) *Response {
|
||||
if r.Examples == nil {
|
||||
r.Examples = make(map[string]interface{})
|
||||
}
|
||||
r.Examples[mediaType] = example
|
||||
return r
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// Responses is a container for the expected responses of an operation.
|
||||
// The container maps a HTTP response code to the expected response.
|
||||
// It is not expected from the documentation to necessarily cover all possible HTTP response codes,
|
||||
// since they may not be known in advance. However, it is expected from the documentation to cover
|
||||
// a successful operation response and any known errors.
|
||||
//
|
||||
// The `default` can be used a default response object for all HTTP codes that are not covered
|
||||
// individually by the specification.
|
||||
//
|
||||
// The `Responses Object` MUST contain at least one response code, and it SHOULD be the response
|
||||
// for a successful operation call.
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#responsesObject
|
||||
type Responses struct {
|
||||
VendorExtensible
|
||||
ResponsesProps
|
||||
}
|
||||
|
||||
// JSONLookup implements an interface to customize json pointer lookup
|
||||
func (r Responses) JSONLookup(token string) (interface{}, error) {
|
||||
if token == "default" {
|
||||
return r.Default, nil
|
||||
}
|
||||
if ex, ok := r.Extensions[token]; ok {
|
||||
return &ex, nil
|
||||
}
|
||||
if i, err := strconv.Atoi(token); err == nil {
|
||||
if scr, ok := r.StatusCodeResponses[i]; ok {
|
||||
return scr, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("object has no field %q", token)
|
||||
}
|
||||
|
||||
// UnmarshalJSON hydrates this items instance with the data from JSON
|
||||
func (r *Responses) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &r.ResponsesProps); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &r.VendorExtensible); err != nil {
|
||||
return err
|
||||
}
|
||||
if reflect.DeepEqual(ResponsesProps{}, r.ResponsesProps) {
|
||||
r.ResponsesProps = ResponsesProps{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON converts this items object to JSON
|
||||
func (r Responses) MarshalJSON() ([]byte, error) {
|
||||
b1, err := json.Marshal(r.ResponsesProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b2, err := json.Marshal(r.VendorExtensible)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
concated := swag.ConcatJSON(b1, b2)
|
||||
return concated, nil
|
||||
}
|
||||
|
||||
// ResponsesProps describes all responses for an operation.
|
||||
// It tells what is the default response and maps all responses with a
|
||||
// HTTP status code.
|
||||
type ResponsesProps struct {
|
||||
Default *Response
|
||||
StatusCodeResponses map[int]Response
|
||||
}
|
||||
|
||||
// MarshalJSON marshals responses as JSON
|
||||
func (r ResponsesProps) MarshalJSON() ([]byte, error) {
|
||||
toser := map[string]Response{}
|
||||
if r.Default != nil {
|
||||
toser["default"] = *r.Default
|
||||
}
|
||||
for k, v := range r.StatusCodeResponses {
|
||||
toser[strconv.Itoa(k)] = v
|
||||
}
|
||||
return json.Marshal(toser)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals responses from JSON
|
||||
func (r *ResponsesProps) UnmarshalJSON(data []byte) error {
|
||||
var res map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v, ok := res["default"]; ok {
|
||||
var defaultRes Response
|
||||
if err := json.Unmarshal(v, &defaultRes); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Default = &defaultRes
|
||||
delete(res, "default")
|
||||
}
|
||||
for k, v := range res {
|
||||
if !strings.HasPrefix(k, "x-") {
|
||||
var statusCodeResp Response
|
||||
if err := json.Unmarshal(v, &statusCodeResp); err != nil {
|
||||
return err
|
||||
}
|
||||
if nk, err := strconv.Atoi(k); err == nil {
|
||||
if r.StatusCodeResponses == nil {
|
||||
r.StatusCodeResponses = map[int]Response{}
|
||||
}
|
||||
r.StatusCodeResponses[nk] = statusCodeResp
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,645 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// BooleanProperty creates a boolean property
|
||||
func BooleanProperty() *Schema {
|
||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"boolean"}}}
|
||||
}
|
||||
|
||||
// BoolProperty creates a boolean property
|
||||
func BoolProperty() *Schema { return BooleanProperty() }
|
||||
|
||||
// StringProperty creates a string property
|
||||
func StringProperty() *Schema {
|
||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}}}
|
||||
}
|
||||
|
||||
// CharProperty creates a string property
|
||||
func CharProperty() *Schema {
|
||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}}}
|
||||
}
|
||||
|
||||
// Float64Property creates a float64/double property
|
||||
func Float64Property() *Schema {
|
||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"number"}, Format: "double"}}
|
||||
}
|
||||
|
||||
// Float32Property creates a float32/float property
|
||||
func Float32Property() *Schema {
|
||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"number"}, Format: "float"}}
|
||||
}
|
||||
|
||||
// Int8Property creates an int8 property
|
||||
func Int8Property() *Schema {
|
||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int8"}}
|
||||
}
|
||||
|
||||
// Int16Property creates an int16 property
|
||||
func Int16Property() *Schema {
|
||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int16"}}
|
||||
}
|
||||
|
||||
// Int32Property creates an int32 property
|
||||
func Int32Property() *Schema {
|
||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int32"}}
|
||||
}
|
||||
|
||||
// Int64Property creates an int64 property
|
||||
func Int64Property() *Schema {
|
||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int64"}}
|
||||
}
|
||||
|
||||
// StrFmtProperty creates a property for the named string format
|
||||
func StrFmtProperty(format string) *Schema {
|
||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}, Format: format}}
|
||||
}
|
||||
|
||||
// DateProperty creates a date property
|
||||
func DateProperty() *Schema {
|
||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}, Format: "date"}}
|
||||
}
|
||||
|
||||
// DateTimeProperty creates a date time property
|
||||
func DateTimeProperty() *Schema {
|
||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}, Format: "date-time"}}
|
||||
}
|
||||
|
||||
// MapProperty creates a map property
|
||||
func MapProperty(property *Schema) *Schema {
|
||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"object"},
|
||||
AdditionalProperties: &SchemaOrBool{Allows: true, Schema: property}}}
|
||||
}
|
||||
|
||||
// RefProperty creates a ref property
|
||||
func RefProperty(name string) *Schema {
|
||||
return &Schema{SchemaProps: SchemaProps{Ref: MustCreateRef(name)}}
|
||||
}
|
||||
|
||||
// RefSchema creates a ref property
|
||||
func RefSchema(name string) *Schema {
|
||||
return &Schema{SchemaProps: SchemaProps{Ref: MustCreateRef(name)}}
|
||||
}
|
||||
|
||||
// ArrayProperty creates an array property
|
||||
func ArrayProperty(items *Schema) *Schema {
|
||||
if items == nil {
|
||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"array"}}}
|
||||
}
|
||||
return &Schema{SchemaProps: SchemaProps{Items: &SchemaOrArray{Schema: items}, Type: []string{"array"}}}
|
||||
}
|
||||
|
||||
// ComposedSchema creates a schema with allOf
|
||||
func ComposedSchema(schemas ...Schema) *Schema {
|
||||
s := new(Schema)
|
||||
s.AllOf = schemas
|
||||
return s
|
||||
}
|
||||
|
||||
// SchemaURL represents a schema url
|
||||
type SchemaURL string
|
||||
|
||||
// MarshalJSON marshal this to JSON
|
||||
func (r SchemaURL) MarshalJSON() ([]byte, error) {
|
||||
if r == "" {
|
||||
return []byte("{}"), nil
|
||||
}
|
||||
v := map[string]interface{}{"$schema": string(r)}
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshal this from JSON
|
||||
func (r *SchemaURL) UnmarshalJSON(data []byte) error {
|
||||
var v map[string]interface{}
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.fromMap(v)
|
||||
}
|
||||
|
||||
func (r *SchemaURL) fromMap(v map[string]interface{}) error {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
if vv, ok := v["$schema"]; ok {
|
||||
if str, ok := vv.(string); ok {
|
||||
u, err := parseURL(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = SchemaURL(u.String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SchemaProps describes a JSON schema (draft 4)
|
||||
type SchemaProps struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Ref Ref `json:"-"`
|
||||
Schema SchemaURL `json:"-"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Type StringOrArray `json:"type,omitempty"`
|
||||
Nullable bool `json:"nullable,omitempty"`
|
||||
Format string `json:"format,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Default interface{} `json:"default,omitempty"`
|
||||
Maximum *float64 `json:"maximum,omitempty"`
|
||||
ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"`
|
||||
Minimum *float64 `json:"minimum,omitempty"`
|
||||
ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"`
|
||||
MaxLength *int64 `json:"maxLength,omitempty"`
|
||||
MinLength *int64 `json:"minLength,omitempty"`
|
||||
Pattern string `json:"pattern,omitempty"`
|
||||
MaxItems *int64 `json:"maxItems,omitempty"`
|
||||
MinItems *int64 `json:"minItems,omitempty"`
|
||||
UniqueItems bool `json:"uniqueItems,omitempty"`
|
||||
MultipleOf *float64 `json:"multipleOf,omitempty"`
|
||||
Enum []interface{} `json:"enum,omitempty"`
|
||||
MaxProperties *int64 `json:"maxProperties,omitempty"`
|
||||
MinProperties *int64 `json:"minProperties,omitempty"`
|
||||
Required []string `json:"required,omitempty"`
|
||||
Items *SchemaOrArray `json:"items,omitempty"`
|
||||
AllOf []Schema `json:"allOf,omitempty"`
|
||||
OneOf []Schema `json:"oneOf,omitempty"`
|
||||
AnyOf []Schema `json:"anyOf,omitempty"`
|
||||
Not *Schema `json:"not,omitempty"`
|
||||
Properties SchemaProperties `json:"properties,omitempty"`
|
||||
AdditionalProperties *SchemaOrBool `json:"additionalProperties,omitempty"`
|
||||
PatternProperties SchemaProperties `json:"patternProperties,omitempty"`
|
||||
Dependencies Dependencies `json:"dependencies,omitempty"`
|
||||
AdditionalItems *SchemaOrBool `json:"additionalItems,omitempty"`
|
||||
Definitions Definitions `json:"definitions,omitempty"`
|
||||
}
|
||||
|
||||
// SwaggerSchemaProps are additional properties supported by swagger schemas, but not JSON-schema (draft 4)
|
||||
type SwaggerSchemaProps struct {
|
||||
Discriminator string `json:"discriminator,omitempty"`
|
||||
ReadOnly bool `json:"readOnly,omitempty"`
|
||||
XML *XMLObject `json:"xml,omitempty"`
|
||||
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"`
|
||||
Example interface{} `json:"example,omitempty"`
|
||||
}
|
||||
|
||||
// Schema the schema object allows the definition of input and output data types.
|
||||
// These types can be objects, but also primitives and arrays.
|
||||
// This object is based on the [JSON Schema Specification Draft 4](http://json-schema.org/)
|
||||
// and uses a predefined subset of it.
|
||||
// On top of this subset, there are extensions provided by this specification to allow for more complete documentation.
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#schemaObject
|
||||
type Schema struct {
|
||||
VendorExtensible
|
||||
SchemaProps
|
||||
SwaggerSchemaProps
|
||||
ExtraProps map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
// JSONLookup implements an interface to customize json pointer lookup
|
||||
func (s Schema) JSONLookup(token string) (interface{}, error) {
|
||||
if ex, ok := s.Extensions[token]; ok {
|
||||
return &ex, nil
|
||||
}
|
||||
|
||||
if ex, ok := s.ExtraProps[token]; ok {
|
||||
return &ex, nil
|
||||
}
|
||||
|
||||
r, _, err := jsonpointer.GetForToken(s.SchemaProps, token)
|
||||
if r != nil || (err != nil && !strings.HasPrefix(err.Error(), "object has no field")) {
|
||||
return r, err
|
||||
}
|
||||
r, _, err = jsonpointer.GetForToken(s.SwaggerSchemaProps, token)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// WithID sets the id for this schema, allows for chaining
|
||||
func (s *Schema) WithID(id string) *Schema {
|
||||
s.ID = id
|
||||
return s
|
||||
}
|
||||
|
||||
// WithTitle sets the title for this schema, allows for chaining
|
||||
func (s *Schema) WithTitle(title string) *Schema {
|
||||
s.Title = title
|
||||
return s
|
||||
}
|
||||
|
||||
// WithDescription sets the description for this schema, allows for chaining
|
||||
func (s *Schema) WithDescription(description string) *Schema {
|
||||
s.Description = description
|
||||
return s
|
||||
}
|
||||
|
||||
// WithProperties sets the properties for this schema
|
||||
func (s *Schema) WithProperties(schemas map[string]Schema) *Schema {
|
||||
s.Properties = schemas
|
||||
return s
|
||||
}
|
||||
|
||||
// SetProperty sets a property on this schema
|
||||
func (s *Schema) SetProperty(name string, schema Schema) *Schema {
|
||||
if s.Properties == nil {
|
||||
s.Properties = make(map[string]Schema)
|
||||
}
|
||||
s.Properties[name] = schema
|
||||
return s
|
||||
}
|
||||
|
||||
// WithAllOf sets the all of property
|
||||
func (s *Schema) WithAllOf(schemas ...Schema) *Schema {
|
||||
s.AllOf = schemas
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMaxProperties sets the max number of properties an object can have
|
||||
func (s *Schema) WithMaxProperties(max int64) *Schema {
|
||||
s.MaxProperties = &max
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMinProperties sets the min number of properties an object must have
|
||||
func (s *Schema) WithMinProperties(min int64) *Schema {
|
||||
s.MinProperties = &min
|
||||
return s
|
||||
}
|
||||
|
||||
// Typed sets the type of this schema for a single value item
|
||||
func (s *Schema) Typed(tpe, format string) *Schema {
|
||||
s.Type = []string{tpe}
|
||||
s.Format = format
|
||||
return s
|
||||
}
|
||||
|
||||
// AddType adds a type with potential format to the types for this schema
|
||||
func (s *Schema) AddType(tpe, format string) *Schema {
|
||||
s.Type = append(s.Type, tpe)
|
||||
if format != "" {
|
||||
s.Format = format
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// AsNullable flags this schema as nullable.
|
||||
func (s *Schema) AsNullable() *Schema {
|
||||
s.Nullable = true
|
||||
return s
|
||||
}
|
||||
|
||||
// CollectionOf a fluent builder method for an array parameter
|
||||
func (s *Schema) CollectionOf(items Schema) *Schema {
|
||||
s.Type = []string{jsonArray}
|
||||
s.Items = &SchemaOrArray{Schema: &items}
|
||||
return s
|
||||
}
|
||||
|
||||
// WithDefault sets the default value on this parameter
|
||||
func (s *Schema) WithDefault(defaultValue interface{}) *Schema {
|
||||
s.Default = defaultValue
|
||||
return s
|
||||
}
|
||||
|
||||
// WithRequired flags this parameter as required
|
||||
func (s *Schema) WithRequired(items ...string) *Schema {
|
||||
s.Required = items
|
||||
return s
|
||||
}
|
||||
|
||||
// AddRequired adds field names to the required properties array
|
||||
func (s *Schema) AddRequired(items ...string) *Schema {
|
||||
s.Required = append(s.Required, items...)
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMaxLength sets a max length value
|
||||
func (s *Schema) WithMaxLength(max int64) *Schema {
|
||||
s.MaxLength = &max
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMinLength sets a min length value
|
||||
func (s *Schema) WithMinLength(min int64) *Schema {
|
||||
s.MinLength = &min
|
||||
return s
|
||||
}
|
||||
|
||||
// WithPattern sets a pattern value
|
||||
func (s *Schema) WithPattern(pattern string) *Schema {
|
||||
s.Pattern = pattern
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMultipleOf sets a multiple of value
|
||||
func (s *Schema) WithMultipleOf(number float64) *Schema {
|
||||
s.MultipleOf = &number
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMaximum sets a maximum number value
|
||||
func (s *Schema) WithMaximum(max float64, exclusive bool) *Schema {
|
||||
s.Maximum = &max
|
||||
s.ExclusiveMaximum = exclusive
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMinimum sets a minimum number value
|
||||
func (s *Schema) WithMinimum(min float64, exclusive bool) *Schema {
|
||||
s.Minimum = &min
|
||||
s.ExclusiveMinimum = exclusive
|
||||
return s
|
||||
}
|
||||
|
||||
// WithEnum sets a the enum values (replace)
|
||||
func (s *Schema) WithEnum(values ...interface{}) *Schema {
|
||||
s.Enum = append([]interface{}{}, values...)
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMaxItems sets the max items
|
||||
func (s *Schema) WithMaxItems(size int64) *Schema {
|
||||
s.MaxItems = &size
|
||||
return s
|
||||
}
|
||||
|
||||
// WithMinItems sets the min items
|
||||
func (s *Schema) WithMinItems(size int64) *Schema {
|
||||
s.MinItems = &size
|
||||
return s
|
||||
}
|
||||
|
||||
// UniqueValues dictates that this array can only have unique items
|
||||
func (s *Schema) UniqueValues() *Schema {
|
||||
s.UniqueItems = true
|
||||
return s
|
||||
}
|
||||
|
||||
// AllowDuplicates this array can have duplicates
|
||||
func (s *Schema) AllowDuplicates() *Schema {
|
||||
s.UniqueItems = false
|
||||
return s
|
||||
}
|
||||
|
||||
// AddToAllOf adds a schema to the allOf property
|
||||
func (s *Schema) AddToAllOf(schemas ...Schema) *Schema {
|
||||
s.AllOf = append(s.AllOf, schemas...)
|
||||
return s
|
||||
}
|
||||
|
||||
// WithDiscriminator sets the name of the discriminator field
|
||||
func (s *Schema) WithDiscriminator(discriminator string) *Schema {
|
||||
s.Discriminator = discriminator
|
||||
return s
|
||||
}
|
||||
|
||||
// AsReadOnly flags this schema as readonly
|
||||
func (s *Schema) AsReadOnly() *Schema {
|
||||
s.ReadOnly = true
|
||||
return s
|
||||
}
|
||||
|
||||
// AsWritable flags this schema as writeable (not read-only)
|
||||
func (s *Schema) AsWritable() *Schema {
|
||||
s.ReadOnly = false
|
||||
return s
|
||||
}
|
||||
|
||||
// WithExample sets the example for this schema
|
||||
func (s *Schema) WithExample(example interface{}) *Schema {
|
||||
s.Example = example
|
||||
return s
|
||||
}
|
||||
|
||||
// WithExternalDocs sets/removes the external docs for/from this schema.
|
||||
// When you pass empty strings as params the external documents will be removed.
|
||||
// When you pass non-empty string as one value then those values will be used on the external docs object.
|
||||
// So when you pass a non-empty description, you should also pass the url and vice versa.
|
||||
func (s *Schema) WithExternalDocs(description, url string) *Schema {
|
||||
if description == "" && url == "" {
|
||||
s.ExternalDocs = nil
|
||||
return s
|
||||
}
|
||||
|
||||
if s.ExternalDocs == nil {
|
||||
s.ExternalDocs = &ExternalDocumentation{}
|
||||
}
|
||||
s.ExternalDocs.Description = description
|
||||
s.ExternalDocs.URL = url
|
||||
return s
|
||||
}
|
||||
|
||||
// WithXMLName sets the xml name for the object
|
||||
func (s *Schema) WithXMLName(name string) *Schema {
|
||||
if s.XML == nil {
|
||||
s.XML = new(XMLObject)
|
||||
}
|
||||
s.XML.Name = name
|
||||
return s
|
||||
}
|
||||
|
||||
// WithXMLNamespace sets the xml namespace for the object
|
||||
func (s *Schema) WithXMLNamespace(namespace string) *Schema {
|
||||
if s.XML == nil {
|
||||
s.XML = new(XMLObject)
|
||||
}
|
||||
s.XML.Namespace = namespace
|
||||
return s
|
||||
}
|
||||
|
||||
// WithXMLPrefix sets the xml prefix for the object
|
||||
func (s *Schema) WithXMLPrefix(prefix string) *Schema {
|
||||
if s.XML == nil {
|
||||
s.XML = new(XMLObject)
|
||||
}
|
||||
s.XML.Prefix = prefix
|
||||
return s
|
||||
}
|
||||
|
||||
// AsXMLAttribute flags this object as xml attribute
|
||||
func (s *Schema) AsXMLAttribute() *Schema {
|
||||
if s.XML == nil {
|
||||
s.XML = new(XMLObject)
|
||||
}
|
||||
s.XML.Attribute = true
|
||||
return s
|
||||
}
|
||||
|
||||
// AsXMLElement flags this object as an xml node
|
||||
func (s *Schema) AsXMLElement() *Schema {
|
||||
if s.XML == nil {
|
||||
s.XML = new(XMLObject)
|
||||
}
|
||||
s.XML.Attribute = false
|
||||
return s
|
||||
}
|
||||
|
||||
// AsWrappedXML flags this object as wrapped, this is mostly useful for array types
|
||||
func (s *Schema) AsWrappedXML() *Schema {
|
||||
if s.XML == nil {
|
||||
s.XML = new(XMLObject)
|
||||
}
|
||||
s.XML.Wrapped = true
|
||||
return s
|
||||
}
|
||||
|
||||
// AsUnwrappedXML flags this object as an xml node
|
||||
func (s *Schema) AsUnwrappedXML() *Schema {
|
||||
if s.XML == nil {
|
||||
s.XML = new(XMLObject)
|
||||
}
|
||||
s.XML.Wrapped = false
|
||||
return s
|
||||
}
|
||||
|
||||
// SetValidations defines all schema validations.
|
||||
//
|
||||
// NOTE: Required, ReadOnly, AllOf, AnyOf, OneOf and Not are not considered.
|
||||
func (s *Schema) SetValidations(val SchemaValidations) {
|
||||
s.Maximum = val.Maximum
|
||||
s.ExclusiveMaximum = val.ExclusiveMaximum
|
||||
s.Minimum = val.Minimum
|
||||
s.ExclusiveMinimum = val.ExclusiveMinimum
|
||||
s.MaxLength = val.MaxLength
|
||||
s.MinLength = val.MinLength
|
||||
s.Pattern = val.Pattern
|
||||
s.MaxItems = val.MaxItems
|
||||
s.MinItems = val.MinItems
|
||||
s.UniqueItems = val.UniqueItems
|
||||
s.MultipleOf = val.MultipleOf
|
||||
s.Enum = val.Enum
|
||||
s.MinProperties = val.MinProperties
|
||||
s.MaxProperties = val.MaxProperties
|
||||
s.PatternProperties = val.PatternProperties
|
||||
}
|
||||
|
||||
// WithValidations is a fluent method to set schema validations
|
||||
func (s *Schema) WithValidations(val SchemaValidations) *Schema {
|
||||
s.SetValidations(val)
|
||||
return s
|
||||
}
|
||||
|
||||
// Validations returns a clone of the validations for this schema
|
||||
func (s Schema) Validations() SchemaValidations {
|
||||
return SchemaValidations{
|
||||
CommonValidations: CommonValidations{
|
||||
Maximum: s.Maximum,
|
||||
ExclusiveMaximum: s.ExclusiveMaximum,
|
||||
Minimum: s.Minimum,
|
||||
ExclusiveMinimum: s.ExclusiveMinimum,
|
||||
MaxLength: s.MaxLength,
|
||||
MinLength: s.MinLength,
|
||||
Pattern: s.Pattern,
|
||||
MaxItems: s.MaxItems,
|
||||
MinItems: s.MinItems,
|
||||
UniqueItems: s.UniqueItems,
|
||||
MultipleOf: s.MultipleOf,
|
||||
Enum: s.Enum,
|
||||
},
|
||||
MinProperties: s.MinProperties,
|
||||
MaxProperties: s.MaxProperties,
|
||||
PatternProperties: s.PatternProperties,
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON marshal this to JSON
|
||||
func (s Schema) MarshalJSON() ([]byte, error) {
|
||||
b1, err := json.Marshal(s.SchemaProps)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("schema props %v", err)
|
||||
}
|
||||
b2, err := json.Marshal(s.VendorExtensible)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("vendor props %v", err)
|
||||
}
|
||||
b3, err := s.Ref.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ref prop %v", err)
|
||||
}
|
||||
b4, err := s.Schema.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("schema prop %v", err)
|
||||
}
|
||||
b5, err := json.Marshal(s.SwaggerSchemaProps)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("common validations %v", err)
|
||||
}
|
||||
var b6 []byte
|
||||
if s.ExtraProps != nil {
|
||||
jj, err := json.Marshal(s.ExtraProps)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("extra props %v", err)
|
||||
}
|
||||
b6 = jj
|
||||
}
|
||||
return swag.ConcatJSON(b1, b2, b3, b4, b5, b6), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON marshal this from JSON
|
||||
func (s *Schema) UnmarshalJSON(data []byte) error {
|
||||
props := struct {
|
||||
SchemaProps
|
||||
SwaggerSchemaProps
|
||||
}{}
|
||||
if err := json.Unmarshal(data, &props); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sch := Schema{
|
||||
SchemaProps: props.SchemaProps,
|
||||
SwaggerSchemaProps: props.SwaggerSchemaProps,
|
||||
}
|
||||
|
||||
var d map[string]interface{}
|
||||
if err := json.Unmarshal(data, &d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = sch.Ref.fromMap(d)
|
||||
_ = sch.Schema.fromMap(d)
|
||||
|
||||
delete(d, "$ref")
|
||||
delete(d, "$schema")
|
||||
for _, pn := range swag.DefaultJSONNameProvider.GetJSONNames(s) {
|
||||
delete(d, pn)
|
||||
}
|
||||
|
||||
for k, vv := range d {
|
||||
lk := strings.ToLower(k)
|
||||
if strings.HasPrefix(lk, "x-") {
|
||||
if sch.Extensions == nil {
|
||||
sch.Extensions = map[string]interface{}{}
|
||||
}
|
||||
sch.Extensions[k] = vv
|
||||
continue
|
||||
}
|
||||
if sch.ExtraProps == nil {
|
||||
sch.ExtraProps = map[string]interface{}{}
|
||||
}
|
||||
sch.ExtraProps[k] = vv
|
||||
}
|
||||
|
||||
*s = sch
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,331 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// PathLoader is a function to use when loading remote refs.
|
||||
//
|
||||
// This is a package level default. It may be overridden or bypassed by
|
||||
// specifying the loader in ExpandOptions.
|
||||
//
|
||||
// NOTE: if you are using the go-openapi/loads package, it will override
|
||||
// this value with its own default (a loader to retrieve YAML documents as
|
||||
// well as JSON ones).
|
||||
var PathLoader = func(pth string) (json.RawMessage, error) {
|
||||
data, err := swag.LoadFromFileOrHTTP(pth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.RawMessage(data), nil
|
||||
}
|
||||
|
||||
// resolverContext allows to share a context during spec processing.
|
||||
// At the moment, it just holds the index of circular references found.
|
||||
type resolverContext struct {
|
||||
// circulars holds all visited circular references, to shortcircuit $ref resolution.
|
||||
//
|
||||
// This structure is privately instantiated and needs not be locked against
|
||||
// concurrent access, unless we chose to implement a parallel spec walking.
|
||||
circulars map[string]bool
|
||||
basePath string
|
||||
loadDoc func(string) (json.RawMessage, error)
|
||||
rootID string
|
||||
}
|
||||
|
||||
func newResolverContext(options *ExpandOptions) *resolverContext {
|
||||
expandOptions := optionsOrDefault(options)
|
||||
|
||||
// path loader may be overridden by options
|
||||
var loader func(string) (json.RawMessage, error)
|
||||
if expandOptions.PathLoader == nil {
|
||||
loader = PathLoader
|
||||
} else {
|
||||
loader = expandOptions.PathLoader
|
||||
}
|
||||
|
||||
return &resolverContext{
|
||||
circulars: make(map[string]bool),
|
||||
basePath: expandOptions.RelativeBase, // keep the root base path in context
|
||||
loadDoc: loader,
|
||||
}
|
||||
}
|
||||
|
||||
type schemaLoader struct {
|
||||
root interface{}
|
||||
options *ExpandOptions
|
||||
cache ResolutionCache
|
||||
context *resolverContext
|
||||
}
|
||||
|
||||
func (r *schemaLoader) transitiveResolver(basePath string, ref Ref) *schemaLoader {
|
||||
if ref.IsRoot() || ref.HasFragmentOnly {
|
||||
return r
|
||||
}
|
||||
|
||||
baseRef := MustCreateRef(basePath)
|
||||
currentRef := normalizeRef(&ref, basePath)
|
||||
if strings.HasPrefix(currentRef.String(), baseRef.String()) {
|
||||
return r
|
||||
}
|
||||
|
||||
// set a new root against which to resolve
|
||||
rootURL := currentRef.GetURL()
|
||||
rootURL.Fragment = ""
|
||||
root, _ := r.cache.Get(rootURL.String())
|
||||
|
||||
// shallow copy of resolver options to set a new RelativeBase when
|
||||
// traversing multiple documents
|
||||
newOptions := r.options
|
||||
newOptions.RelativeBase = rootURL.String()
|
||||
|
||||
return defaultSchemaLoader(root, newOptions, r.cache, r.context)
|
||||
}
|
||||
|
||||
func (r *schemaLoader) updateBasePath(transitive *schemaLoader, basePath string) string {
|
||||
if transitive != r {
|
||||
if transitive.options != nil && transitive.options.RelativeBase != "" {
|
||||
return normalizeBase(transitive.options.RelativeBase)
|
||||
}
|
||||
}
|
||||
|
||||
return basePath
|
||||
}
|
||||
|
||||
func (r *schemaLoader) resolveRef(ref *Ref, target interface{}, basePath string) error {
|
||||
tgt := reflect.ValueOf(target)
|
||||
if tgt.Kind() != reflect.Ptr {
|
||||
return ErrResolveRefNeedsAPointer
|
||||
}
|
||||
|
||||
if ref.GetURL() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
res interface{}
|
||||
data interface{}
|
||||
err error
|
||||
)
|
||||
|
||||
// Resolve against the root if it isn't nil, and if ref is pointing at the root, or has a fragment only which means
|
||||
// it is pointing somewhere in the root.
|
||||
root := r.root
|
||||
if (ref.IsRoot() || ref.HasFragmentOnly) && root == nil && basePath != "" {
|
||||
if baseRef, erb := NewRef(basePath); erb == nil {
|
||||
root, _, _, _ = r.load(baseRef.GetURL())
|
||||
}
|
||||
}
|
||||
|
||||
if (ref.IsRoot() || ref.HasFragmentOnly) && root != nil {
|
||||
data = root
|
||||
} else {
|
||||
baseRef := normalizeRef(ref, basePath)
|
||||
data, _, _, err = r.load(baseRef.GetURL())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
res = data
|
||||
if ref.String() != "" {
|
||||
res, _, err = ref.GetPointer().Get(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return swag.DynamicJSONToStruct(res, target)
|
||||
}
|
||||
|
||||
func (r *schemaLoader) load(refURL *url.URL) (interface{}, url.URL, bool, error) {
|
||||
debugLog("loading schema from url: %s", refURL)
|
||||
toFetch := *refURL
|
||||
toFetch.Fragment = ""
|
||||
|
||||
var err error
|
||||
pth := toFetch.String()
|
||||
normalized := normalizeBase(pth)
|
||||
debugLog("loading doc from: %s", normalized)
|
||||
|
||||
data, fromCache := r.cache.Get(normalized)
|
||||
if fromCache {
|
||||
return data, toFetch, fromCache, nil
|
||||
}
|
||||
|
||||
b, err := r.context.loadDoc(normalized)
|
||||
if err != nil {
|
||||
return nil, url.URL{}, false, err
|
||||
}
|
||||
|
||||
var doc interface{}
|
||||
if err := json.Unmarshal(b, &doc); err != nil {
|
||||
return nil, url.URL{}, false, err
|
||||
}
|
||||
r.cache.Set(normalized, doc)
|
||||
|
||||
return doc, toFetch, fromCache, nil
|
||||
}
|
||||
|
||||
// isCircular detects cycles in sequences of $ref.
|
||||
//
|
||||
// It relies on a private context (which needs not be locked).
|
||||
func (r *schemaLoader) isCircular(ref *Ref, basePath string, parentRefs ...string) (foundCycle bool) {
|
||||
normalizedRef := normalizeURI(ref.String(), basePath)
|
||||
if _, ok := r.context.circulars[normalizedRef]; ok {
|
||||
// circular $ref has been already detected in another explored cycle
|
||||
foundCycle = true
|
||||
return
|
||||
}
|
||||
foundCycle = swag.ContainsStrings(parentRefs, normalizedRef) // normalized windows url's are lower cased
|
||||
if foundCycle {
|
||||
r.context.circulars[normalizedRef] = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Resolve resolves a reference against basePath and stores the result in target.
|
||||
//
|
||||
// Resolve is not in charge of following references: it only resolves ref by following its URL.
|
||||
//
|
||||
// If the schema the ref is referring to holds nested refs, Resolve doesn't resolve them.
|
||||
//
|
||||
// If basePath is an empty string, ref is resolved against the root schema stored in the schemaLoader struct
|
||||
func (r *schemaLoader) Resolve(ref *Ref, target interface{}, basePath string) error {
|
||||
return r.resolveRef(ref, target, basePath)
|
||||
}
|
||||
|
||||
func (r *schemaLoader) deref(input interface{}, parentRefs []string, basePath string) error {
|
||||
var ref *Ref
|
||||
switch refable := input.(type) {
|
||||
case *Schema:
|
||||
ref = &refable.Ref
|
||||
case *Parameter:
|
||||
ref = &refable.Ref
|
||||
case *Response:
|
||||
ref = &refable.Ref
|
||||
case *PathItem:
|
||||
ref = &refable.Ref
|
||||
default:
|
||||
return fmt.Errorf("unsupported type: %T: %w", input, ErrDerefUnsupportedType)
|
||||
}
|
||||
|
||||
curRef := ref.String()
|
||||
if curRef == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
normalizedRef := normalizeRef(ref, basePath)
|
||||
normalizedBasePath := normalizedRef.RemoteURI()
|
||||
|
||||
if r.isCircular(normalizedRef, basePath, parentRefs...) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := r.resolveRef(ref, input, basePath); r.shouldStopOnError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if ref.String() == "" || ref.String() == curRef {
|
||||
// done with rereferencing
|
||||
return nil
|
||||
}
|
||||
|
||||
parentRefs = append(parentRefs, normalizedRef.String())
|
||||
return r.deref(input, parentRefs, normalizedBasePath)
|
||||
}
|
||||
|
||||
func (r *schemaLoader) shouldStopOnError(err error) bool {
|
||||
if err != nil && !r.options.ContinueOnError {
|
||||
return true
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *schemaLoader) setSchemaID(target interface{}, id, basePath string) (string, string) {
|
||||
debugLog("schema has ID: %s", id)
|
||||
|
||||
// handling the case when id is a folder
|
||||
// remember that basePath has to point to a file
|
||||
var refPath string
|
||||
if strings.HasSuffix(id, "/") {
|
||||
// ensure this is detected as a file, not a folder
|
||||
refPath = fmt.Sprintf("%s%s", id, "placeholder.json")
|
||||
} else {
|
||||
refPath = id
|
||||
}
|
||||
|
||||
// updates the current base path
|
||||
// * important: ID can be a relative path
|
||||
// * registers target to be fetchable from the new base proposed by this id
|
||||
newBasePath := normalizeURI(refPath, basePath)
|
||||
|
||||
// store found IDs for possible future reuse in $ref
|
||||
r.cache.Set(newBasePath, target)
|
||||
|
||||
// the root document has an ID: all $ref relative to that ID may
|
||||
// be rebased relative to the root document
|
||||
if basePath == r.context.basePath {
|
||||
debugLog("root document is a schema with ID: %s (normalized as:%s)", id, newBasePath)
|
||||
r.context.rootID = newBasePath
|
||||
}
|
||||
|
||||
return newBasePath, refPath
|
||||
}
|
||||
|
||||
func defaultSchemaLoader(
|
||||
root interface{},
|
||||
expandOptions *ExpandOptions,
|
||||
cache ResolutionCache,
|
||||
context *resolverContext) *schemaLoader {
|
||||
|
||||
if expandOptions == nil {
|
||||
expandOptions = &ExpandOptions{}
|
||||
}
|
||||
|
||||
cache = cacheOrDefault(cache)
|
||||
|
||||
if expandOptions.RelativeBase == "" {
|
||||
// if no relative base is provided, assume the root document
|
||||
// contains all $ref, or at least, that the relative documents
|
||||
// may be resolved from the current working directory.
|
||||
expandOptions.RelativeBase = baseForRoot(root, cache)
|
||||
}
|
||||
debugLog("effective expander options: %#v", expandOptions)
|
||||
|
||||
if context == nil {
|
||||
context = newResolverContext(expandOptions)
|
||||
}
|
||||
|
||||
return &schemaLoader{
|
||||
root: root,
|
||||
options: expandOptions,
|
||||
cache: cache,
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,149 @@
|
||||
{
|
||||
"id": "http://json-schema.org/draft-04/schema#",
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"description": "Core schema meta-schema",
|
||||
"definitions": {
|
||||
"schemaArray": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": { "$ref": "#" }
|
||||
},
|
||||
"positiveInteger": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"positiveIntegerDefault0": {
|
||||
"allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
|
||||
},
|
||||
"simpleTypes": {
|
||||
"enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
|
||||
},
|
||||
"stringArray": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": {},
|
||||
"multipleOf": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"exclusiveMinimum": true
|
||||
},
|
||||
"maximum": {
|
||||
"type": "number"
|
||||
},
|
||||
"exclusiveMaximum": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"minimum": {
|
||||
"type": "number"
|
||||
},
|
||||
"exclusiveMinimum": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"maxLength": { "$ref": "#/definitions/positiveInteger" },
|
||||
"minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
||||
"pattern": {
|
||||
"type": "string",
|
||||
"format": "regex"
|
||||
},
|
||||
"additionalItems": {
|
||||
"anyOf": [
|
||||
{ "type": "boolean" },
|
||||
{ "$ref": "#" }
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{ "$ref": "#" },
|
||||
{ "$ref": "#/definitions/schemaArray" }
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"maxItems": { "$ref": "#/definitions/positiveInteger" },
|
||||
"minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
||||
"uniqueItems": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"maxProperties": { "$ref": "#/definitions/positiveInteger" },
|
||||
"minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
||||
"required": { "$ref": "#/definitions/stringArray" },
|
||||
"additionalProperties": {
|
||||
"anyOf": [
|
||||
{ "type": "boolean" },
|
||||
{ "$ref": "#" }
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"definitions": {
|
||||
"type": "object",
|
||||
"additionalProperties": { "$ref": "#" },
|
||||
"default": {}
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"additionalProperties": { "$ref": "#" },
|
||||
"default": {}
|
||||
},
|
||||
"patternProperties": {
|
||||
"type": "object",
|
||||
"additionalProperties": { "$ref": "#" },
|
||||
"default": {}
|
||||
},
|
||||
"dependencies": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"anyOf": [
|
||||
{ "$ref": "#" },
|
||||
{ "$ref": "#/definitions/stringArray" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"enum": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"type": {
|
||||
"anyOf": [
|
||||
{ "$ref": "#/definitions/simpleTypes" },
|
||||
{
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/simpleTypes" },
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"format": { "type": "string" },
|
||||
"allOf": { "$ref": "#/definitions/schemaArray" },
|
||||
"anyOf": { "$ref": "#/definitions/schemaArray" },
|
||||
"oneOf": { "$ref": "#/definitions/schemaArray" },
|
||||
"not": { "$ref": "#" }
|
||||
},
|
||||
"dependencies": {
|
||||
"exclusiveMaximum": [ "maximum" ],
|
||||
"exclusiveMinimum": [ "minimum" ]
|
||||
},
|
||||
"default": {}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,170 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
const (
|
||||
basic = "basic"
|
||||
apiKey = "apiKey"
|
||||
oauth2 = "oauth2"
|
||||
implicit = "implicit"
|
||||
password = "password"
|
||||
application = "application"
|
||||
accessCode = "accessCode"
|
||||
)
|
||||
|
||||
// BasicAuth creates a basic auth security scheme
|
||||
func BasicAuth() *SecurityScheme {
|
||||
return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{Type: basic}}
|
||||
}
|
||||
|
||||
// APIKeyAuth creates an api key auth security scheme
|
||||
func APIKeyAuth(fieldName, valueSource string) *SecurityScheme {
|
||||
return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{Type: apiKey, Name: fieldName, In: valueSource}}
|
||||
}
|
||||
|
||||
// OAuth2Implicit creates an implicit flow oauth2 security scheme
|
||||
func OAuth2Implicit(authorizationURL string) *SecurityScheme {
|
||||
return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{
|
||||
Type: oauth2,
|
||||
Flow: implicit,
|
||||
AuthorizationURL: authorizationURL,
|
||||
}}
|
||||
}
|
||||
|
||||
// OAuth2Password creates a password flow oauth2 security scheme
|
||||
func OAuth2Password(tokenURL string) *SecurityScheme {
|
||||
return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{
|
||||
Type: oauth2,
|
||||
Flow: password,
|
||||
TokenURL: tokenURL,
|
||||
}}
|
||||
}
|
||||
|
||||
// OAuth2Application creates an application flow oauth2 security scheme
|
||||
func OAuth2Application(tokenURL string) *SecurityScheme {
|
||||
return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{
|
||||
Type: oauth2,
|
||||
Flow: application,
|
||||
TokenURL: tokenURL,
|
||||
}}
|
||||
}
|
||||
|
||||
// OAuth2AccessToken creates an access token flow oauth2 security scheme
|
||||
func OAuth2AccessToken(authorizationURL, tokenURL string) *SecurityScheme {
|
||||
return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{
|
||||
Type: oauth2,
|
||||
Flow: accessCode,
|
||||
AuthorizationURL: authorizationURL,
|
||||
TokenURL: tokenURL,
|
||||
}}
|
||||
}
|
||||
|
||||
// SecuritySchemeProps describes a swagger security scheme in the securityDefinitions section
|
||||
type SecuritySchemeProps struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name,omitempty"` // api key
|
||||
In string `json:"in,omitempty"` // api key
|
||||
Flow string `json:"flow,omitempty"` // oauth2
|
||||
AuthorizationURL string `json:"authorizationUrl"` // oauth2
|
||||
TokenURL string `json:"tokenUrl,omitempty"` // oauth2
|
||||
Scopes map[string]string `json:"scopes,omitempty"` // oauth2
|
||||
}
|
||||
|
||||
// AddScope adds a scope to this security scheme
|
||||
func (s *SecuritySchemeProps) AddScope(scope, description string) {
|
||||
if s.Scopes == nil {
|
||||
s.Scopes = make(map[string]string)
|
||||
}
|
||||
s.Scopes[scope] = description
|
||||
}
|
||||
|
||||
// SecurityScheme allows the definition of a security scheme that can be used by the operations.
|
||||
// Supported schemes are basic authentication, an API key (either as a header or as a query parameter)
|
||||
// and OAuth2's common flows (implicit, password, application and access code).
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#securitySchemeObject
|
||||
type SecurityScheme struct {
|
||||
VendorExtensible
|
||||
SecuritySchemeProps
|
||||
}
|
||||
|
||||
// JSONLookup implements an interface to customize json pointer lookup
|
||||
func (s SecurityScheme) JSONLookup(token string) (interface{}, error) {
|
||||
if ex, ok := s.Extensions[token]; ok {
|
||||
return &ex, nil
|
||||
}
|
||||
|
||||
r, _, err := jsonpointer.GetForToken(s.SecuritySchemeProps, token)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// MarshalJSON marshal this to JSON
|
||||
func (s SecurityScheme) MarshalJSON() ([]byte, error) {
|
||||
var (
|
||||
b1 []byte
|
||||
err error
|
||||
)
|
||||
|
||||
if s.Type == oauth2 && (s.Flow == "implicit" || s.Flow == "accessCode") {
|
||||
// when oauth2 for implicit or accessCode flows, empty AuthorizationURL is added as empty string
|
||||
b1, err = json.Marshal(s.SecuritySchemeProps)
|
||||
} else {
|
||||
// when not oauth2, empty AuthorizationURL should be omitted
|
||||
b1, err = json.Marshal(struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name,omitempty"` // api key
|
||||
In string `json:"in,omitempty"` // api key
|
||||
Flow string `json:"flow,omitempty"` // oauth2
|
||||
AuthorizationURL string `json:"authorizationUrl,omitempty"` // oauth2
|
||||
TokenURL string `json:"tokenUrl,omitempty"` // oauth2
|
||||
Scopes map[string]string `json:"scopes,omitempty"` // oauth2
|
||||
}{
|
||||
Description: s.Description,
|
||||
Type: s.Type,
|
||||
Name: s.Name,
|
||||
In: s.In,
|
||||
Flow: s.Flow,
|
||||
AuthorizationURL: s.AuthorizationURL,
|
||||
TokenURL: s.TokenURL,
|
||||
Scopes: s.Scopes,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b2, err := json.Marshal(s.VendorExtensible)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return swag.ConcatJSON(b1, b2), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON marshal this from JSON
|
||||
func (s *SecurityScheme) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &s.SecuritySchemeProps); err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, &s.VendorExtensible)
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
//go:generate curl -L --progress -o ./schemas/v2/schema.json http://swagger.io/v2/schema.json
|
||||
//go:generate curl -L --progress -o ./schemas/jsonschema-draft-04.json http://json-schema.org/draft-04/schema
|
||||
//go:generate go-bindata -pkg=spec -prefix=./schemas -ignore=.*\.md ./schemas/...
|
||||
//go:generate perl -pi -e s,Json,JSON,g bindata.go
|
||||
|
||||
const (
|
||||
// SwaggerSchemaURL the url for the swagger 2.0 schema to validate specs
|
||||
SwaggerSchemaURL = "http://swagger.io/v2/schema.json#"
|
||||
// JSONSchemaURL the url for the json schema
|
||||
JSONSchemaURL = "http://json-schema.org/draft-04/schema#"
|
||||
)
|
||||
|
||||
// MustLoadJSONSchemaDraft04 panics when Swagger20Schema returns an error
|
||||
func MustLoadJSONSchemaDraft04() *Schema {
|
||||
d, e := JSONSchemaDraft04()
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// JSONSchemaDraft04 loads the json schema document for json shema draft04
|
||||
func JSONSchemaDraft04() (*Schema, error) {
|
||||
b, err := jsonschemaDraft04JSONBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
schema := new(Schema)
|
||||
if err := json.Unmarshal(b, schema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
// MustLoadSwagger20Schema panics when Swagger20Schema returns an error
|
||||
func MustLoadSwagger20Schema() *Schema {
|
||||
d, e := Swagger20Schema()
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Swagger20Schema loads the swagger 2.0 schema from the embedded assets
|
||||
func Swagger20Schema() (*Schema, error) {
|
||||
|
||||
b, err := v2SchemaJSONBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
schema := new(Schema)
|
||||
if err := json.Unmarshal(b, schema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return schema, nil
|
||||
}
|
||||
@ -0,0 +1,448 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// Swagger this is the root document object for the API specification.
|
||||
// It combines what previously was the Resource Listing and API Declaration (version 1.2 and earlier)
|
||||
// together into one document.
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#swagger-object-
|
||||
type Swagger struct {
|
||||
VendorExtensible
|
||||
SwaggerProps
|
||||
}
|
||||
|
||||
// JSONLookup look up a value by the json property name
|
||||
func (s Swagger) JSONLookup(token string) (interface{}, error) {
|
||||
if ex, ok := s.Extensions[token]; ok {
|
||||
return &ex, nil
|
||||
}
|
||||
r, _, err := jsonpointer.GetForToken(s.SwaggerProps, token)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// MarshalJSON marshals this swagger structure to json
|
||||
func (s Swagger) MarshalJSON() ([]byte, error) {
|
||||
b1, err := json.Marshal(s.SwaggerProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b2, err := json.Marshal(s.VendorExtensible)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return swag.ConcatJSON(b1, b2), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals a swagger spec from json
|
||||
func (s *Swagger) UnmarshalJSON(data []byte) error {
|
||||
var sw Swagger
|
||||
if err := json.Unmarshal(data, &sw.SwaggerProps); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(data, &sw.VendorExtensible); err != nil {
|
||||
return err
|
||||
}
|
||||
*s = sw
|
||||
return nil
|
||||
}
|
||||
|
||||
// GobEncode provides a safe gob encoder for Swagger, including extensions
|
||||
func (s Swagger) GobEncode() ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
raw := struct {
|
||||
Props SwaggerProps
|
||||
Ext VendorExtensible
|
||||
}{
|
||||
Props: s.SwaggerProps,
|
||||
Ext: s.VendorExtensible,
|
||||
}
|
||||
err := gob.NewEncoder(&b).Encode(raw)
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
// GobDecode provides a safe gob decoder for Swagger, including extensions
|
||||
func (s *Swagger) GobDecode(b []byte) error {
|
||||
var raw struct {
|
||||
Props SwaggerProps
|
||||
Ext VendorExtensible
|
||||
}
|
||||
buf := bytes.NewBuffer(b)
|
||||
err := gob.NewDecoder(buf).Decode(&raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.SwaggerProps = raw.Props
|
||||
s.VendorExtensible = raw.Ext
|
||||
return nil
|
||||
}
|
||||
|
||||
// SwaggerProps captures the top-level properties of an Api specification
|
||||
//
|
||||
// NOTE: validation rules
|
||||
// - the scheme, when present must be from [http, https, ws, wss]
|
||||
// - BasePath must start with a leading "/"
|
||||
// - Paths is required
|
||||
type SwaggerProps struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Consumes []string `json:"consumes,omitempty"`
|
||||
Produces []string `json:"produces,omitempty"`
|
||||
Schemes []string `json:"schemes,omitempty"`
|
||||
Swagger string `json:"swagger,omitempty"`
|
||||
Info *Info `json:"info,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
BasePath string `json:"basePath,omitempty"`
|
||||
Paths *Paths `json:"paths"`
|
||||
Definitions Definitions `json:"definitions,omitempty"`
|
||||
Parameters map[string]Parameter `json:"parameters,omitempty"`
|
||||
Responses map[string]Response `json:"responses,omitempty"`
|
||||
SecurityDefinitions SecurityDefinitions `json:"securityDefinitions,omitempty"`
|
||||
Security []map[string][]string `json:"security,omitempty"`
|
||||
Tags []Tag `json:"tags,omitempty"`
|
||||
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"`
|
||||
}
|
||||
|
||||
type swaggerPropsAlias SwaggerProps
|
||||
|
||||
type gobSwaggerPropsAlias struct {
|
||||
Security []map[string]struct {
|
||||
List []string
|
||||
Pad bool
|
||||
}
|
||||
Alias *swaggerPropsAlias
|
||||
SecurityIsEmpty bool
|
||||
}
|
||||
|
||||
// GobEncode provides a safe gob encoder for SwaggerProps, including empty security requirements
|
||||
func (o SwaggerProps) GobEncode() ([]byte, error) {
|
||||
raw := gobSwaggerPropsAlias{
|
||||
Alias: (*swaggerPropsAlias)(&o),
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
if o.Security == nil {
|
||||
// nil security requirement
|
||||
err := gob.NewEncoder(&b).Encode(raw)
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
if len(o.Security) == 0 {
|
||||
// empty, but non-nil security requirement
|
||||
raw.SecurityIsEmpty = true
|
||||
raw.Alias.Security = nil
|
||||
err := gob.NewEncoder(&b).Encode(raw)
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
raw.Security = make([]map[string]struct {
|
||||
List []string
|
||||
Pad bool
|
||||
}, 0, len(o.Security))
|
||||
for _, req := range o.Security {
|
||||
v := make(map[string]struct {
|
||||
List []string
|
||||
Pad bool
|
||||
}, len(req))
|
||||
for k, val := range req {
|
||||
v[k] = struct {
|
||||
List []string
|
||||
Pad bool
|
||||
}{
|
||||
List: val,
|
||||
}
|
||||
}
|
||||
raw.Security = append(raw.Security, v)
|
||||
}
|
||||
|
||||
err := gob.NewEncoder(&b).Encode(raw)
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
// GobDecode provides a safe gob decoder for SwaggerProps, including empty security requirements
|
||||
func (o *SwaggerProps) GobDecode(b []byte) error {
|
||||
var raw gobSwaggerPropsAlias
|
||||
|
||||
buf := bytes.NewBuffer(b)
|
||||
err := gob.NewDecoder(buf).Decode(&raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if raw.Alias == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case raw.SecurityIsEmpty:
|
||||
// empty, but non-nil security requirement
|
||||
raw.Alias.Security = []map[string][]string{}
|
||||
case len(raw.Alias.Security) == 0:
|
||||
// nil security requirement
|
||||
raw.Alias.Security = nil
|
||||
default:
|
||||
raw.Alias.Security = make([]map[string][]string, 0, len(raw.Security))
|
||||
for _, req := range raw.Security {
|
||||
v := make(map[string][]string, len(req))
|
||||
for k, val := range req {
|
||||
v[k] = make([]string, 0, len(val.List))
|
||||
v[k] = append(v[k], val.List...)
|
||||
}
|
||||
raw.Alias.Security = append(raw.Alias.Security, v)
|
||||
}
|
||||
}
|
||||
|
||||
*o = *(*SwaggerProps)(raw.Alias)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dependencies represent a dependencies property
|
||||
type Dependencies map[string]SchemaOrStringArray
|
||||
|
||||
// SchemaOrBool represents a schema or boolean value, is biased towards true for the boolean property
|
||||
type SchemaOrBool struct {
|
||||
Allows bool
|
||||
Schema *Schema
|
||||
}
|
||||
|
||||
// JSONLookup implements an interface to customize json pointer lookup
|
||||
func (s SchemaOrBool) JSONLookup(token string) (interface{}, error) {
|
||||
if token == "allows" {
|
||||
return s.Allows, nil
|
||||
}
|
||||
r, _, err := jsonpointer.GetForToken(s.Schema, token)
|
||||
return r, err
|
||||
}
|
||||
|
||||
var jsTrue = []byte("true")
|
||||
var jsFalse = []byte("false")
|
||||
|
||||
// MarshalJSON convert this object to JSON
|
||||
func (s SchemaOrBool) MarshalJSON() ([]byte, error) {
|
||||
if s.Schema != nil {
|
||||
return json.Marshal(s.Schema)
|
||||
}
|
||||
|
||||
if s.Schema == nil && !s.Allows {
|
||||
return jsFalse, nil
|
||||
}
|
||||
return jsTrue, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts this bool or schema object from a JSON structure
|
||||
func (s *SchemaOrBool) UnmarshalJSON(data []byte) error {
|
||||
var nw SchemaOrBool
|
||||
if len(data) > 0 {
|
||||
if data[0] == '{' {
|
||||
var sch Schema
|
||||
if err := json.Unmarshal(data, &sch); err != nil {
|
||||
return err
|
||||
}
|
||||
nw.Schema = &sch
|
||||
}
|
||||
nw.Allows = !bytes.Equal(data, []byte("false"))
|
||||
}
|
||||
*s = nw
|
||||
return nil
|
||||
}
|
||||
|
||||
// SchemaOrStringArray represents a schema or a string array
|
||||
type SchemaOrStringArray struct {
|
||||
Schema *Schema
|
||||
Property []string
|
||||
}
|
||||
|
||||
// JSONLookup implements an interface to customize json pointer lookup
|
||||
func (s SchemaOrStringArray) JSONLookup(token string) (interface{}, error) {
|
||||
r, _, err := jsonpointer.GetForToken(s.Schema, token)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// MarshalJSON converts this schema object or array into JSON structure
|
||||
func (s SchemaOrStringArray) MarshalJSON() ([]byte, error) {
|
||||
if len(s.Property) > 0 {
|
||||
return json.Marshal(s.Property)
|
||||
}
|
||||
if s.Schema != nil {
|
||||
return json.Marshal(s.Schema)
|
||||
}
|
||||
return []byte("null"), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts this schema object or array from a JSON structure
|
||||
func (s *SchemaOrStringArray) UnmarshalJSON(data []byte) error {
|
||||
var first byte
|
||||
if len(data) > 1 {
|
||||
first = data[0]
|
||||
}
|
||||
var nw SchemaOrStringArray
|
||||
if first == '{' {
|
||||
var sch Schema
|
||||
if err := json.Unmarshal(data, &sch); err != nil {
|
||||
return err
|
||||
}
|
||||
nw.Schema = &sch
|
||||
}
|
||||
if first == '[' {
|
||||
if err := json.Unmarshal(data, &nw.Property); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*s = nw
|
||||
return nil
|
||||
}
|
||||
|
||||
// Definitions contains the models explicitly defined in this spec
|
||||
// An object to hold data types that can be consumed and produced by operations.
|
||||
// These data types can be primitives, arrays or models.
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#definitionsObject
|
||||
type Definitions map[string]Schema
|
||||
|
||||
// SecurityDefinitions a declaration of the security schemes available to be used in the specification.
|
||||
// This does not enforce the security schemes on the operations and only serves to provide
|
||||
// the relevant details for each scheme.
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#securityDefinitionsObject
|
||||
type SecurityDefinitions map[string]*SecurityScheme
|
||||
|
||||
// StringOrArray represents a value that can either be a string
|
||||
// or an array of strings. Mainly here for serialization purposes
|
||||
type StringOrArray []string
|
||||
|
||||
// Contains returns true when the value is contained in the slice
|
||||
func (s StringOrArray) Contains(value string) bool {
|
||||
for _, str := range s {
|
||||
if str == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// JSONLookup implements an interface to customize json pointer lookup
|
||||
func (s SchemaOrArray) JSONLookup(token string) (interface{}, error) {
|
||||
if _, err := strconv.Atoi(token); err == nil {
|
||||
r, _, err := jsonpointer.GetForToken(s.Schemas, token)
|
||||
return r, err
|
||||
}
|
||||
r, _, err := jsonpointer.GetForToken(s.Schema, token)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals this string or array object from a JSON array or JSON string
|
||||
func (s *StringOrArray) UnmarshalJSON(data []byte) error {
|
||||
var first byte
|
||||
if len(data) > 1 {
|
||||
first = data[0]
|
||||
}
|
||||
|
||||
if first == '[' {
|
||||
var parsed []string
|
||||
if err := json.Unmarshal(data, &parsed); err != nil {
|
||||
return err
|
||||
}
|
||||
*s = StringOrArray(parsed)
|
||||
return nil
|
||||
}
|
||||
|
||||
var single interface{}
|
||||
if err := json.Unmarshal(data, &single); err != nil {
|
||||
return err
|
||||
}
|
||||
if single == nil {
|
||||
return nil
|
||||
}
|
||||
switch v := single.(type) {
|
||||
case string:
|
||||
*s = StringOrArray([]string{v})
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("only string or array is allowed, not %T", single)
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON converts this string or array to a JSON array or JSON string
|
||||
func (s StringOrArray) MarshalJSON() ([]byte, error) {
|
||||
if len(s) == 1 {
|
||||
return json.Marshal([]string(s)[0])
|
||||
}
|
||||
return json.Marshal([]string(s))
|
||||
}
|
||||
|
||||
// SchemaOrArray represents a value that can either be a Schema
|
||||
// or an array of Schema. Mainly here for serialization purposes
|
||||
type SchemaOrArray struct {
|
||||
Schema *Schema
|
||||
Schemas []Schema
|
||||
}
|
||||
|
||||
// Len returns the number of schemas in this property
|
||||
func (s SchemaOrArray) Len() int {
|
||||
if s.Schema != nil {
|
||||
return 1
|
||||
}
|
||||
return len(s.Schemas)
|
||||
}
|
||||
|
||||
// ContainsType returns true when one of the schemas is of the specified type
|
||||
func (s *SchemaOrArray) ContainsType(name string) bool {
|
||||
if s.Schema != nil {
|
||||
return s.Schema.Type != nil && s.Schema.Type.Contains(name)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MarshalJSON converts this schema object or array into JSON structure
|
||||
func (s SchemaOrArray) MarshalJSON() ([]byte, error) {
|
||||
if len(s.Schemas) > 0 {
|
||||
return json.Marshal(s.Schemas)
|
||||
}
|
||||
return json.Marshal(s.Schema)
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts this schema object or array from a JSON structure
|
||||
func (s *SchemaOrArray) UnmarshalJSON(data []byte) error {
|
||||
var nw SchemaOrArray
|
||||
var first byte
|
||||
if len(data) > 1 {
|
||||
first = data[0]
|
||||
}
|
||||
if first == '{' {
|
||||
var sch Schema
|
||||
if err := json.Unmarshal(data, &sch); err != nil {
|
||||
return err
|
||||
}
|
||||
nw.Schema = &sch
|
||||
}
|
||||
if first == '[' {
|
||||
if err := json.Unmarshal(data, &nw.Schemas); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*s = nw
|
||||
return nil
|
||||
}
|
||||
|
||||
// vim:set ft=go noet sts=2 sw=2 ts=2:
|
||||
@ -0,0 +1,75 @@
|
||||
// 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 spec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// TagProps describe a tag entry in the top level tags section of a swagger spec
|
||||
type TagProps struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"`
|
||||
}
|
||||
|
||||
// NewTag creates a new tag
|
||||
func NewTag(name, description string, externalDocs *ExternalDocumentation) Tag {
|
||||
return Tag{TagProps: TagProps{Description: description, Name: name, ExternalDocs: externalDocs}}
|
||||
}
|
||||
|
||||
// Tag allows adding meta data to a single tag that is used by the
|
||||
// [Operation Object](http://goo.gl/8us55a#operationObject).
|
||||
// It is not mandatory to have a Tag Object per tag used there.
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#tagObject
|
||||
type Tag struct {
|
||||
VendorExtensible
|
||||
TagProps
|
||||
}
|
||||
|
||||
// JSONLookup implements an interface to customize json pointer lookup
|
||||
func (t Tag) JSONLookup(token string) (interface{}, error) {
|
||||
if ex, ok := t.Extensions[token]; ok {
|
||||
return &ex, nil
|
||||
}
|
||||
|
||||
r, _, err := jsonpointer.GetForToken(t.TagProps, token)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// MarshalJSON marshal this to JSON
|
||||
func (t Tag) MarshalJSON() ([]byte, error) {
|
||||
b1, err := json.Marshal(t.TagProps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b2, err := json.Marshal(t.VendorExtensible)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return swag.ConcatJSON(b1, b2), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON marshal this from JSON
|
||||
func (t *Tag) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &t.TagProps); err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, &t.VendorExtensible)
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package spec
|
||||
|
||||
import "net/url"
|
||||
|
||||
func parseURL(s string) (*url.URL, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err == nil {
|
||||
u.OmitHost = false
|
||||
}
|
||||
return u, err
|
||||
}
|
||||
@ -0,0 +1,215 @@
|
||||
package spec
|
||||
|
||||
// CommonValidations describe common JSON-schema validations
|
||||
type CommonValidations struct {
|
||||
Maximum *float64 `json:"maximum,omitempty"`
|
||||
ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"`
|
||||
Minimum *float64 `json:"minimum,omitempty"`
|
||||
ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"`
|
||||
MaxLength *int64 `json:"maxLength,omitempty"`
|
||||
MinLength *int64 `json:"minLength,omitempty"`
|
||||
Pattern string `json:"pattern,omitempty"`
|
||||
MaxItems *int64 `json:"maxItems,omitempty"`
|
||||
MinItems *int64 `json:"minItems,omitempty"`
|
||||
UniqueItems bool `json:"uniqueItems,omitempty"`
|
||||
MultipleOf *float64 `json:"multipleOf,omitempty"`
|
||||
Enum []interface{} `json:"enum,omitempty"`
|
||||
}
|
||||
|
||||
// SetValidations defines all validations for a simple schema.
|
||||
//
|
||||
// NOTE: the input is the larger set of validations available for schemas.
|
||||
// For simple schemas, MinProperties and MaxProperties are ignored.
|
||||
func (v *CommonValidations) SetValidations(val SchemaValidations) {
|
||||
v.Maximum = val.Maximum
|
||||
v.ExclusiveMaximum = val.ExclusiveMaximum
|
||||
v.Minimum = val.Minimum
|
||||
v.ExclusiveMinimum = val.ExclusiveMinimum
|
||||
v.MaxLength = val.MaxLength
|
||||
v.MinLength = val.MinLength
|
||||
v.Pattern = val.Pattern
|
||||
v.MaxItems = val.MaxItems
|
||||
v.MinItems = val.MinItems
|
||||
v.UniqueItems = val.UniqueItems
|
||||
v.MultipleOf = val.MultipleOf
|
||||
v.Enum = val.Enum
|
||||
}
|
||||
|
||||
type clearedValidation struct {
|
||||
Validation string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
type clearedValidations []clearedValidation
|
||||
|
||||
func (c clearedValidations) apply(cbs []func(string, interface{})) {
|
||||
for _, cb := range cbs {
|
||||
for _, cleared := range c {
|
||||
cb(cleared.Validation, cleared.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ClearNumberValidations clears all number validations.
|
||||
//
|
||||
// Some callbacks may be set by the caller to capture changed values.
|
||||
func (v *CommonValidations) ClearNumberValidations(cbs ...func(string, interface{})) {
|
||||
done := make(clearedValidations, 0, 5)
|
||||
defer func() {
|
||||
done.apply(cbs)
|
||||
}()
|
||||
|
||||
if v.Minimum != nil {
|
||||
done = append(done, clearedValidation{Validation: "minimum", Value: v.Minimum})
|
||||
v.Minimum = nil
|
||||
}
|
||||
if v.Maximum != nil {
|
||||
done = append(done, clearedValidation{Validation: "maximum", Value: v.Maximum})
|
||||
v.Maximum = nil
|
||||
}
|
||||
if v.ExclusiveMaximum {
|
||||
done = append(done, clearedValidation{Validation: "exclusiveMaximum", Value: v.ExclusiveMaximum})
|
||||
v.ExclusiveMaximum = false
|
||||
}
|
||||
if v.ExclusiveMinimum {
|
||||
done = append(done, clearedValidation{Validation: "exclusiveMinimum", Value: v.ExclusiveMinimum})
|
||||
v.ExclusiveMinimum = false
|
||||
}
|
||||
if v.MultipleOf != nil {
|
||||
done = append(done, clearedValidation{Validation: "multipleOf", Value: v.MultipleOf})
|
||||
v.MultipleOf = nil
|
||||
}
|
||||
}
|
||||
|
||||
// ClearStringValidations clears all string validations.
|
||||
//
|
||||
// Some callbacks may be set by the caller to capture changed values.
|
||||
func (v *CommonValidations) ClearStringValidations(cbs ...func(string, interface{})) {
|
||||
done := make(clearedValidations, 0, 3)
|
||||
defer func() {
|
||||
done.apply(cbs)
|
||||
}()
|
||||
|
||||
if v.Pattern != "" {
|
||||
done = append(done, clearedValidation{Validation: "pattern", Value: v.Pattern})
|
||||
v.Pattern = ""
|
||||
}
|
||||
if v.MinLength != nil {
|
||||
done = append(done, clearedValidation{Validation: "minLength", Value: v.MinLength})
|
||||
v.MinLength = nil
|
||||
}
|
||||
if v.MaxLength != nil {
|
||||
done = append(done, clearedValidation{Validation: "maxLength", Value: v.MaxLength})
|
||||
v.MaxLength = nil
|
||||
}
|
||||
}
|
||||
|
||||
// ClearArrayValidations clears all array validations.
|
||||
//
|
||||
// Some callbacks may be set by the caller to capture changed values.
|
||||
func (v *CommonValidations) ClearArrayValidations(cbs ...func(string, interface{})) {
|
||||
done := make(clearedValidations, 0, 3)
|
||||
defer func() {
|
||||
done.apply(cbs)
|
||||
}()
|
||||
|
||||
if v.MaxItems != nil {
|
||||
done = append(done, clearedValidation{Validation: "maxItems", Value: v.MaxItems})
|
||||
v.MaxItems = nil
|
||||
}
|
||||
if v.MinItems != nil {
|
||||
done = append(done, clearedValidation{Validation: "minItems", Value: v.MinItems})
|
||||
v.MinItems = nil
|
||||
}
|
||||
if v.UniqueItems {
|
||||
done = append(done, clearedValidation{Validation: "uniqueItems", Value: v.UniqueItems})
|
||||
v.UniqueItems = false
|
||||
}
|
||||
}
|
||||
|
||||
// Validations returns a clone of the validations for a simple schema.
|
||||
//
|
||||
// NOTE: in the context of simple schema objects, MinProperties, MaxProperties
|
||||
// and PatternProperties remain unset.
|
||||
func (v CommonValidations) Validations() SchemaValidations {
|
||||
return SchemaValidations{
|
||||
CommonValidations: v,
|
||||
}
|
||||
}
|
||||
|
||||
// HasNumberValidations indicates if the validations are for numbers or integers
|
||||
func (v CommonValidations) HasNumberValidations() bool {
|
||||
return v.Maximum != nil || v.Minimum != nil || v.MultipleOf != nil
|
||||
}
|
||||
|
||||
// HasStringValidations indicates if the validations are for strings
|
||||
func (v CommonValidations) HasStringValidations() bool {
|
||||
return v.MaxLength != nil || v.MinLength != nil || v.Pattern != ""
|
||||
}
|
||||
|
||||
// HasArrayValidations indicates if the validations are for arrays
|
||||
func (v CommonValidations) HasArrayValidations() bool {
|
||||
return v.MaxItems != nil || v.MinItems != nil || v.UniqueItems
|
||||
}
|
||||
|
||||
// HasEnum indicates if the validation includes some enum constraint
|
||||
func (v CommonValidations) HasEnum() bool {
|
||||
return len(v.Enum) > 0
|
||||
}
|
||||
|
||||
// SchemaValidations describes the validation properties of a schema
|
||||
//
|
||||
// NOTE: at this moment, this is not embedded in SchemaProps because this would induce a breaking change
|
||||
// in the exported members: all initializers using litterals would fail.
|
||||
type SchemaValidations struct {
|
||||
CommonValidations
|
||||
|
||||
PatternProperties SchemaProperties `json:"patternProperties,omitempty"`
|
||||
MaxProperties *int64 `json:"maxProperties,omitempty"`
|
||||
MinProperties *int64 `json:"minProperties,omitempty"`
|
||||
}
|
||||
|
||||
// HasObjectValidations indicates if the validations are for objects
|
||||
func (v SchemaValidations) HasObjectValidations() bool {
|
||||
return v.MaxProperties != nil || v.MinProperties != nil || v.PatternProperties != nil
|
||||
}
|
||||
|
||||
// SetValidations for schema validations
|
||||
func (v *SchemaValidations) SetValidations(val SchemaValidations) {
|
||||
v.CommonValidations.SetValidations(val)
|
||||
v.PatternProperties = val.PatternProperties
|
||||
v.MaxProperties = val.MaxProperties
|
||||
v.MinProperties = val.MinProperties
|
||||
}
|
||||
|
||||
// Validations for a schema
|
||||
func (v SchemaValidations) Validations() SchemaValidations {
|
||||
val := v.CommonValidations.Validations()
|
||||
val.PatternProperties = v.PatternProperties
|
||||
val.MinProperties = v.MinProperties
|
||||
val.MaxProperties = v.MaxProperties
|
||||
return val
|
||||
}
|
||||
|
||||
// ClearObjectValidations returns a clone of the validations with all object validations cleared.
|
||||
//
|
||||
// Some callbacks may be set by the caller to capture changed values.
|
||||
func (v *SchemaValidations) ClearObjectValidations(cbs ...func(string, interface{})) {
|
||||
done := make(clearedValidations, 0, 3)
|
||||
defer func() {
|
||||
done.apply(cbs)
|
||||
}()
|
||||
|
||||
if v.MaxProperties != nil {
|
||||
done = append(done, clearedValidation{Validation: "maxProperties", Value: v.MaxProperties})
|
||||
v.MaxProperties = nil
|
||||
}
|
||||
if v.MinProperties != nil {
|
||||
done = append(done, clearedValidation{Validation: "minProperties", Value: v.MinProperties})
|
||||
v.MinProperties = nil
|
||||
}
|
||||
if v.PatternProperties != nil {
|
||||
done = append(done, clearedValidation{Validation: "patternProperties", Value: v.PatternProperties})
|
||||
v.PatternProperties = nil
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
// 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 spec
|
||||
|
||||
// XMLObject a metadata object that allows for more fine-tuned XML model definitions.
|
||||
//
|
||||
// For more information: http://goo.gl/8us55a#xmlObject
|
||||
type XMLObject struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
Prefix string `json:"prefix,omitempty"`
|
||||
Attribute bool `json:"attribute,omitempty"`
|
||||
Wrapped bool `json:"wrapped,omitempty"`
|
||||
}
|
||||
|
||||
// WithName sets the xml name for the object
|
||||
func (x *XMLObject) WithName(name string) *XMLObject {
|
||||
x.Name = name
|
||||
return x
|
||||
}
|
||||
|
||||
// WithNamespace sets the xml namespace for the object
|
||||
func (x *XMLObject) WithNamespace(namespace string) *XMLObject {
|
||||
x.Namespace = namespace
|
||||
return x
|
||||
}
|
||||
|
||||
// WithPrefix sets the xml prefix for the object
|
||||
func (x *XMLObject) WithPrefix(prefix string) *XMLObject {
|
||||
x.Prefix = prefix
|
||||
return x
|
||||
}
|
||||
|
||||
// AsAttribute flags this object as xml attribute
|
||||
func (x *XMLObject) AsAttribute() *XMLObject {
|
||||
x.Attribute = true
|
||||
return x
|
||||
}
|
||||
|
||||
// AsElement flags this object as an xml node
|
||||
func (x *XMLObject) AsElement() *XMLObject {
|
||||
x.Attribute = false
|
||||
return x
|
||||
}
|
||||
|
||||
// AsWrapped flags this object as wrapped, this is mostly useful for array types
|
||||
func (x *XMLObject) AsWrapped() *XMLObject {
|
||||
x.Wrapped = true
|
||||
return x
|
||||
}
|
||||
|
||||
// AsUnwrapped flags this object as an xml node
|
||||
func (x *XMLObject) AsUnwrapped() *XMLObject {
|
||||
x.Wrapped = false
|
||||
return x
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Set default charset
|
||||
[*.{js,py,go,scala,rb,java,html,css,less,sass,md}]
|
||||
charset = utf-8
|
||||
|
||||
# Tab indentation (no size specified)
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Matches the exact files either package.json or .travis.yml
|
||||
[{package.json,.travis.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@ -0,0 +1,2 @@
|
||||
# gofmt always uses LF, whereas Git uses CRLF on Windows.
|
||||
*.go text eol=lf
|
||||
@ -0,0 +1,5 @@
|
||||
secrets.yml
|
||||
vendor
|
||||
Godeps
|
||||
.idea
|
||||
*.out
|
||||
@ -0,0 +1,60 @@
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
golint:
|
||||
min-confidence: 0
|
||||
gocyclo:
|
||||
min-complexity: 45
|
||||
maligned:
|
||||
suggest-new: true
|
||||
dupl:
|
||||
threshold: 200
|
||||
goconst:
|
||||
min-len: 3
|
||||
min-occurrences: 3
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- maligned
|
||||
- lll
|
||||
- gochecknoinits
|
||||
- gochecknoglobals
|
||||
- funlen
|
||||
- godox
|
||||
- gocognit
|
||||
- whitespace
|
||||
- wsl
|
||||
- wrapcheck
|
||||
- testpackage
|
||||
- nlreturn
|
||||
- gomnd
|
||||
- exhaustivestruct
|
||||
- goerr113
|
||||
- errorlint
|
||||
- nestif
|
||||
- godot
|
||||
- gofumpt
|
||||
- paralleltest
|
||||
- tparallel
|
||||
- thelper
|
||||
- ifshort
|
||||
- exhaustruct
|
||||
- varnamelen
|
||||
- gci
|
||||
- depguard
|
||||
- errchkjson
|
||||
- inamedparam
|
||||
- nonamedreturns
|
||||
- musttag
|
||||
- ireturn
|
||||
- forcetypeassert
|
||||
- cyclop
|
||||
# deprecated linters
|
||||
- deadcode
|
||||
- interfacer
|
||||
- scopelint
|
||||
- varcheck
|
||||
- structcheck
|
||||
- golint
|
||||
- nosnakecase
|
||||
@ -0,0 +1,52 @@
|
||||
# Benchmarks
|
||||
|
||||
## Name mangling utilities
|
||||
|
||||
```bash
|
||||
go test -bench XXX -run XXX -benchtime 30s
|
||||
```
|
||||
|
||||
### Benchmarks at b3e7a5386f996177e4808f11acb2aa93a0f660df
|
||||
|
||||
```
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/go-openapi/swag
|
||||
cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
|
||||
BenchmarkToXXXName/ToGoName-4 862623 44101 ns/op 10450 B/op 732 allocs/op
|
||||
BenchmarkToXXXName/ToVarName-4 853656 40728 ns/op 10468 B/op 734 allocs/op
|
||||
BenchmarkToXXXName/ToFileName-4 1268312 27813 ns/op 9785 B/op 617 allocs/op
|
||||
BenchmarkToXXXName/ToCommandName-4 1276322 27903 ns/op 9785 B/op 617 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameLower-4 895334 40354 ns/op 10472 B/op 731 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameTitle-4 882441 40678 ns/op 10566 B/op 749 allocs/op
|
||||
```
|
||||
|
||||
### Benchmarks after PR #79
|
||||
|
||||
~ x10 performance improvement and ~ /100 memory allocations.
|
||||
|
||||
```
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/go-openapi/swag
|
||||
cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
|
||||
BenchmarkToXXXName/ToGoName-4 9595830 3991 ns/op 42 B/op 5 allocs/op
|
||||
BenchmarkToXXXName/ToVarName-4 9194276 3984 ns/op 62 B/op 7 allocs/op
|
||||
BenchmarkToXXXName/ToFileName-4 17002711 2123 ns/op 147 B/op 7 allocs/op
|
||||
BenchmarkToXXXName/ToCommandName-4 16772926 2111 ns/op 147 B/op 7 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameLower-4 9788331 3749 ns/op 92 B/op 6 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameTitle-4 9188260 3941 ns/op 104 B/op 6 allocs/op
|
||||
```
|
||||
|
||||
```
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/go-openapi/swag
|
||||
cpu: AMD Ryzen 7 5800X 8-Core Processor
|
||||
BenchmarkToXXXName/ToGoName-16 18527378 1972 ns/op 42 B/op 5 allocs/op
|
||||
BenchmarkToXXXName/ToVarName-16 15552692 2093 ns/op 62 B/op 7 allocs/op
|
||||
BenchmarkToXXXName/ToFileName-16 32161176 1117 ns/op 147 B/op 7 allocs/op
|
||||
BenchmarkToXXXName/ToCommandName-16 32256634 1137 ns/op 147 B/op 7 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameLower-16 18599661 1946 ns/op 92 B/op 6 allocs/op
|
||||
BenchmarkToXXXName/ToHumanNameTitle-16 17581353 2054 ns/op 105 B/op 6 allocs/op
|
||||
```
|
||||
@ -0,0 +1,74 @@
|
||||
# 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/
|
||||
@ -0,0 +1,202 @@
|
||||
|
||||
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.
|
||||
@ -0,0 +1,23 @@
|
||||
# Swag [](https://github.com/go-openapi/swag/actions?query=workflow%3A"go+test") [](https://codecov.io/gh/go-openapi/swag)
|
||||
|
||||
[](https://slackin.goswagger.io)
|
||||
[](https://raw.githubusercontent.com/go-openapi/swag/master/LICENSE)
|
||||
[](https://pkg.go.dev/github.com/go-openapi/swag)
|
||||
[](https://goreportcard.com/report/github.com/go-openapi/swag)
|
||||
|
||||
Contains a bunch of helper functions for go-openapi and go-swagger projects.
|
||||
|
||||
You may also use it standalone for your projects.
|
||||
|
||||
* convert between value and pointers for builtin types
|
||||
* convert from string to builtin types (wraps strconv)
|
||||
* fast json concatenation
|
||||
* search in path
|
||||
* load from file or http
|
||||
* name mangling
|
||||
|
||||
|
||||
This repo has only few dependencies outside of the standard library:
|
||||
|
||||
* YAML utilities depend on `gopkg.in/yaml.v3`
|
||||
* `github.com/mailru/easyjson v0.7.7`
|
||||
@ -0,0 +1,208 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package swag
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// same as ECMA Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER
|
||||
const (
|
||||
maxJSONFloat = float64(1<<53 - 1) // 9007199254740991.0 2^53 - 1
|
||||
minJSONFloat = -float64(1<<53 - 1) //-9007199254740991.0 -2^53 - 1
|
||||
epsilon float64 = 1e-9
|
||||
)
|
||||
|
||||
// IsFloat64AJSONInteger allow for integers [-2^53, 2^53-1] inclusive
|
||||
func IsFloat64AJSONInteger(f float64) bool {
|
||||
if math.IsNaN(f) || math.IsInf(f, 0) || f < minJSONFloat || f > maxJSONFloat {
|
||||
return false
|
||||
}
|
||||
fa := math.Abs(f)
|
||||
g := float64(uint64(f))
|
||||
ga := math.Abs(g)
|
||||
|
||||
diff := math.Abs(f - g)
|
||||
|
||||
// more info: https://floating-point-gui.de/errors/comparison/#look-out-for-edge-cases
|
||||
switch {
|
||||
case f == g: // best case
|
||||
return true
|
||||
case f == float64(int64(f)) || f == float64(uint64(f)): // optimistic case
|
||||
return true
|
||||
case f == 0 || g == 0 || diff < math.SmallestNonzeroFloat64: // very close to 0 values
|
||||
return diff < (epsilon * math.SmallestNonzeroFloat64)
|
||||
}
|
||||
// check the relative error
|
||||
return diff/math.Min(fa+ga, math.MaxFloat64) < epsilon
|
||||
}
|
||||
|
||||
var evaluatesAsTrue map[string]struct{}
|
||||
|
||||
func init() {
|
||||
evaluatesAsTrue = map[string]struct{}{
|
||||
"true": {},
|
||||
"1": {},
|
||||
"yes": {},
|
||||
"ok": {},
|
||||
"y": {},
|
||||
"on": {},
|
||||
"selected": {},
|
||||
"checked": {},
|
||||
"t": {},
|
||||
"enabled": {},
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertBool turn a string into a boolean
|
||||
func ConvertBool(str string) (bool, error) {
|
||||
_, ok := evaluatesAsTrue[strings.ToLower(str)]
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// ConvertFloat32 turn a string into a float32
|
||||
func ConvertFloat32(str string) (float32, error) {
|
||||
f, err := strconv.ParseFloat(str, 32)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return float32(f), nil
|
||||
}
|
||||
|
||||
// ConvertFloat64 turn a string into a float64
|
||||
func ConvertFloat64(str string) (float64, error) {
|
||||
return strconv.ParseFloat(str, 64)
|
||||
}
|
||||
|
||||
// ConvertInt8 turn a string into an int8
|
||||
func ConvertInt8(str string) (int8, error) {
|
||||
i, err := strconv.ParseInt(str, 10, 8)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int8(i), nil
|
||||
}
|
||||
|
||||
// ConvertInt16 turn a string into an int16
|
||||
func ConvertInt16(str string) (int16, error) {
|
||||
i, err := strconv.ParseInt(str, 10, 16)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int16(i), nil
|
||||
}
|
||||
|
||||
// ConvertInt32 turn a string into an int32
|
||||
func ConvertInt32(str string) (int32, error) {
|
||||
i, err := strconv.ParseInt(str, 10, 32)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int32(i), nil
|
||||
}
|
||||
|
||||
// ConvertInt64 turn a string into an int64
|
||||
func ConvertInt64(str string) (int64, error) {
|
||||
return strconv.ParseInt(str, 10, 64)
|
||||
}
|
||||
|
||||
// ConvertUint8 turn a string into an uint8
|
||||
func ConvertUint8(str string) (uint8, error) {
|
||||
i, err := strconv.ParseUint(str, 10, 8)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint8(i), nil
|
||||
}
|
||||
|
||||
// ConvertUint16 turn a string into an uint16
|
||||
func ConvertUint16(str string) (uint16, error) {
|
||||
i, err := strconv.ParseUint(str, 10, 16)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint16(i), nil
|
||||
}
|
||||
|
||||
// ConvertUint32 turn a string into an uint32
|
||||
func ConvertUint32(str string) (uint32, error) {
|
||||
i, err := strconv.ParseUint(str, 10, 32)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint32(i), nil
|
||||
}
|
||||
|
||||
// ConvertUint64 turn a string into an uint64
|
||||
func ConvertUint64(str string) (uint64, error) {
|
||||
return strconv.ParseUint(str, 10, 64)
|
||||
}
|
||||
|
||||
// FormatBool turns a boolean into a string
|
||||
func FormatBool(value bool) string {
|
||||
return strconv.FormatBool(value)
|
||||
}
|
||||
|
||||
// FormatFloat32 turns a float32 into a string
|
||||
func FormatFloat32(value float32) string {
|
||||
return strconv.FormatFloat(float64(value), 'f', -1, 32)
|
||||
}
|
||||
|
||||
// FormatFloat64 turns a float64 into a string
|
||||
func FormatFloat64(value float64) string {
|
||||
return strconv.FormatFloat(value, 'f', -1, 64)
|
||||
}
|
||||
|
||||
// FormatInt8 turns an int8 into a string
|
||||
func FormatInt8(value int8) string {
|
||||
return strconv.FormatInt(int64(value), 10)
|
||||
}
|
||||
|
||||
// FormatInt16 turns an int16 into a string
|
||||
func FormatInt16(value int16) string {
|
||||
return strconv.FormatInt(int64(value), 10)
|
||||
}
|
||||
|
||||
// FormatInt32 turns an int32 into a string
|
||||
func FormatInt32(value int32) string {
|
||||
return strconv.Itoa(int(value))
|
||||
}
|
||||
|
||||
// FormatInt64 turns an int64 into a string
|
||||
func FormatInt64(value int64) string {
|
||||
return strconv.FormatInt(value, 10)
|
||||
}
|
||||
|
||||
// FormatUint8 turns an uint8 into a string
|
||||
func FormatUint8(value uint8) string {
|
||||
return strconv.FormatUint(uint64(value), 10)
|
||||
}
|
||||
|
||||
// FormatUint16 turns an uint16 into a string
|
||||
func FormatUint16(value uint16) string {
|
||||
return strconv.FormatUint(uint64(value), 10)
|
||||
}
|
||||
|
||||
// FormatUint32 turns an uint32 into a string
|
||||
func FormatUint32(value uint32) string {
|
||||
return strconv.FormatUint(uint64(value), 10)
|
||||
}
|
||||
|
||||
// FormatUint64 turns an uint64 into a string
|
||||
func FormatUint64(value uint64) string {
|
||||
return strconv.FormatUint(value, 10)
|
||||
}
|
||||
@ -0,0 +1,730 @@
|
||||
package swag
|
||||
|
||||
import "time"
|
||||
|
||||
// This file was taken from the aws go sdk
|
||||
|
||||
// String returns a pointer to of the string value passed in.
|
||||
func String(v string) *string {
|
||||
return &v
|
||||
}
|
||||
|
||||
// StringValue returns the value of the string pointer passed in or
|
||||
// "" if the pointer is nil.
|
||||
func StringValue(v *string) string {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// StringSlice converts a slice of string values into a slice of
|
||||
// string pointers
|
||||
func StringSlice(src []string) []*string {
|
||||
dst := make([]*string, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// StringValueSlice converts a slice of string pointers into a slice of
|
||||
// string values
|
||||
func StringValueSlice(src []*string) []string {
|
||||
dst := make([]string, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// StringMap converts a string map of string values into a string
|
||||
// map of string pointers
|
||||
func StringMap(src map[string]string) map[string]*string {
|
||||
dst := make(map[string]*string)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// StringValueMap converts a string map of string pointers into a string
|
||||
// map of string values
|
||||
func StringValueMap(src map[string]*string) map[string]string {
|
||||
dst := make(map[string]string)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Bool returns a pointer to of the bool value passed in.
|
||||
func Bool(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
// BoolValue returns the value of the bool pointer passed in or
|
||||
// false if the pointer is nil.
|
||||
func BoolValue(v *bool) bool {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// BoolSlice converts a slice of bool values into a slice of
|
||||
// bool pointers
|
||||
func BoolSlice(src []bool) []*bool {
|
||||
dst := make([]*bool, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// BoolValueSlice converts a slice of bool pointers into a slice of
|
||||
// bool values
|
||||
func BoolValueSlice(src []*bool) []bool {
|
||||
dst := make([]bool, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// BoolMap converts a string map of bool values into a string
|
||||
// map of bool pointers
|
||||
func BoolMap(src map[string]bool) map[string]*bool {
|
||||
dst := make(map[string]*bool)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// BoolValueMap converts a string map of bool pointers into a string
|
||||
// map of bool values
|
||||
func BoolValueMap(src map[string]*bool) map[string]bool {
|
||||
dst := make(map[string]bool)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Int returns a pointer to of the int value passed in.
|
||||
func Int(v int) *int {
|
||||
return &v
|
||||
}
|
||||
|
||||
// IntValue returns the value of the int pointer passed in or
|
||||
// 0 if the pointer is nil.
|
||||
func IntValue(v *int) int {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// IntSlice converts a slice of int values into a slice of
|
||||
// int pointers
|
||||
func IntSlice(src []int) []*int {
|
||||
dst := make([]*int, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// IntValueSlice converts a slice of int pointers into a slice of
|
||||
// int values
|
||||
func IntValueSlice(src []*int) []int {
|
||||
dst := make([]int, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// IntMap converts a string map of int values into a string
|
||||
// map of int pointers
|
||||
func IntMap(src map[string]int) map[string]*int {
|
||||
dst := make(map[string]*int)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// IntValueMap converts a string map of int pointers into a string
|
||||
// map of int values
|
||||
func IntValueMap(src map[string]*int) map[string]int {
|
||||
dst := make(map[string]int)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Int32 returns a pointer to of the int32 value passed in.
|
||||
func Int32(v int32) *int32 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Int32Value returns the value of the int32 pointer passed in or
|
||||
// 0 if the pointer is nil.
|
||||
func Int32Value(v *int32) int32 {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Int32Slice converts a slice of int32 values into a slice of
|
||||
// int32 pointers
|
||||
func Int32Slice(src []int32) []*int32 {
|
||||
dst := make([]*int32, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Int32ValueSlice converts a slice of int32 pointers into a slice of
|
||||
// int32 values
|
||||
func Int32ValueSlice(src []*int32) []int32 {
|
||||
dst := make([]int32, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Int32Map converts a string map of int32 values into a string
|
||||
// map of int32 pointers
|
||||
func Int32Map(src map[string]int32) map[string]*int32 {
|
||||
dst := make(map[string]*int32)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Int32ValueMap converts a string map of int32 pointers into a string
|
||||
// map of int32 values
|
||||
func Int32ValueMap(src map[string]*int32) map[string]int32 {
|
||||
dst := make(map[string]int32)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Int64 returns a pointer to of the int64 value passed in.
|
||||
func Int64(v int64) *int64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Int64Value returns the value of the int64 pointer passed in or
|
||||
// 0 if the pointer is nil.
|
||||
func Int64Value(v *int64) int64 {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Int64Slice converts a slice of int64 values into a slice of
|
||||
// int64 pointers
|
||||
func Int64Slice(src []int64) []*int64 {
|
||||
dst := make([]*int64, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Int64ValueSlice converts a slice of int64 pointers into a slice of
|
||||
// int64 values
|
||||
func Int64ValueSlice(src []*int64) []int64 {
|
||||
dst := make([]int64, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Int64Map converts a string map of int64 values into a string
|
||||
// map of int64 pointers
|
||||
func Int64Map(src map[string]int64) map[string]*int64 {
|
||||
dst := make(map[string]*int64)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Int64ValueMap converts a string map of int64 pointers into a string
|
||||
// map of int64 values
|
||||
func Int64ValueMap(src map[string]*int64) map[string]int64 {
|
||||
dst := make(map[string]int64)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Uint16 returns a pointer to of the uint16 value passed in.
|
||||
func Uint16(v uint16) *uint16 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Uint16Value returns the value of the uint16 pointer passed in or
|
||||
// 0 if the pointer is nil.
|
||||
func Uint16Value(v *uint16) uint16 {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// Uint16Slice converts a slice of uint16 values into a slice of
|
||||
// uint16 pointers
|
||||
func Uint16Slice(src []uint16) []*uint16 {
|
||||
dst := make([]*uint16, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// Uint16ValueSlice converts a slice of uint16 pointers into a slice of
|
||||
// uint16 values
|
||||
func Uint16ValueSlice(src []*uint16) []uint16 {
|
||||
dst := make([]uint16, len(src))
|
||||
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// Uint16Map converts a string map of uint16 values into a string
|
||||
// map of uint16 pointers
|
||||
func Uint16Map(src map[string]uint16) map[string]*uint16 {
|
||||
dst := make(map[string]*uint16)
|
||||
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// Uint16ValueMap converts a string map of uint16 pointers into a string
|
||||
// map of uint16 values
|
||||
func Uint16ValueMap(src map[string]*uint16) map[string]uint16 {
|
||||
dst := make(map[string]uint16)
|
||||
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// Uint returns a pointer to of the uint value passed in.
|
||||
func Uint(v uint) *uint {
|
||||
return &v
|
||||
}
|
||||
|
||||
// UintValue returns the value of the uint pointer passed in or
|
||||
// 0 if the pointer is nil.
|
||||
func UintValue(v *uint) uint {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// UintSlice converts a slice of uint values into a slice of
|
||||
// uint pointers
|
||||
func UintSlice(src []uint) []*uint {
|
||||
dst := make([]*uint, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// UintValueSlice converts a slice of uint pointers into a slice of
|
||||
// uint values
|
||||
func UintValueSlice(src []*uint) []uint {
|
||||
dst := make([]uint, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// UintMap converts a string map of uint values into a string
|
||||
// map of uint pointers
|
||||
func UintMap(src map[string]uint) map[string]*uint {
|
||||
dst := make(map[string]*uint)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// UintValueMap converts a string map of uint pointers into a string
|
||||
// map of uint values
|
||||
func UintValueMap(src map[string]*uint) map[string]uint {
|
||||
dst := make(map[string]uint)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Uint32 returns a pointer to of the uint32 value passed in.
|
||||
func Uint32(v uint32) *uint32 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Uint32Value returns the value of the uint32 pointer passed in or
|
||||
// 0 if the pointer is nil.
|
||||
func Uint32Value(v *uint32) uint32 {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Uint32Slice converts a slice of uint32 values into a slice of
|
||||
// uint32 pointers
|
||||
func Uint32Slice(src []uint32) []*uint32 {
|
||||
dst := make([]*uint32, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Uint32ValueSlice converts a slice of uint32 pointers into a slice of
|
||||
// uint32 values
|
||||
func Uint32ValueSlice(src []*uint32) []uint32 {
|
||||
dst := make([]uint32, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Uint32Map converts a string map of uint32 values into a string
|
||||
// map of uint32 pointers
|
||||
func Uint32Map(src map[string]uint32) map[string]*uint32 {
|
||||
dst := make(map[string]*uint32)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Uint32ValueMap converts a string map of uint32 pointers into a string
|
||||
// map of uint32 values
|
||||
func Uint32ValueMap(src map[string]*uint32) map[string]uint32 {
|
||||
dst := make(map[string]uint32)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Uint64 returns a pointer to of the uint64 value passed in.
|
||||
func Uint64(v uint64) *uint64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Uint64Value returns the value of the uint64 pointer passed in or
|
||||
// 0 if the pointer is nil.
|
||||
func Uint64Value(v *uint64) uint64 {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Uint64Slice converts a slice of uint64 values into a slice of
|
||||
// uint64 pointers
|
||||
func Uint64Slice(src []uint64) []*uint64 {
|
||||
dst := make([]*uint64, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Uint64ValueSlice converts a slice of uint64 pointers into a slice of
|
||||
// uint64 values
|
||||
func Uint64ValueSlice(src []*uint64) []uint64 {
|
||||
dst := make([]uint64, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Uint64Map converts a string map of uint64 values into a string
|
||||
// map of uint64 pointers
|
||||
func Uint64Map(src map[string]uint64) map[string]*uint64 {
|
||||
dst := make(map[string]*uint64)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Uint64ValueMap converts a string map of uint64 pointers into a string
|
||||
// map of uint64 values
|
||||
func Uint64ValueMap(src map[string]*uint64) map[string]uint64 {
|
||||
dst := make(map[string]uint64)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Float32 returns a pointer to of the float32 value passed in.
|
||||
func Float32(v float32) *float32 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Float32Value returns the value of the float32 pointer passed in or
|
||||
// 0 if the pointer is nil.
|
||||
func Float32Value(v *float32) float32 {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// Float32Slice converts a slice of float32 values into a slice of
|
||||
// float32 pointers
|
||||
func Float32Slice(src []float32) []*float32 {
|
||||
dst := make([]*float32, len(src))
|
||||
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// Float32ValueSlice converts a slice of float32 pointers into a slice of
|
||||
// float32 values
|
||||
func Float32ValueSlice(src []*float32) []float32 {
|
||||
dst := make([]float32, len(src))
|
||||
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// Float32Map converts a string map of float32 values into a string
|
||||
// map of float32 pointers
|
||||
func Float32Map(src map[string]float32) map[string]*float32 {
|
||||
dst := make(map[string]*float32)
|
||||
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// Float32ValueMap converts a string map of float32 pointers into a string
|
||||
// map of float32 values
|
||||
func Float32ValueMap(src map[string]*float32) map[string]float32 {
|
||||
dst := make(map[string]float32)
|
||||
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// Float64 returns a pointer to of the float64 value passed in.
|
||||
func Float64(v float64) *float64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Float64Value returns the value of the float64 pointer passed in or
|
||||
// 0 if the pointer is nil.
|
||||
func Float64Value(v *float64) float64 {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Float64Slice converts a slice of float64 values into a slice of
|
||||
// float64 pointers
|
||||
func Float64Slice(src []float64) []*float64 {
|
||||
dst := make([]*float64, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Float64ValueSlice converts a slice of float64 pointers into a slice of
|
||||
// float64 values
|
||||
func Float64ValueSlice(src []*float64) []float64 {
|
||||
dst := make([]float64, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Float64Map converts a string map of float64 values into a string
|
||||
// map of float64 pointers
|
||||
func Float64Map(src map[string]float64) map[string]*float64 {
|
||||
dst := make(map[string]*float64)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Float64ValueMap converts a string map of float64 pointers into a string
|
||||
// map of float64 values
|
||||
func Float64ValueMap(src map[string]*float64) map[string]float64 {
|
||||
dst := make(map[string]float64)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Time returns a pointer to of the time.Time value passed in.
|
||||
func Time(v time.Time) *time.Time {
|
||||
return &v
|
||||
}
|
||||
|
||||
// TimeValue returns the value of the time.Time pointer passed in or
|
||||
// time.Time{} if the pointer is nil.
|
||||
func TimeValue(v *time.Time) time.Time {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// TimeSlice converts a slice of time.Time values into a slice of
|
||||
// time.Time pointers
|
||||
func TimeSlice(src []time.Time) []*time.Time {
|
||||
dst := make([]*time.Time, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// TimeValueSlice converts a slice of time.Time pointers into a slice of
|
||||
// time.Time values
|
||||
func TimeValueSlice(src []*time.Time) []time.Time {
|
||||
dst := make([]time.Time, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// TimeMap converts a string map of time.Time values into a string
|
||||
// map of time.Time pointers
|
||||
func TimeMap(src map[string]time.Time) map[string]*time.Time {
|
||||
dst := make(map[string]*time.Time)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// TimeValueMap converts a string map of time.Time pointers into a string
|
||||
// map of time.Time values
|
||||
func TimeValueMap(src map[string]*time.Time) map[string]time.Time {
|
||||
dst := make(map[string]time.Time)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package swag contains a bunch of helper functions for go-openapi and go-swagger projects.
|
||||
|
||||
You may also use it standalone for your projects.
|
||||
|
||||
- convert between value and pointers for builtin types
|
||||
- convert from string to builtin types (wraps strconv)
|
||||
- fast json concatenation
|
||||
- search in path
|
||||
- load from file or http
|
||||
- name mangling
|
||||
|
||||
This repo has only few dependencies outside of the standard library:
|
||||
|
||||
- YAML utilities depend on gopkg.in/yaml.v2
|
||||
*/
|
||||
package swag
|
||||
@ -0,0 +1,33 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package swag
|
||||
|
||||
import "mime/multipart"
|
||||
|
||||
// File represents an uploaded file.
|
||||
type File struct {
|
||||
Data multipart.File
|
||||
Header *multipart.FileHeader
|
||||
}
|
||||
|
||||
// Read bytes from the file
|
||||
func (f *File) Read(p []byte) (n int, err error) {
|
||||
return f.Data.Read(p)
|
||||
}
|
||||
|
||||
// Close the file
|
||||
func (f *File) Close() error {
|
||||
return f.Data.Close()
|
||||
}
|
||||
@ -0,0 +1,202 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package swag
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// commonInitialisms are common acronyms that are kept as whole uppercased words.
|
||||
commonInitialisms *indexOfInitialisms
|
||||
|
||||
// initialisms is a slice of sorted initialisms
|
||||
initialisms []string
|
||||
|
||||
// a copy of initialisms pre-baked as []rune
|
||||
initialismsRunes [][]rune
|
||||
initialismsUpperCased [][]rune
|
||||
|
||||
isInitialism func(string) bool
|
||||
|
||||
maxAllocMatches int
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Taken from https://github.com/golang/lint/blob/3390df4df2787994aea98de825b964ac7944b817/lint.go#L732-L769
|
||||
configuredInitialisms := map[string]bool{
|
||||
"ACL": true,
|
||||
"API": true,
|
||||
"ASCII": true,
|
||||
"CPU": true,
|
||||
"CSS": true,
|
||||
"DNS": true,
|
||||
"EOF": true,
|
||||
"GUID": true,
|
||||
"HTML": true,
|
||||
"HTTPS": true,
|
||||
"HTTP": true,
|
||||
"ID": true,
|
||||
"IP": true,
|
||||
"IPv4": true,
|
||||
"IPv6": true,
|
||||
"JSON": true,
|
||||
"LHS": true,
|
||||
"OAI": true,
|
||||
"QPS": true,
|
||||
"RAM": true,
|
||||
"RHS": true,
|
||||
"RPC": true,
|
||||
"SLA": true,
|
||||
"SMTP": true,
|
||||
"SQL": true,
|
||||
"SSH": true,
|
||||
"TCP": true,
|
||||
"TLS": true,
|
||||
"TTL": true,
|
||||
"UDP": true,
|
||||
"UI": true,
|
||||
"UID": true,
|
||||
"UUID": true,
|
||||
"URI": true,
|
||||
"URL": true,
|
||||
"UTF8": true,
|
||||
"VM": true,
|
||||
"XML": true,
|
||||
"XMPP": true,
|
||||
"XSRF": true,
|
||||
"XSS": true,
|
||||
}
|
||||
|
||||
// a thread-safe index of initialisms
|
||||
commonInitialisms = newIndexOfInitialisms().load(configuredInitialisms)
|
||||
initialisms = commonInitialisms.sorted()
|
||||
initialismsRunes = asRunes(initialisms)
|
||||
initialismsUpperCased = asUpperCased(initialisms)
|
||||
maxAllocMatches = maxAllocHeuristic(initialismsRunes)
|
||||
|
||||
// a test function
|
||||
isInitialism = commonInitialisms.isInitialism
|
||||
}
|
||||
|
||||
func asRunes(in []string) [][]rune {
|
||||
out := make([][]rune, len(in))
|
||||
for i, initialism := range in {
|
||||
out[i] = []rune(initialism)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func asUpperCased(in []string) [][]rune {
|
||||
out := make([][]rune, len(in))
|
||||
|
||||
for i, initialism := range in {
|
||||
out[i] = []rune(upper(trim(initialism)))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func maxAllocHeuristic(in [][]rune) int {
|
||||
heuristic := make(map[rune]int)
|
||||
for _, initialism := range in {
|
||||
heuristic[initialism[0]]++
|
||||
}
|
||||
|
||||
var maxAlloc int
|
||||
for _, val := range heuristic {
|
||||
if val > maxAlloc {
|
||||
maxAlloc = val
|
||||
}
|
||||
}
|
||||
|
||||
return maxAlloc
|
||||
}
|
||||
|
||||
// AddInitialisms add additional initialisms
|
||||
func AddInitialisms(words ...string) {
|
||||
for _, word := range words {
|
||||
// commonInitialisms[upper(word)] = true
|
||||
commonInitialisms.add(upper(word))
|
||||
}
|
||||
// sort again
|
||||
initialisms = commonInitialisms.sorted()
|
||||
initialismsRunes = asRunes(initialisms)
|
||||
initialismsUpperCased = asUpperCased(initialisms)
|
||||
}
|
||||
|
||||
// indexOfInitialisms is a thread-safe implementation of the sorted index of initialisms.
|
||||
// Since go1.9, this may be implemented with sync.Map.
|
||||
type indexOfInitialisms struct {
|
||||
sortMutex *sync.Mutex
|
||||
index *sync.Map
|
||||
}
|
||||
|
||||
func newIndexOfInitialisms() *indexOfInitialisms {
|
||||
return &indexOfInitialisms{
|
||||
sortMutex: new(sync.Mutex),
|
||||
index: new(sync.Map),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *indexOfInitialisms) load(initial map[string]bool) *indexOfInitialisms {
|
||||
m.sortMutex.Lock()
|
||||
defer m.sortMutex.Unlock()
|
||||
for k, v := range initial {
|
||||
m.index.Store(k, v)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *indexOfInitialisms) isInitialism(key string) bool {
|
||||
_, ok := m.index.Load(key)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m *indexOfInitialisms) add(key string) *indexOfInitialisms {
|
||||
m.index.Store(key, true)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *indexOfInitialisms) sorted() (result []string) {
|
||||
m.sortMutex.Lock()
|
||||
defer m.sortMutex.Unlock()
|
||||
m.index.Range(func(key, _ interface{}) bool {
|
||||
k := key.(string)
|
||||
result = append(result, k)
|
||||
return true
|
||||
})
|
||||
sort.Sort(sort.Reverse(byInitialism(result)))
|
||||
return
|
||||
}
|
||||
|
||||
type byInitialism []string
|
||||
|
||||
func (s byInitialism) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s byInitialism) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
func (s byInitialism) Less(i, j int) bool {
|
||||
if len(s[i]) != len(s[j]) {
|
||||
return len(s[i]) < len(s[j])
|
||||
}
|
||||
|
||||
return strings.Compare(s[i], s[j]) > 0
|
||||
}
|
||||
@ -0,0 +1,312 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package swag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mailru/easyjson/jlexer"
|
||||
"github.com/mailru/easyjson/jwriter"
|
||||
)
|
||||
|
||||
// nullJSON represents a JSON object with null type
|
||||
var nullJSON = []byte("null")
|
||||
|
||||
// DefaultJSONNameProvider the default cache for types
|
||||
var DefaultJSONNameProvider = NewNameProvider()
|
||||
|
||||
const comma = byte(',')
|
||||
|
||||
var closers map[byte]byte
|
||||
|
||||
func init() {
|
||||
closers = map[byte]byte{
|
||||
'{': '}',
|
||||
'[': ']',
|
||||
}
|
||||
}
|
||||
|
||||
type ejMarshaler interface {
|
||||
MarshalEasyJSON(w *jwriter.Writer)
|
||||
}
|
||||
|
||||
type ejUnmarshaler interface {
|
||||
UnmarshalEasyJSON(w *jlexer.Lexer)
|
||||
}
|
||||
|
||||
// WriteJSON writes json data, prefers finding an appropriate interface to short-circuit the marshaler
|
||||
// so it takes the fastest option available.
|
||||
func WriteJSON(data interface{}) ([]byte, error) {
|
||||
if d, ok := data.(ejMarshaler); ok {
|
||||
jw := new(jwriter.Writer)
|
||||
d.MarshalEasyJSON(jw)
|
||||
return jw.BuildBytes()
|
||||
}
|
||||
if d, ok := data.(json.Marshaler); ok {
|
||||
return d.MarshalJSON()
|
||||
}
|
||||
return json.Marshal(data)
|
||||
}
|
||||
|
||||
// ReadJSON reads json data, prefers finding an appropriate interface to short-circuit the unmarshaler
|
||||
// so it takes the fastest option available
|
||||
func ReadJSON(data []byte, value interface{}) error {
|
||||
trimmedData := bytes.Trim(data, "\x00")
|
||||
if d, ok := value.(ejUnmarshaler); ok {
|
||||
jl := &jlexer.Lexer{Data: trimmedData}
|
||||
d.UnmarshalEasyJSON(jl)
|
||||
return jl.Error()
|
||||
}
|
||||
if d, ok := value.(json.Unmarshaler); ok {
|
||||
return d.UnmarshalJSON(trimmedData)
|
||||
}
|
||||
return json.Unmarshal(trimmedData, value)
|
||||
}
|
||||
|
||||
// DynamicJSONToStruct converts an untyped json structure into a struct
|
||||
func DynamicJSONToStruct(data interface{}, target interface{}) error {
|
||||
// TODO: convert straight to a json typed map (mergo + iterate?)
|
||||
b, err := WriteJSON(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ReadJSON(b, target)
|
||||
}
|
||||
|
||||
// ConcatJSON concatenates multiple json objects efficiently
|
||||
func ConcatJSON(blobs ...[]byte) []byte {
|
||||
if len(blobs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
last := len(blobs) - 1
|
||||
for blobs[last] == nil || bytes.Equal(blobs[last], nullJSON) {
|
||||
// strips trailing null objects
|
||||
last--
|
||||
if last < 0 {
|
||||
// there was nothing but "null"s or nil...
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if last == 0 {
|
||||
return blobs[0]
|
||||
}
|
||||
|
||||
var opening, closing byte
|
||||
var idx, a int
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
for i, b := range blobs[:last+1] {
|
||||
if b == nil || bytes.Equal(b, nullJSON) {
|
||||
// a null object is in the list: skip it
|
||||
continue
|
||||
}
|
||||
if len(b) > 0 && opening == 0 { // is this an array or an object?
|
||||
opening, closing = b[0], closers[b[0]]
|
||||
}
|
||||
|
||||
if opening != '{' && opening != '[' {
|
||||
continue // don't know how to concatenate non container objects
|
||||
}
|
||||
|
||||
if len(b) < 3 { // yep empty but also the last one, so closing this thing
|
||||
if i == last && a > 0 {
|
||||
if err := buf.WriteByte(closing); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
idx = 0
|
||||
if a > 0 { // we need to join with a comma for everything beyond the first non-empty item
|
||||
if err := buf.WriteByte(comma); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
idx = 1 // this is not the first or the last so we want to drop the leading bracket
|
||||
}
|
||||
|
||||
if i != last { // not the last one, strip brackets
|
||||
if _, err := buf.Write(b[idx : len(b)-1]); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
} else { // last one, strip only the leading bracket
|
||||
if _, err := buf.Write(b[idx:]); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
a++
|
||||
}
|
||||
// somehow it ended up being empty, so provide a default value
|
||||
if buf.Len() == 0 {
|
||||
if err := buf.WriteByte(opening); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if err := buf.WriteByte(closing); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// ToDynamicJSON turns an object into a properly JSON typed structure
|
||||
func ToDynamicJSON(data interface{}) interface{} {
|
||||
// TODO: convert straight to a json typed map (mergo + iterate?)
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
var res interface{}
|
||||
if err := json.Unmarshal(b, &res); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// FromDynamicJSON turns an object into a properly JSON typed structure
|
||||
func FromDynamicJSON(data, target interface{}) error {
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
return json.Unmarshal(b, target)
|
||||
}
|
||||
|
||||
// NameProvider represents an object capable of translating from go property names
|
||||
// to json property names
|
||||
// This type is thread-safe.
|
||||
type NameProvider struct {
|
||||
lock *sync.Mutex
|
||||
index map[reflect.Type]nameIndex
|
||||
}
|
||||
|
||||
type nameIndex struct {
|
||||
jsonNames map[string]string
|
||||
goNames map[string]string
|
||||
}
|
||||
|
||||
// NewNameProvider creates a new name provider
|
||||
func NewNameProvider() *NameProvider {
|
||||
return &NameProvider{
|
||||
lock: &sync.Mutex{},
|
||||
index: make(map[reflect.Type]nameIndex),
|
||||
}
|
||||
}
|
||||
|
||||
func buildnameIndex(tpe reflect.Type, idx, reverseIdx map[string]string) {
|
||||
for i := 0; i < tpe.NumField(); i++ {
|
||||
targetDes := tpe.Field(i)
|
||||
|
||||
if targetDes.PkgPath != "" { // unexported
|
||||
continue
|
||||
}
|
||||
|
||||
if targetDes.Anonymous { // walk embedded structures tree down first
|
||||
buildnameIndex(targetDes.Type, idx, reverseIdx)
|
||||
continue
|
||||
}
|
||||
|
||||
if tag := targetDes.Tag.Get("json"); tag != "" {
|
||||
|
||||
parts := strings.Split(tag, ",")
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
nm := parts[0]
|
||||
if nm == "-" {
|
||||
continue
|
||||
}
|
||||
if nm == "" { // empty string means we want to use the Go name
|
||||
nm = targetDes.Name
|
||||
}
|
||||
|
||||
idx[nm] = targetDes.Name
|
||||
reverseIdx[targetDes.Name] = nm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newNameIndex(tpe reflect.Type) nameIndex {
|
||||
var idx = make(map[string]string, tpe.NumField())
|
||||
var reverseIdx = make(map[string]string, tpe.NumField())
|
||||
|
||||
buildnameIndex(tpe, idx, reverseIdx)
|
||||
return nameIndex{jsonNames: idx, goNames: reverseIdx}
|
||||
}
|
||||
|
||||
// GetJSONNames gets all the json property names for a type
|
||||
func (n *NameProvider) GetJSONNames(subject interface{}) []string {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
tpe := reflect.Indirect(reflect.ValueOf(subject)).Type()
|
||||
names, ok := n.index[tpe]
|
||||
if !ok {
|
||||
names = n.makeNameIndex(tpe)
|
||||
}
|
||||
|
||||
res := make([]string, 0, len(names.jsonNames))
|
||||
for k := range names.jsonNames {
|
||||
res = append(res, k)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// GetJSONName gets the json name for a go property name
|
||||
func (n *NameProvider) GetJSONName(subject interface{}, name string) (string, bool) {
|
||||
tpe := reflect.Indirect(reflect.ValueOf(subject)).Type()
|
||||
return n.GetJSONNameForType(tpe, name)
|
||||
}
|
||||
|
||||
// GetJSONNameForType gets the json name for a go property name on a given type
|
||||
func (n *NameProvider) GetJSONNameForType(tpe reflect.Type, name string) (string, bool) {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
names, ok := n.index[tpe]
|
||||
if !ok {
|
||||
names = n.makeNameIndex(tpe)
|
||||
}
|
||||
nme, ok := names.goNames[name]
|
||||
return nme, ok
|
||||
}
|
||||
|
||||
func (n *NameProvider) makeNameIndex(tpe reflect.Type) nameIndex {
|
||||
names := newNameIndex(tpe)
|
||||
n.index[tpe] = names
|
||||
return names
|
||||
}
|
||||
|
||||
// GetGoName gets the go name for a json property name
|
||||
func (n *NameProvider) GetGoName(subject interface{}, name string) (string, bool) {
|
||||
tpe := reflect.Indirect(reflect.ValueOf(subject)).Type()
|
||||
return n.GetGoNameForType(tpe, name)
|
||||
}
|
||||
|
||||
// GetGoNameForType gets the go name for a given type for a json property name
|
||||
func (n *NameProvider) GetGoNameForType(tpe reflect.Type, name string) (string, bool) {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
names, ok := n.index[tpe]
|
||||
if !ok {
|
||||
names = n.makeNameIndex(tpe)
|
||||
}
|
||||
nme, ok := names.jsonNames[name]
|
||||
return nme, ok
|
||||
}
|
||||
@ -0,0 +1,176 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package swag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LoadHTTPTimeout the default timeout for load requests
|
||||
var LoadHTTPTimeout = 30 * time.Second
|
||||
|
||||
// LoadHTTPBasicAuthUsername the username to use when load requests require basic auth
|
||||
var LoadHTTPBasicAuthUsername = ""
|
||||
|
||||
// LoadHTTPBasicAuthPassword the password to use when load requests require basic auth
|
||||
var LoadHTTPBasicAuthPassword = ""
|
||||
|
||||
// LoadHTTPCustomHeaders an optional collection of custom HTTP headers for load requests
|
||||
var LoadHTTPCustomHeaders = map[string]string{}
|
||||
|
||||
// LoadFromFileOrHTTP loads the bytes from a file or a remote http server based on the path passed in
|
||||
func LoadFromFileOrHTTP(pth string) ([]byte, error) {
|
||||
return LoadStrategy(pth, os.ReadFile, loadHTTPBytes(LoadHTTPTimeout))(pth)
|
||||
}
|
||||
|
||||
// LoadFromFileOrHTTPWithTimeout loads the bytes from a file or a remote http server based on the path passed in
|
||||
// timeout arg allows for per request overriding of the request timeout
|
||||
func LoadFromFileOrHTTPWithTimeout(pth string, timeout time.Duration) ([]byte, error) {
|
||||
return LoadStrategy(pth, os.ReadFile, loadHTTPBytes(timeout))(pth)
|
||||
}
|
||||
|
||||
// LoadStrategy returns a loader function for a given path or URI.
|
||||
//
|
||||
// The load strategy returns the remote load for any path starting with `http`.
|
||||
// So this works for any URI with a scheme `http` or `https`.
|
||||
//
|
||||
// The fallback strategy is to call the local loader.
|
||||
//
|
||||
// The local loader takes a local file system path (absolute or relative) as argument,
|
||||
// or alternatively a `file://...` URI, **without host** (see also below for windows).
|
||||
//
|
||||
// There are a few liberalities, initially intended to be tolerant regarding the URI syntax,
|
||||
// especially on windows.
|
||||
//
|
||||
// Before the local loader is called, the given path is transformed:
|
||||
// - percent-encoded characters are unescaped
|
||||
// - simple paths (e.g. `./folder/file`) are passed as-is
|
||||
// - on windows, occurrences of `/` are replaced by `\`, so providing a relative path such a `folder/file` works too.
|
||||
//
|
||||
// For paths provided as URIs with the "file" scheme, please note that:
|
||||
// - `file://` is simply stripped.
|
||||
// This means that the host part of the URI is not parsed at all.
|
||||
// For example, `file:///folder/file" becomes "/folder/file`,
|
||||
// but `file://localhost/folder/file` becomes `localhost/folder/file` on unix systems.
|
||||
// Similarly, `file://./folder/file` yields `./folder/file`.
|
||||
// - on windows, `file://...` can take a host so as to specify an UNC share location.
|
||||
//
|
||||
// Reminder about windows-specifics:
|
||||
// - `file://host/folder/file` becomes an UNC path like `\\host\folder\file` (no port specification is supported)
|
||||
// - `file:///c:/folder/file` becomes `C:\folder\file`
|
||||
// - `file://c:/folder/file` is tolerated (without leading `/`) and becomes `c:\folder\file`
|
||||
func LoadStrategy(pth string, local, remote func(string) ([]byte, error)) func(string) ([]byte, error) {
|
||||
if strings.HasPrefix(pth, "http") {
|
||||
return remote
|
||||
}
|
||||
|
||||
return func(p string) ([]byte, error) {
|
||||
upth, err := url.PathUnescape(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(p, `file://`) {
|
||||
// regular file path provided: just normalize slashes
|
||||
return local(filepath.FromSlash(upth))
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
// crude processing: this leaves full URIs with a host with a (mostly) unexpected result
|
||||
upth = strings.TrimPrefix(upth, `file://`)
|
||||
|
||||
return local(filepath.FromSlash(upth))
|
||||
}
|
||||
|
||||
// windows-only pre-processing of file://... URIs
|
||||
|
||||
// support for canonical file URIs on windows.
|
||||
u, err := url.Parse(filepath.ToSlash(upth))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.Host != "" {
|
||||
// assume UNC name (volume share)
|
||||
// NOTE: UNC port not yet supported
|
||||
|
||||
// when the "host" segment is a drive letter:
|
||||
// file://C:/folder/... => C:\folder
|
||||
upth = path.Clean(strings.Join([]string{u.Host, u.Path}, `/`))
|
||||
if !strings.HasSuffix(u.Host, ":") && u.Host[0] != '.' {
|
||||
// tolerance: if we have a leading dot, this can't be a host
|
||||
// file://host/share/folder\... ==> \\host\share\path\folder
|
||||
upth = "//" + upth
|
||||
}
|
||||
} else {
|
||||
// no host, let's figure out if this is a drive letter
|
||||
upth = strings.TrimPrefix(upth, `file://`)
|
||||
first, _, _ := strings.Cut(strings.TrimPrefix(u.Path, "/"), "/")
|
||||
if strings.HasSuffix(first, ":") {
|
||||
// drive letter in the first segment:
|
||||
// file:///c:/folder/... ==> strip the leading slash
|
||||
upth = strings.TrimPrefix(upth, `/`)
|
||||
}
|
||||
}
|
||||
|
||||
return local(filepath.FromSlash(upth))
|
||||
}
|
||||
}
|
||||
|
||||
func loadHTTPBytes(timeout time.Duration) func(path string) ([]byte, error) {
|
||||
return func(path string) ([]byte, error) {
|
||||
client := &http.Client{Timeout: timeout}
|
||||
req, err := http.NewRequest(http.MethodGet, path, nil) //nolint:noctx
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if LoadHTTPBasicAuthUsername != "" && LoadHTTPBasicAuthPassword != "" {
|
||||
req.SetBasicAuth(LoadHTTPBasicAuthUsername, LoadHTTPBasicAuthPassword)
|
||||
}
|
||||
|
||||
for key, val := range LoadHTTPCustomHeaders {
|
||||
req.Header.Set(key, val)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
defer func() {
|
||||
if resp != nil {
|
||||
if e := resp.Body.Close(); e != nil {
|
||||
log.Println(e)
|
||||
}
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("could not access document at %q [%s] ", path, resp.Status)
|
||||
}
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package swag
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type (
|
||||
lexemKind uint8
|
||||
|
||||
nameLexem struct {
|
||||
original string
|
||||
matchedInitialism string
|
||||
kind lexemKind
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
lexemKindCasualName lexemKind = iota
|
||||
lexemKindInitialismName
|
||||
)
|
||||
|
||||
func newInitialismNameLexem(original, matchedInitialism string) nameLexem {
|
||||
return nameLexem{
|
||||
kind: lexemKindInitialismName,
|
||||
original: original,
|
||||
matchedInitialism: matchedInitialism,
|
||||
}
|
||||
}
|
||||
|
||||
func newCasualNameLexem(original string) nameLexem {
|
||||
return nameLexem{
|
||||
kind: lexemKindCasualName,
|
||||
original: original,
|
||||
}
|
||||
}
|
||||
|
||||
func (l nameLexem) GetUnsafeGoName() string {
|
||||
if l.kind == lexemKindInitialismName {
|
||||
return l.matchedInitialism
|
||||
}
|
||||
|
||||
var (
|
||||
first rune
|
||||
rest string
|
||||
)
|
||||
|
||||
for i, orig := range l.original {
|
||||
if i == 0 {
|
||||
first = orig
|
||||
continue
|
||||
}
|
||||
|
||||
if i > 0 {
|
||||
rest = l.original[i:]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(l.original) > 1 {
|
||||
b := poolOfBuffers.BorrowBuffer(utf8.UTFMax + len(rest))
|
||||
defer func() {
|
||||
poolOfBuffers.RedeemBuffer(b)
|
||||
}()
|
||||
b.WriteRune(unicode.ToUpper(first))
|
||||
b.WriteString(lower(rest))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
return l.original
|
||||
}
|
||||
|
||||
func (l nameLexem) GetOriginal() string {
|
||||
return l.original
|
||||
}
|
||||
|
||||
func (l nameLexem) IsInitialism() bool {
|
||||
return l.kind == lexemKindInitialismName
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package swag
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// SplitHostPort splits a network address into a host and a port.
|
||||
// The port is -1 when there is no port to be found
|
||||
func SplitHostPort(addr string) (host string, port int, err error) {
|
||||
h, p, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
if p == "" {
|
||||
return "", -1, &net.AddrError{Err: "missing port in address", Addr: addr}
|
||||
}
|
||||
|
||||
pi, err := strconv.Atoi(p)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
return h, pi, nil
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package swag
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// GOPATHKey represents the env key for gopath
|
||||
GOPATHKey = "GOPATH"
|
||||
)
|
||||
|
||||
// FindInSearchPath finds a package in a provided lists of paths
|
||||
func FindInSearchPath(searchPath, pkg string) string {
|
||||
pathsList := filepath.SplitList(searchPath)
|
||||
for _, path := range pathsList {
|
||||
if evaluatedPath, err := filepath.EvalSymlinks(filepath.Join(path, "src", pkg)); err == nil {
|
||||
if _, err := os.Stat(evaluatedPath); err == nil {
|
||||
return evaluatedPath
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// FindInGoSearchPath finds a package in the $GOPATH:$GOROOT
|
||||
func FindInGoSearchPath(pkg string) string {
|
||||
return FindInSearchPath(FullGoSearchPath(), pkg)
|
||||
}
|
||||
|
||||
// FullGoSearchPath gets the search paths for finding packages
|
||||
func FullGoSearchPath() string {
|
||||
allPaths := os.Getenv(GOPATHKey)
|
||||
if allPaths == "" {
|
||||
allPaths = filepath.Join(os.Getenv("HOME"), "go")
|
||||
}
|
||||
if allPaths != "" {
|
||||
allPaths = strings.Join([]string{allPaths, runtime.GOROOT()}, ":")
|
||||
} else {
|
||||
allPaths = runtime.GOROOT()
|
||||
}
|
||||
return allPaths
|
||||
}
|
||||
@ -0,0 +1,508 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package swag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type (
|
||||
splitter struct {
|
||||
initialisms []string
|
||||
initialismsRunes [][]rune
|
||||
initialismsUpperCased [][]rune // initialisms cached in their trimmed, upper-cased version
|
||||
postSplitInitialismCheck bool
|
||||
}
|
||||
|
||||
splitterOption func(*splitter)
|
||||
|
||||
initialismMatch struct {
|
||||
body []rune
|
||||
start, end int
|
||||
complete bool
|
||||
}
|
||||
initialismMatches []initialismMatch
|
||||
)
|
||||
|
||||
type (
|
||||
// memory pools of temporary objects.
|
||||
//
|
||||
// These are used to recycle temporarily allocated objects
|
||||
// and relieve the GC from undue pressure.
|
||||
|
||||
matchesPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
buffersPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
lexemsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
splittersPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// poolOfMatches holds temporary slices for recycling during the initialism match process
|
||||
poolOfMatches = matchesPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := make(initialismMatches, 0, maxAllocMatches)
|
||||
|
||||
return &s
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
poolOfBuffers = buffersPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
poolOfLexems = lexemsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := make([]nameLexem, 0, maxAllocMatches)
|
||||
|
||||
return &s
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
poolOfSplitters = splittersPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := newSplitter()
|
||||
|
||||
return &s
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// nameReplaceTable finds a word representation for special characters.
|
||||
func nameReplaceTable(r rune) (string, bool) {
|
||||
switch r {
|
||||
case '@':
|
||||
return "At ", true
|
||||
case '&':
|
||||
return "And ", true
|
||||
case '|':
|
||||
return "Pipe ", true
|
||||
case '$':
|
||||
return "Dollar ", true
|
||||
case '!':
|
||||
return "Bang ", true
|
||||
case '-':
|
||||
return "", true
|
||||
case '_':
|
||||
return "", true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
// split calls the splitter.
|
||||
//
|
||||
// Use newSplitter for more control and options
|
||||
func split(str string) []string {
|
||||
s := poolOfSplitters.BorrowSplitter()
|
||||
lexems := s.split(str)
|
||||
result := make([]string, 0, len(*lexems))
|
||||
|
||||
for _, lexem := range *lexems {
|
||||
result = append(result, lexem.GetOriginal())
|
||||
}
|
||||
poolOfLexems.RedeemLexems(lexems)
|
||||
poolOfSplitters.RedeemSplitter(s)
|
||||
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
func newSplitter(options ...splitterOption) splitter {
|
||||
s := splitter{
|
||||
postSplitInitialismCheck: false,
|
||||
initialisms: initialisms,
|
||||
initialismsRunes: initialismsRunes,
|
||||
initialismsUpperCased: initialismsUpperCased,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(&s)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// withPostSplitInitialismCheck allows to catch initialisms after main split process
|
||||
func withPostSplitInitialismCheck(s *splitter) {
|
||||
s.postSplitInitialismCheck = true
|
||||
}
|
||||
|
||||
func (p matchesPool) BorrowMatches() *initialismMatches {
|
||||
s := p.Get().(*initialismMatches)
|
||||
*s = (*s)[:0] // reset slice, keep allocated capacity
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (p buffersPool) BorrowBuffer(size int) *bytes.Buffer {
|
||||
s := p.Get().(*bytes.Buffer)
|
||||
s.Reset()
|
||||
|
||||
if s.Cap() < size {
|
||||
s.Grow(size)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (p lexemsPool) BorrowLexems() *[]nameLexem {
|
||||
s := p.Get().(*[]nameLexem)
|
||||
*s = (*s)[:0] // reset slice, keep allocated capacity
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (p splittersPool) BorrowSplitter(options ...splitterOption) *splitter {
|
||||
s := p.Get().(*splitter)
|
||||
s.postSplitInitialismCheck = false // reset options
|
||||
for _, apply := range options {
|
||||
apply(s)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (p matchesPool) RedeemMatches(s *initialismMatches) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p buffersPool) RedeemBuffer(s *bytes.Buffer) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p lexemsPool) RedeemLexems(s *[]nameLexem) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p splittersPool) RedeemSplitter(s *splitter) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (m initialismMatch) isZero() bool {
|
||||
return m.start == 0 && m.end == 0
|
||||
}
|
||||
|
||||
func (s splitter) split(name string) *[]nameLexem {
|
||||
nameRunes := []rune(name)
|
||||
matches := s.gatherInitialismMatches(nameRunes)
|
||||
if matches == nil {
|
||||
return poolOfLexems.BorrowLexems()
|
||||
}
|
||||
|
||||
return s.mapMatchesToNameLexems(nameRunes, matches)
|
||||
}
|
||||
|
||||
func (s splitter) gatherInitialismMatches(nameRunes []rune) *initialismMatches {
|
||||
var matches *initialismMatches
|
||||
|
||||
for currentRunePosition, currentRune := range nameRunes {
|
||||
// recycle these allocations as we loop over runes
|
||||
// with such recycling, only 2 slices should be allocated per call
|
||||
// instead of o(n).
|
||||
newMatches := poolOfMatches.BorrowMatches()
|
||||
|
||||
// check current initialism matches
|
||||
if matches != nil { // skip first iteration
|
||||
for _, match := range *matches {
|
||||
if keepCompleteMatch := match.complete; keepCompleteMatch {
|
||||
*newMatches = append(*newMatches, match)
|
||||
continue
|
||||
}
|
||||
|
||||
// drop failed match
|
||||
currentMatchRune := match.body[currentRunePosition-match.start]
|
||||
if currentMatchRune != currentRune {
|
||||
continue
|
||||
}
|
||||
|
||||
// try to complete ongoing match
|
||||
if currentRunePosition-match.start == len(match.body)-1 {
|
||||
// we are close; the next step is to check the symbol ahead
|
||||
// if it is a small letter, then it is not the end of match
|
||||
// but beginning of the next word
|
||||
|
||||
if currentRunePosition < len(nameRunes)-1 {
|
||||
nextRune := nameRunes[currentRunePosition+1]
|
||||
if newWord := unicode.IsLower(nextRune); newWord {
|
||||
// oh ok, it was the start of a new word
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
match.complete = true
|
||||
match.end = currentRunePosition
|
||||
}
|
||||
|
||||
*newMatches = append(*newMatches, match)
|
||||
}
|
||||
}
|
||||
|
||||
// check for new initialism matches
|
||||
for i := range s.initialisms {
|
||||
initialismRunes := s.initialismsRunes[i]
|
||||
if initialismRunes[0] == currentRune {
|
||||
*newMatches = append(*newMatches, initialismMatch{
|
||||
start: currentRunePosition,
|
||||
body: initialismRunes,
|
||||
complete: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if matches != nil {
|
||||
poolOfMatches.RedeemMatches(matches)
|
||||
}
|
||||
matches = newMatches
|
||||
}
|
||||
|
||||
// up to the caller to redeem this last slice
|
||||
return matches
|
||||
}
|
||||
|
||||
func (s splitter) mapMatchesToNameLexems(nameRunes []rune, matches *initialismMatches) *[]nameLexem {
|
||||
nameLexems := poolOfLexems.BorrowLexems()
|
||||
|
||||
var lastAcceptedMatch initialismMatch
|
||||
for _, match := range *matches {
|
||||
if !match.complete {
|
||||
continue
|
||||
}
|
||||
|
||||
if firstMatch := lastAcceptedMatch.isZero(); firstMatch {
|
||||
s.appendBrokenDownCasualString(nameLexems, nameRunes[:match.start])
|
||||
*nameLexems = append(*nameLexems, s.breakInitialism(string(match.body)))
|
||||
|
||||
lastAcceptedMatch = match
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if overlappedMatch := match.start <= lastAcceptedMatch.end; overlappedMatch {
|
||||
continue
|
||||
}
|
||||
|
||||
middle := nameRunes[lastAcceptedMatch.end+1 : match.start]
|
||||
s.appendBrokenDownCasualString(nameLexems, middle)
|
||||
*nameLexems = append(*nameLexems, s.breakInitialism(string(match.body)))
|
||||
|
||||
lastAcceptedMatch = match
|
||||
}
|
||||
|
||||
// we have not found any accepted matches
|
||||
if lastAcceptedMatch.isZero() {
|
||||
*nameLexems = (*nameLexems)[:0]
|
||||
s.appendBrokenDownCasualString(nameLexems, nameRunes)
|
||||
} else if lastAcceptedMatch.end+1 != len(nameRunes) {
|
||||
rest := nameRunes[lastAcceptedMatch.end+1:]
|
||||
s.appendBrokenDownCasualString(nameLexems, rest)
|
||||
}
|
||||
|
||||
poolOfMatches.RedeemMatches(matches)
|
||||
|
||||
return nameLexems
|
||||
}
|
||||
|
||||
func (s splitter) breakInitialism(original string) nameLexem {
|
||||
return newInitialismNameLexem(original, original)
|
||||
}
|
||||
|
||||
func (s splitter) appendBrokenDownCasualString(segments *[]nameLexem, str []rune) {
|
||||
currentSegment := poolOfBuffers.BorrowBuffer(len(str)) // unlike strings.Builder, bytes.Buffer initial storage can reused
|
||||
defer func() {
|
||||
poolOfBuffers.RedeemBuffer(currentSegment)
|
||||
}()
|
||||
|
||||
addCasualNameLexem := func(original string) {
|
||||
*segments = append(*segments, newCasualNameLexem(original))
|
||||
}
|
||||
|
||||
addInitialismNameLexem := func(original, match string) {
|
||||
*segments = append(*segments, newInitialismNameLexem(original, match))
|
||||
}
|
||||
|
||||
var addNameLexem func(string)
|
||||
if s.postSplitInitialismCheck {
|
||||
addNameLexem = func(original string) {
|
||||
for i := range s.initialisms {
|
||||
if isEqualFoldIgnoreSpace(s.initialismsUpperCased[i], original) {
|
||||
addInitialismNameLexem(original, s.initialisms[i])
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
addCasualNameLexem(original)
|
||||
}
|
||||
} else {
|
||||
addNameLexem = addCasualNameLexem
|
||||
}
|
||||
|
||||
for _, rn := range str {
|
||||
if replace, found := nameReplaceTable(rn); found {
|
||||
if currentSegment.Len() > 0 {
|
||||
addNameLexem(currentSegment.String())
|
||||
currentSegment.Reset()
|
||||
}
|
||||
|
||||
if replace != "" {
|
||||
addNameLexem(replace)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !unicode.In(rn, unicode.L, unicode.M, unicode.N, unicode.Pc) {
|
||||
if currentSegment.Len() > 0 {
|
||||
addNameLexem(currentSegment.String())
|
||||
currentSegment.Reset()
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if unicode.IsUpper(rn) {
|
||||
if currentSegment.Len() > 0 {
|
||||
addNameLexem(currentSegment.String())
|
||||
}
|
||||
currentSegment.Reset()
|
||||
}
|
||||
|
||||
currentSegment.WriteRune(rn)
|
||||
}
|
||||
|
||||
if currentSegment.Len() > 0 {
|
||||
addNameLexem(currentSegment.String())
|
||||
}
|
||||
}
|
||||
|
||||
// isEqualFoldIgnoreSpace is the same as strings.EqualFold, but
|
||||
// it ignores leading and trailing blank spaces in the compared
|
||||
// string.
|
||||
//
|
||||
// base is assumed to be composed of upper-cased runes, and be already
|
||||
// trimmed.
|
||||
//
|
||||
// This code is heavily inspired from strings.EqualFold.
|
||||
func isEqualFoldIgnoreSpace(base []rune, str string) bool {
|
||||
var i, baseIndex int
|
||||
// equivalent to b := []byte(str), but without data copy
|
||||
b := hackStringBytes(str)
|
||||
|
||||
for i < len(b) {
|
||||
if c := b[i]; c < utf8.RuneSelf {
|
||||
// fast path for ASCII
|
||||
if c != ' ' && c != '\t' {
|
||||
break
|
||||
}
|
||||
i++
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// unicode case
|
||||
r, size := utf8.DecodeRune(b[i:])
|
||||
if !unicode.IsSpace(r) {
|
||||
break
|
||||
}
|
||||
i += size
|
||||
}
|
||||
|
||||
if i >= len(b) {
|
||||
return len(base) == 0
|
||||
}
|
||||
|
||||
for _, baseRune := range base {
|
||||
if i >= len(b) {
|
||||
break
|
||||
}
|
||||
|
||||
if c := b[i]; c < utf8.RuneSelf {
|
||||
// single byte rune case (ASCII)
|
||||
if baseRune >= utf8.RuneSelf {
|
||||
return false
|
||||
}
|
||||
|
||||
baseChar := byte(baseRune)
|
||||
if c != baseChar &&
|
||||
!('a' <= c && c <= 'z' && c-'a'+'A' == baseChar) {
|
||||
return false
|
||||
}
|
||||
|
||||
baseIndex++
|
||||
i++
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// unicode case
|
||||
r, size := utf8.DecodeRune(b[i:])
|
||||
if unicode.ToUpper(r) != baseRune {
|
||||
return false
|
||||
}
|
||||
baseIndex++
|
||||
i += size
|
||||
}
|
||||
|
||||
if baseIndex != len(base) {
|
||||
return false
|
||||
}
|
||||
|
||||
// all passed: now we should only have blanks
|
||||
for i < len(b) {
|
||||
if c := b[i]; c < utf8.RuneSelf {
|
||||
// fast path for ASCII
|
||||
if c != ' ' && c != '\t' {
|
||||
return false
|
||||
}
|
||||
i++
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// unicode case
|
||||
r, size := utf8.DecodeRune(b[i:])
|
||||
if !unicode.IsSpace(r) {
|
||||
return false
|
||||
}
|
||||
|
||||
i += size
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package swag
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// hackStringBytes returns the (unsafe) underlying bytes slice of a string.
|
||||
func hackStringBytes(str string) []byte {
|
||||
return unsafe.Slice(unsafe.StringData(str), len(str))
|
||||
}
|
||||
@ -0,0 +1,364 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package swag
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// GoNamePrefixFunc sets an optional rule to prefix go names
|
||||
// which do not start with a letter.
|
||||
//
|
||||
// The prefix function is assumed to return a string that starts with an upper case letter.
|
||||
//
|
||||
// e.g. to help convert "123" into "{prefix}123"
|
||||
//
|
||||
// The default is to prefix with "X"
|
||||
var GoNamePrefixFunc func(string) string
|
||||
|
||||
func prefixFunc(name, in string) string {
|
||||
if GoNamePrefixFunc == nil {
|
||||
return "X" + in
|
||||
}
|
||||
|
||||
return GoNamePrefixFunc(name) + in
|
||||
}
|
||||
|
||||
const (
|
||||
// collectionFormatComma = "csv"
|
||||
collectionFormatSpace = "ssv"
|
||||
collectionFormatTab = "tsv"
|
||||
collectionFormatPipe = "pipes"
|
||||
collectionFormatMulti = "multi"
|
||||
)
|
||||
|
||||
// JoinByFormat joins a string array by a known format (e.g. swagger's collectionFormat attribute):
|
||||
//
|
||||
// ssv: space separated value
|
||||
// tsv: tab separated value
|
||||
// pipes: pipe (|) separated value
|
||||
// csv: comma separated value (default)
|
||||
func JoinByFormat(data []string, format string) []string {
|
||||
if len(data) == 0 {
|
||||
return data
|
||||
}
|
||||
var sep string
|
||||
switch format {
|
||||
case collectionFormatSpace:
|
||||
sep = " "
|
||||
case collectionFormatTab:
|
||||
sep = "\t"
|
||||
case collectionFormatPipe:
|
||||
sep = "|"
|
||||
case collectionFormatMulti:
|
||||
return data
|
||||
default:
|
||||
sep = ","
|
||||
}
|
||||
return []string{strings.Join(data, sep)}
|
||||
}
|
||||
|
||||
// SplitByFormat splits a string by a known format:
|
||||
//
|
||||
// ssv: space separated value
|
||||
// tsv: tab separated value
|
||||
// pipes: pipe (|) separated value
|
||||
// csv: comma separated value (default)
|
||||
func SplitByFormat(data, format string) []string {
|
||||
if data == "" {
|
||||
return nil
|
||||
}
|
||||
var sep string
|
||||
switch format {
|
||||
case collectionFormatSpace:
|
||||
sep = " "
|
||||
case collectionFormatTab:
|
||||
sep = "\t"
|
||||
case collectionFormatPipe:
|
||||
sep = "|"
|
||||
case collectionFormatMulti:
|
||||
return nil
|
||||
default:
|
||||
sep = ","
|
||||
}
|
||||
var result []string
|
||||
for _, s := range strings.Split(data, sep) {
|
||||
if ts := strings.TrimSpace(s); ts != "" {
|
||||
result = append(result, ts)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Removes leading whitespaces
|
||||
func trim(str string) string {
|
||||
return strings.TrimSpace(str)
|
||||
}
|
||||
|
||||
// Shortcut to strings.ToUpper()
|
||||
func upper(str string) string {
|
||||
return strings.ToUpper(trim(str))
|
||||
}
|
||||
|
||||
// Shortcut to strings.ToLower()
|
||||
func lower(str string) string {
|
||||
return strings.ToLower(trim(str))
|
||||
}
|
||||
|
||||
// Camelize an uppercased word
|
||||
func Camelize(word string) string {
|
||||
camelized := poolOfBuffers.BorrowBuffer(len(word))
|
||||
defer func() {
|
||||
poolOfBuffers.RedeemBuffer(camelized)
|
||||
}()
|
||||
|
||||
for pos, ru := range []rune(word) {
|
||||
if pos > 0 {
|
||||
camelized.WriteRune(unicode.ToLower(ru))
|
||||
} else {
|
||||
camelized.WriteRune(unicode.ToUpper(ru))
|
||||
}
|
||||
}
|
||||
return camelized.String()
|
||||
}
|
||||
|
||||
// ToFileName lowercases and underscores a go type name
|
||||
func ToFileName(name string) string {
|
||||
in := split(name)
|
||||
out := make([]string, 0, len(in))
|
||||
|
||||
for _, w := range in {
|
||||
out = append(out, lower(w))
|
||||
}
|
||||
|
||||
return strings.Join(out, "_")
|
||||
}
|
||||
|
||||
// ToCommandName lowercases and underscores a go type name
|
||||
func ToCommandName(name string) string {
|
||||
in := split(name)
|
||||
out := make([]string, 0, len(in))
|
||||
|
||||
for _, w := range in {
|
||||
out = append(out, lower(w))
|
||||
}
|
||||
return strings.Join(out, "-")
|
||||
}
|
||||
|
||||
// ToHumanNameLower represents a code name as a human series of words
|
||||
func ToHumanNameLower(name string) string {
|
||||
s := poolOfSplitters.BorrowSplitter(withPostSplitInitialismCheck)
|
||||
in := s.split(name)
|
||||
poolOfSplitters.RedeemSplitter(s)
|
||||
out := make([]string, 0, len(*in))
|
||||
|
||||
for _, w := range *in {
|
||||
if !w.IsInitialism() {
|
||||
out = append(out, lower(w.GetOriginal()))
|
||||
} else {
|
||||
out = append(out, trim(w.GetOriginal()))
|
||||
}
|
||||
}
|
||||
poolOfLexems.RedeemLexems(in)
|
||||
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
// ToHumanNameTitle represents a code name as a human series of words with the first letters titleized
|
||||
func ToHumanNameTitle(name string) string {
|
||||
s := poolOfSplitters.BorrowSplitter(withPostSplitInitialismCheck)
|
||||
in := s.split(name)
|
||||
poolOfSplitters.RedeemSplitter(s)
|
||||
|
||||
out := make([]string, 0, len(*in))
|
||||
for _, w := range *in {
|
||||
original := trim(w.GetOriginal())
|
||||
if !w.IsInitialism() {
|
||||
out = append(out, Camelize(original))
|
||||
} else {
|
||||
out = append(out, original)
|
||||
}
|
||||
}
|
||||
poolOfLexems.RedeemLexems(in)
|
||||
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
// ToJSONName camelcases a name which can be underscored or pascal cased
|
||||
func ToJSONName(name string) string {
|
||||
in := split(name)
|
||||
out := make([]string, 0, len(in))
|
||||
|
||||
for i, w := range in {
|
||||
if i == 0 {
|
||||
out = append(out, lower(w))
|
||||
continue
|
||||
}
|
||||
out = append(out, Camelize(trim(w)))
|
||||
}
|
||||
return strings.Join(out, "")
|
||||
}
|
||||
|
||||
// ToVarName camelcases a name which can be underscored or pascal cased
|
||||
func ToVarName(name string) string {
|
||||
res := ToGoName(name)
|
||||
if isInitialism(res) {
|
||||
return lower(res)
|
||||
}
|
||||
if len(res) <= 1 {
|
||||
return lower(res)
|
||||
}
|
||||
return lower(res[:1]) + res[1:]
|
||||
}
|
||||
|
||||
// ToGoName translates a swagger name which can be underscored or camel cased to a name that golint likes
|
||||
func ToGoName(name string) string {
|
||||
s := poolOfSplitters.BorrowSplitter(withPostSplitInitialismCheck)
|
||||
lexems := s.split(name)
|
||||
poolOfSplitters.RedeemSplitter(s)
|
||||
defer func() {
|
||||
poolOfLexems.RedeemLexems(lexems)
|
||||
}()
|
||||
lexemes := *lexems
|
||||
|
||||
if len(lexemes) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
result := poolOfBuffers.BorrowBuffer(len(name))
|
||||
defer func() {
|
||||
poolOfBuffers.RedeemBuffer(result)
|
||||
}()
|
||||
|
||||
// check if not starting with a letter, upper case
|
||||
firstPart := lexemes[0].GetUnsafeGoName()
|
||||
if lexemes[0].IsInitialism() {
|
||||
firstPart = upper(firstPart)
|
||||
}
|
||||
|
||||
if c := firstPart[0]; c < utf8.RuneSelf {
|
||||
// ASCII
|
||||
switch {
|
||||
case 'A' <= c && c <= 'Z':
|
||||
result.WriteString(firstPart)
|
||||
case 'a' <= c && c <= 'z':
|
||||
result.WriteByte(c - 'a' + 'A')
|
||||
result.WriteString(firstPart[1:])
|
||||
default:
|
||||
result.WriteString(prefixFunc(name, firstPart))
|
||||
// NOTE: no longer check if prefixFunc returns a string that starts with uppercase:
|
||||
// assume this is always the case
|
||||
}
|
||||
} else {
|
||||
// unicode
|
||||
firstRune, _ := utf8.DecodeRuneInString(firstPart)
|
||||
switch {
|
||||
case !unicode.IsLetter(firstRune):
|
||||
result.WriteString(prefixFunc(name, firstPart))
|
||||
case !unicode.IsUpper(firstRune):
|
||||
result.WriteString(prefixFunc(name, firstPart))
|
||||
/*
|
||||
result.WriteRune(unicode.ToUpper(firstRune))
|
||||
result.WriteString(firstPart[offset:])
|
||||
*/
|
||||
default:
|
||||
result.WriteString(firstPart)
|
||||
}
|
||||
}
|
||||
|
||||
for _, lexem := range lexemes[1:] {
|
||||
goName := lexem.GetUnsafeGoName()
|
||||
|
||||
// to support old behavior
|
||||
if lexem.IsInitialism() {
|
||||
goName = upper(goName)
|
||||
}
|
||||
result.WriteString(goName)
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// ContainsStrings searches a slice of strings for a case-sensitive match
|
||||
func ContainsStrings(coll []string, item string) bool {
|
||||
for _, a := range coll {
|
||||
if a == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ContainsStringsCI searches a slice of strings for a case-insensitive match
|
||||
func ContainsStringsCI(coll []string, item string) bool {
|
||||
for _, a := range coll {
|
||||
if strings.EqualFold(a, item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type zeroable interface {
|
||||
IsZero() bool
|
||||
}
|
||||
|
||||
// IsZero returns true when the value passed into the function is a zero value.
|
||||
// This allows for safer checking of interface values.
|
||||
func IsZero(data interface{}) bool {
|
||||
v := reflect.ValueOf(data)
|
||||
// check for nil data
|
||||
switch v.Kind() { //nolint:exhaustive
|
||||
case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
if v.IsNil() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// check for things that have an IsZero method instead
|
||||
if vv, ok := data.(zeroable); ok {
|
||||
return vv.IsZero()
|
||||
}
|
||||
|
||||
// continue with slightly more complex reflection
|
||||
switch v.Kind() { //nolint:exhaustive
|
||||
case reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Struct, reflect.Array:
|
||||
return reflect.DeepEqual(data, reflect.Zero(v.Type()).Interface())
|
||||
case reflect.Invalid:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// CommandLineOptionsGroup represents a group of user-defined command line options
|
||||
type CommandLineOptionsGroup struct {
|
||||
ShortDescription string
|
||||
LongDescription string
|
||||
Options interface{}
|
||||
}
|
||||
@ -0,0 +1,481 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package swag
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/mailru/easyjson/jlexer"
|
||||
"github.com/mailru/easyjson/jwriter"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// YAMLMatcher matches yaml
|
||||
func YAMLMatcher(path string) bool {
|
||||
ext := filepath.Ext(path)
|
||||
return ext == ".yaml" || ext == ".yml"
|
||||
}
|
||||
|
||||
// YAMLToJSON converts YAML unmarshaled data into json compatible data
|
||||
func YAMLToJSON(data interface{}) (json.RawMessage, error) {
|
||||
jm, err := transformData(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := WriteJSON(jm)
|
||||
return json.RawMessage(b), err
|
||||
}
|
||||
|
||||
// BytesToYAMLDoc converts a byte slice into a YAML document
|
||||
func BytesToYAMLDoc(data []byte) (interface{}, error) {
|
||||
var document yaml.Node // preserve order that is present in the document
|
||||
if err := yaml.Unmarshal(data, &document); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if document.Kind != yaml.DocumentNode || len(document.Content) != 1 || document.Content[0].Kind != yaml.MappingNode {
|
||||
return nil, errors.New("only YAML documents that are objects are supported")
|
||||
}
|
||||
return &document, nil
|
||||
}
|
||||
|
||||
func yamlNode(root *yaml.Node) (interface{}, error) {
|
||||
switch root.Kind {
|
||||
case yaml.DocumentNode:
|
||||
return yamlDocument(root)
|
||||
case yaml.SequenceNode:
|
||||
return yamlSequence(root)
|
||||
case yaml.MappingNode:
|
||||
return yamlMapping(root)
|
||||
case yaml.ScalarNode:
|
||||
return yamlScalar(root)
|
||||
case yaml.AliasNode:
|
||||
return yamlNode(root.Alias)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported YAML node type: %v", root.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func yamlDocument(node *yaml.Node) (interface{}, error) {
|
||||
if len(node.Content) != 1 {
|
||||
return nil, fmt.Errorf("unexpected YAML Document node content length: %d", len(node.Content))
|
||||
}
|
||||
return yamlNode(node.Content[0])
|
||||
}
|
||||
|
||||
func yamlMapping(node *yaml.Node) (interface{}, error) {
|
||||
m := make(JSONMapSlice, len(node.Content)/2)
|
||||
|
||||
var j int
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
var nmi JSONMapItem
|
||||
k, err := yamlStringScalarC(node.Content[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode YAML map key: %w", err)
|
||||
}
|
||||
nmi.Key = k
|
||||
v, err := yamlNode(node.Content[i+1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to process YAML map value for key %q: %w", k, err)
|
||||
}
|
||||
nmi.Value = v
|
||||
m[j] = nmi
|
||||
j++
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func yamlSequence(node *yaml.Node) (interface{}, error) {
|
||||
s := make([]interface{}, 0)
|
||||
|
||||
for i := 0; i < len(node.Content); i++ {
|
||||
|
||||
v, err := yamlNode(node.Content[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode YAML sequence value: %w", err)
|
||||
}
|
||||
s = append(s, v)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
const ( // See https://yaml.org/type/
|
||||
yamlStringScalar = "tag:yaml.org,2002:str"
|
||||
yamlIntScalar = "tag:yaml.org,2002:int"
|
||||
yamlBoolScalar = "tag:yaml.org,2002:bool"
|
||||
yamlFloatScalar = "tag:yaml.org,2002:float"
|
||||
yamlTimestamp = "tag:yaml.org,2002:timestamp"
|
||||
yamlNull = "tag:yaml.org,2002:null"
|
||||
)
|
||||
|
||||
func yamlScalar(node *yaml.Node) (interface{}, error) {
|
||||
switch node.LongTag() {
|
||||
case yamlStringScalar:
|
||||
return node.Value, nil
|
||||
case yamlBoolScalar:
|
||||
b, err := strconv.ParseBool(node.Value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to process scalar node. Got %q. Expecting bool content: %w", node.Value, err)
|
||||
}
|
||||
return b, nil
|
||||
case yamlIntScalar:
|
||||
i, err := strconv.ParseInt(node.Value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to process scalar node. Got %q. Expecting integer content: %w", node.Value, err)
|
||||
}
|
||||
return i, nil
|
||||
case yamlFloatScalar:
|
||||
f, err := strconv.ParseFloat(node.Value, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to process scalar node. Got %q. Expecting float content: %w", node.Value, err)
|
||||
}
|
||||
return f, nil
|
||||
case yamlTimestamp:
|
||||
return node.Value, nil
|
||||
case yamlNull:
|
||||
return nil, nil //nolint:nilnil
|
||||
default:
|
||||
return nil, fmt.Errorf("YAML tag %q is not supported", node.LongTag())
|
||||
}
|
||||
}
|
||||
|
||||
func yamlStringScalarC(node *yaml.Node) (string, error) {
|
||||
if node.Kind != yaml.ScalarNode {
|
||||
return "", fmt.Errorf("expecting a string scalar but got %q", node.Kind)
|
||||
}
|
||||
switch node.LongTag() {
|
||||
case yamlStringScalar, yamlIntScalar, yamlFloatScalar:
|
||||
return node.Value, nil
|
||||
default:
|
||||
return "", fmt.Errorf("YAML tag %q is not supported as map key", node.LongTag())
|
||||
}
|
||||
}
|
||||
|
||||
// JSONMapSlice represent a JSON object, with the order of keys maintained
|
||||
type JSONMapSlice []JSONMapItem
|
||||
|
||||
// MarshalJSON renders a JSONMapSlice as JSON
|
||||
func (s JSONMapSlice) MarshalJSON() ([]byte, error) {
|
||||
w := &jwriter.Writer{Flags: jwriter.NilMapAsEmpty | jwriter.NilSliceAsEmpty}
|
||||
s.MarshalEasyJSON(w)
|
||||
return w.BuildBytes()
|
||||
}
|
||||
|
||||
// MarshalEasyJSON renders a JSONMapSlice as JSON, using easyJSON
|
||||
func (s JSONMapSlice) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
w.RawByte('{')
|
||||
|
||||
ln := len(s)
|
||||
last := ln - 1
|
||||
for i := 0; i < ln; i++ {
|
||||
s[i].MarshalEasyJSON(w)
|
||||
if i != last { // last item
|
||||
w.RawByte(',')
|
||||
}
|
||||
}
|
||||
|
||||
w.RawByte('}')
|
||||
}
|
||||
|
||||
// UnmarshalJSON makes a JSONMapSlice from JSON
|
||||
func (s *JSONMapSlice) UnmarshalJSON(data []byte) error {
|
||||
l := jlexer.Lexer{Data: data}
|
||||
s.UnmarshalEasyJSON(&l)
|
||||
return l.Error()
|
||||
}
|
||||
|
||||
// UnmarshalEasyJSON makes a JSONMapSlice from JSON, using easyJSON
|
||||
func (s *JSONMapSlice) UnmarshalEasyJSON(in *jlexer.Lexer) {
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
return
|
||||
}
|
||||
|
||||
var result JSONMapSlice
|
||||
in.Delim('{')
|
||||
for !in.IsDelim('}') {
|
||||
var mi JSONMapItem
|
||||
mi.UnmarshalEasyJSON(in)
|
||||
result = append(result, mi)
|
||||
}
|
||||
*s = result
|
||||
}
|
||||
|
||||
func (s JSONMapSlice) MarshalYAML() (interface{}, error) {
|
||||
var n yaml.Node
|
||||
n.Kind = yaml.DocumentNode
|
||||
var nodes []*yaml.Node
|
||||
for _, item := range s {
|
||||
nn, err := json2yaml(item.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ns := []*yaml.Node{
|
||||
{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: yamlStringScalar,
|
||||
Value: item.Key,
|
||||
},
|
||||
nn,
|
||||
}
|
||||
nodes = append(nodes, ns...)
|
||||
}
|
||||
|
||||
n.Content = []*yaml.Node{
|
||||
{
|
||||
Kind: yaml.MappingNode,
|
||||
Content: nodes,
|
||||
},
|
||||
}
|
||||
|
||||
return yaml.Marshal(&n)
|
||||
}
|
||||
|
||||
func isNil(input interface{}) bool {
|
||||
if input == nil {
|
||||
return true
|
||||
}
|
||||
kind := reflect.TypeOf(input).Kind()
|
||||
switch kind { //nolint:exhaustive
|
||||
case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan:
|
||||
return reflect.ValueOf(input).IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func json2yaml(item interface{}) (*yaml.Node, error) {
|
||||
if isNil(item) {
|
||||
return &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: "null",
|
||||
}, nil
|
||||
}
|
||||
|
||||
switch val := item.(type) {
|
||||
case JSONMapSlice:
|
||||
var n yaml.Node
|
||||
n.Kind = yaml.MappingNode
|
||||
for i := range val {
|
||||
childNode, err := json2yaml(&val[i].Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n.Content = append(n.Content, &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: yamlStringScalar,
|
||||
Value: val[i].Key,
|
||||
}, childNode)
|
||||
}
|
||||
return &n, nil
|
||||
case map[string]interface{}:
|
||||
var n yaml.Node
|
||||
n.Kind = yaml.MappingNode
|
||||
keys := make([]string, 0, len(val))
|
||||
for k := range val {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
v := val[k]
|
||||
childNode, err := json2yaml(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n.Content = append(n.Content, &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: yamlStringScalar,
|
||||
Value: k,
|
||||
}, childNode)
|
||||
}
|
||||
return &n, nil
|
||||
case []interface{}:
|
||||
var n yaml.Node
|
||||
n.Kind = yaml.SequenceNode
|
||||
for i := range val {
|
||||
childNode, err := json2yaml(val[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n.Content = append(n.Content, childNode)
|
||||
}
|
||||
return &n, nil
|
||||
case string:
|
||||
return &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: yamlStringScalar,
|
||||
Value: val,
|
||||
}, nil
|
||||
case float64:
|
||||
return &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: yamlFloatScalar,
|
||||
Value: strconv.FormatFloat(val, 'f', -1, 64),
|
||||
}, nil
|
||||
case int64:
|
||||
return &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: yamlIntScalar,
|
||||
Value: strconv.FormatInt(val, 10),
|
||||
}, nil
|
||||
case uint64:
|
||||
return &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: yamlIntScalar,
|
||||
Value: strconv.FormatUint(val, 10),
|
||||
}, nil
|
||||
case bool:
|
||||
return &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: yamlBoolScalar,
|
||||
Value: strconv.FormatBool(val),
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled type: %T", val)
|
||||
}
|
||||
}
|
||||
|
||||
// JSONMapItem represents the value of a key in a JSON object held by JSONMapSlice
|
||||
type JSONMapItem struct {
|
||||
Key string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// MarshalJSON renders a JSONMapItem as JSON
|
||||
func (s JSONMapItem) MarshalJSON() ([]byte, error) {
|
||||
w := &jwriter.Writer{Flags: jwriter.NilMapAsEmpty | jwriter.NilSliceAsEmpty}
|
||||
s.MarshalEasyJSON(w)
|
||||
return w.BuildBytes()
|
||||
}
|
||||
|
||||
// MarshalEasyJSON renders a JSONMapItem as JSON, using easyJSON
|
||||
func (s JSONMapItem) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
w.String(s.Key)
|
||||
w.RawByte(':')
|
||||
w.Raw(WriteJSON(s.Value))
|
||||
}
|
||||
|
||||
// UnmarshalJSON makes a JSONMapItem from JSON
|
||||
func (s *JSONMapItem) UnmarshalJSON(data []byte) error {
|
||||
l := jlexer.Lexer{Data: data}
|
||||
s.UnmarshalEasyJSON(&l)
|
||||
return l.Error()
|
||||
}
|
||||
|
||||
// UnmarshalEasyJSON makes a JSONMapItem from JSON, using easyJSON
|
||||
func (s *JSONMapItem) UnmarshalEasyJSON(in *jlexer.Lexer) {
|
||||
key := in.UnsafeString()
|
||||
in.WantColon()
|
||||
value := in.Interface()
|
||||
in.WantComma()
|
||||
s.Key = key
|
||||
s.Value = value
|
||||
}
|
||||
|
||||
func transformData(input interface{}) (out interface{}, err error) {
|
||||
format := func(t interface{}) (string, error) {
|
||||
switch k := t.(type) {
|
||||
case string:
|
||||
return k, nil
|
||||
case uint:
|
||||
return strconv.FormatUint(uint64(k), 10), nil
|
||||
case uint8:
|
||||
return strconv.FormatUint(uint64(k), 10), nil
|
||||
case uint16:
|
||||
return strconv.FormatUint(uint64(k), 10), nil
|
||||
case uint32:
|
||||
return strconv.FormatUint(uint64(k), 10), nil
|
||||
case uint64:
|
||||
return strconv.FormatUint(k, 10), nil
|
||||
case int:
|
||||
return strconv.Itoa(k), nil
|
||||
case int8:
|
||||
return strconv.FormatInt(int64(k), 10), nil
|
||||
case int16:
|
||||
return strconv.FormatInt(int64(k), 10), nil
|
||||
case int32:
|
||||
return strconv.FormatInt(int64(k), 10), nil
|
||||
case int64:
|
||||
return strconv.FormatInt(k, 10), nil
|
||||
default:
|
||||
return "", fmt.Errorf("unexpected map key type, got: %T", k)
|
||||
}
|
||||
}
|
||||
|
||||
switch in := input.(type) {
|
||||
case yaml.Node:
|
||||
return yamlNode(&in)
|
||||
case *yaml.Node:
|
||||
return yamlNode(in)
|
||||
case map[interface{}]interface{}:
|
||||
o := make(JSONMapSlice, 0, len(in))
|
||||
for ke, va := range in {
|
||||
var nmi JSONMapItem
|
||||
if nmi.Key, err = format(ke); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v, ert := transformData(va)
|
||||
if ert != nil {
|
||||
return nil, ert
|
||||
}
|
||||
nmi.Value = v
|
||||
o = append(o, nmi)
|
||||
}
|
||||
return o, nil
|
||||
case []interface{}:
|
||||
len1 := len(in)
|
||||
o := make([]interface{}, len1)
|
||||
for i := 0; i < len1; i++ {
|
||||
o[i], err = transformData(in[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
// YAMLDoc loads a yaml document from either http or a file and converts it to json
|
||||
func YAMLDoc(path string) (json.RawMessage, error) {
|
||||
yamlDoc, err := YAMLData(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := YAMLToJSON(yamlDoc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// YAMLData loads a yaml document from either http or a file
|
||||
func YAMLData(path string) (interface{}, error) {
|
||||
data, err := LoadFromFileOrHTTP(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return BytesToYAMLDoc(data)
|
||||
}
|
||||
@ -0,0 +1,201 @@
|
||||
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.
|
||||
@ -0,0 +1,245 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package attrmgr contains utilities for managing attributes.
|
||||
// Attributes are added to an X509 certificate as an extension.
|
||||
package attrmgr
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperledger/fabric-protos-go-apiv2/msp"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
// AttrOID is the ASN.1 object identifier for an attribute extension in an
|
||||
// X509 certificate
|
||||
AttrOID = asn1.ObjectIdentifier{1, 2, 3, 4, 5, 6, 7, 8, 1}
|
||||
// AttrOIDString is the string version of AttrOID
|
||||
AttrOIDString = "1.2.3.4.5.6.7.8.1"
|
||||
)
|
||||
|
||||
// Attribute is a name/value pair
|
||||
type Attribute interface {
|
||||
// GetName returns the name of the attribute
|
||||
GetName() string
|
||||
// GetValue returns the value of the attribute
|
||||
GetValue() string
|
||||
}
|
||||
|
||||
// AttributeRequest is a request for an attribute
|
||||
type AttributeRequest interface {
|
||||
// GetName returns the name of an attribute
|
||||
GetName() string
|
||||
// IsRequired returns true if the attribute is required
|
||||
IsRequired() bool
|
||||
}
|
||||
|
||||
// New constructs an attribute manager
|
||||
func New() *Mgr { return &Mgr{} }
|
||||
|
||||
// Mgr is the attribute manager and is the main object for this package
|
||||
type Mgr struct{}
|
||||
|
||||
// ProcessAttributeRequestsForCert add attributes to an X509 certificate, given
|
||||
// attribute requests and attributes.
|
||||
func (mgr *Mgr) ProcessAttributeRequestsForCert(requests []AttributeRequest, attributes []Attribute, cert *x509.Certificate) error {
|
||||
attrs, err := mgr.ProcessAttributeRequests(requests, attributes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mgr.AddAttributesToCert(attrs, cert)
|
||||
}
|
||||
|
||||
// ProcessAttributeRequests takes an array of attribute requests and an identity's attributes
|
||||
// and returns an Attributes object containing the requested attributes.
|
||||
func (mgr *Mgr) ProcessAttributeRequests(requests []AttributeRequest, attributes []Attribute) (*Attributes, error) {
|
||||
attrsMap := map[string]string{}
|
||||
attrs := &Attributes{Attrs: attrsMap}
|
||||
missingRequiredAttrs := []string{}
|
||||
// For each of the attribute requests
|
||||
for _, req := range requests {
|
||||
// Get the attribute
|
||||
name := req.GetName()
|
||||
attr := getAttrByName(name, attributes)
|
||||
if attr == nil {
|
||||
if req.IsRequired() {
|
||||
// Didn't find attribute and it was required; return error below
|
||||
missingRequiredAttrs = append(missingRequiredAttrs, name)
|
||||
}
|
||||
// Skip attribute requests which aren't required
|
||||
continue
|
||||
}
|
||||
attrsMap[name] = attr.GetValue()
|
||||
}
|
||||
if len(missingRequiredAttrs) > 0 {
|
||||
return nil, fmt.Errorf("the following required attributes are missing: %+v",
|
||||
missingRequiredAttrs)
|
||||
}
|
||||
return attrs, nil
|
||||
}
|
||||
|
||||
// AddAttributesToCert adds public attribute info to an X509 certificate.
|
||||
func (mgr *Mgr) AddAttributesToCert(attrs *Attributes, cert *x509.Certificate) error {
|
||||
buf, err := json.Marshal(attrs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal attributes: %s", err)
|
||||
}
|
||||
ext := pkix.Extension{
|
||||
Id: AttrOID,
|
||||
Critical: false,
|
||||
Value: buf,
|
||||
}
|
||||
cert.Extensions = append(cert.Extensions, ext)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAttributesFromCert gets the attributes from a certificate.
|
||||
func (mgr *Mgr) GetAttributesFromCert(cert *x509.Certificate) (*Attributes, error) {
|
||||
// Get certificate attributes from the certificate if it exists
|
||||
buf, err := getAttributesFromCert(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Unmarshal into attributes object
|
||||
attrs := &Attributes{}
|
||||
if buf != nil {
|
||||
err := json.Unmarshal(buf, attrs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal attributes from certificate: %s", err)
|
||||
}
|
||||
}
|
||||
return attrs, nil
|
||||
}
|
||||
|
||||
// GetAttributesFromIdemix ...
|
||||
func (mgr *Mgr) GetAttributesFromIdemix(creator []byte) (*Attributes, error) {
|
||||
if creator == nil {
|
||||
return nil, errors.New("creator is nil")
|
||||
}
|
||||
|
||||
sid := &msp.SerializedIdentity{}
|
||||
err := proto.Unmarshal(creator, sid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal transaction invoker's identity: %s", err)
|
||||
}
|
||||
idemixID := &msp.SerializedIdemixIdentity{}
|
||||
err = proto.Unmarshal(sid.IdBytes, idemixID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal transaction invoker's idemix identity: %s", err)
|
||||
}
|
||||
// Unmarshal into attributes object
|
||||
attrs := &Attributes{
|
||||
Attrs: make(map[string]string),
|
||||
}
|
||||
|
||||
ou := &msp.OrganizationUnit{}
|
||||
err = proto.Unmarshal(idemixID.Ou, ou)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal transaction invoker's ou: %s", err)
|
||||
}
|
||||
attrs.Attrs["ou"] = ou.OrganizationalUnitIdentifier
|
||||
|
||||
role := &msp.MSPRole{}
|
||||
err = proto.Unmarshal(idemixID.Role, role)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal transaction invoker's role: %s", err)
|
||||
}
|
||||
var roleStr string
|
||||
switch role.Role {
|
||||
case 0:
|
||||
roleStr = "member"
|
||||
case 1:
|
||||
roleStr = "admin"
|
||||
case 2:
|
||||
roleStr = "client"
|
||||
case 3:
|
||||
roleStr = "peer"
|
||||
}
|
||||
attrs.Attrs["role"] = roleStr
|
||||
|
||||
return attrs, nil
|
||||
}
|
||||
|
||||
// Attributes contains attribute names and values
|
||||
type Attributes struct {
|
||||
Attrs map[string]string `json:"attrs"`
|
||||
}
|
||||
|
||||
// Names returns the names of the attributes
|
||||
func (a *Attributes) Names() []string {
|
||||
i := 0
|
||||
names := make([]string, len(a.Attrs))
|
||||
for name := range a.Attrs {
|
||||
names[i] = name
|
||||
i++
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// Contains returns true if the named attribute is found
|
||||
func (a *Attributes) Contains(name string) bool {
|
||||
_, ok := a.Attrs[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Value returns an attribute's value
|
||||
func (a *Attributes) Value(name string) (string, bool, error) {
|
||||
attr, ok := a.Attrs[name]
|
||||
return attr, ok, nil
|
||||
}
|
||||
|
||||
// True returns nil if the value of attribute 'name' is true;
|
||||
// otherwise, an appropriate error is returned.
|
||||
func (a *Attributes) True(name string) error {
|
||||
val, ok, err := a.Value(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("Attribute '%s' was not found", name)
|
||||
}
|
||||
if val != "true" {
|
||||
return fmt.Errorf("Attribute '%s' is not true", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the attribute info from a certificate extension, or return nil if not found
|
||||
func getAttributesFromCert(cert *x509.Certificate) ([]byte, error) {
|
||||
for _, ext := range cert.Extensions {
|
||||
if isAttrOID(ext.Id) {
|
||||
return ext.Value, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Is the object ID equal to the attribute info object ID?
|
||||
func isAttrOID(oid asn1.ObjectIdentifier) bool {
|
||||
if len(oid) != len(AttrOID) {
|
||||
return false
|
||||
}
|
||||
for idx, val := range oid {
|
||||
if val != AttrOID[idx] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Get an attribute from 'attrs' by its name, or nil if not found
|
||||
func getAttrByName(name string, attrs []Attribute) Attribute {
|
||||
for _, attr := range attrs {
|
||||
if attr.GetName() == name {
|
||||
return attr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,235 @@
|
||||
# Client Identity Chaincode Library
|
||||
|
||||
The client identity chaincode library enables you to write chaincode which
|
||||
makes access control decisions based on the identity of the client
|
||||
(i.e. the invoker of the chaincode). In particular, you may make access
|
||||
control decisions based on any or a combination of the following information associated with
|
||||
the client:
|
||||
|
||||
* the client identity's MSP (Membership Service Provider) ID
|
||||
* an attribute associated with the client identity
|
||||
* an OU (Organizational Unit) value associated with the client identity
|
||||
|
||||
Attributes are simply name and value pairs associated with an identity.
|
||||
For example, `email=me@gmail.com` indicates an identity has the `email`
|
||||
attribute with a value of `me@gmail.com`.
|
||||
|
||||
## Using the client identity chaincode library
|
||||
|
||||
This section describes how to use the client identity chaincode library.
|
||||
|
||||
All code samples below assume two things:
|
||||
|
||||
1. The type of the `stub` variable is `ChaincodeStubInterface` as passed
|
||||
to your chaincode.
|
||||
2. You have added the following import statement to your chaincode.
|
||||
|
||||
```golang
|
||||
import "github.com/hyperledger/fabric-chaincode-go/v2/pkg/cid"
|
||||
```
|
||||
|
||||
### Getting the client's ID
|
||||
|
||||
The following demonstrates how to get an ID for the client which is guaranteed
|
||||
to be unique within the MSP:
|
||||
|
||||
```golang
|
||||
id, err := cid.GetID(stub)
|
||||
```
|
||||
|
||||
### Getting the MSP ID
|
||||
|
||||
The following demonstrates how to get the MSP ID of the client's identity:
|
||||
|
||||
```golang
|
||||
mspid, err := cid.GetMSPID(stub)
|
||||
```
|
||||
|
||||
### Getting an attribute value
|
||||
|
||||
The following demonstrates how to get the value of the *attr1* attribute:
|
||||
|
||||
```golang
|
||||
val, ok, err := cid.GetAttributeValue(stub, "attr1")
|
||||
if err != nil {
|
||||
// There was an error trying to retrieve the attribute
|
||||
}
|
||||
if !ok {
|
||||
// The client identity does not possess the attribute
|
||||
}
|
||||
// Do something with the value of 'val'
|
||||
```
|
||||
|
||||
### Asserting an attribute value
|
||||
|
||||
Often all you want to do is to make an access control decision based on the value
|
||||
of an attribute, i.e. to assert the value of an attribute. For example, the following
|
||||
will return an error if the client does not have the `myapp.admin` attribute
|
||||
with a value of `true`:
|
||||
|
||||
```golang
|
||||
err := cid.AssertAttributeValue(stub, "myapp.admin", "true")
|
||||
if err != nil {
|
||||
// Return an error
|
||||
}
|
||||
```
|
||||
|
||||
This is effectively using attributes to implement role-based access control,
|
||||
or RBAC for short.
|
||||
|
||||
### Checking for a specific OU value
|
||||
|
||||
```golang
|
||||
found, err := cid.HasOUValue(stub, "myapp.admin")
|
||||
if err != nil {
|
||||
// Return an error
|
||||
}
|
||||
if !found {
|
||||
// The client identity is not part of the Organizational Unit
|
||||
// Return an error
|
||||
}
|
||||
```
|
||||
|
||||
### Getting the client's X509 certificate
|
||||
|
||||
The following demonstrates how to get the X509 certificate of the client, or
|
||||
nil if the client's identity was not based on an X509 certificate:
|
||||
|
||||
```golang
|
||||
cert, err := cid.GetX509Certificate(stub)
|
||||
```
|
||||
|
||||
Note that both `cert` and `err` may be nil as will be the case if the identity
|
||||
is not using an X509 certificate.
|
||||
|
||||
### Performing multiple operations more efficiently
|
||||
|
||||
Sometimes you may need to perform multiple operations in order to make an access
|
||||
decision. For example, the following demonstrates how to grant access to
|
||||
identities with MSP *org1MSP* and *attr1* OR with MSP *org1MSP* and *attr2*.
|
||||
|
||||
```golang
|
||||
// Get the Client ID object
|
||||
id, err := cid.New(stub)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
mspid, err := id.GetMSPID()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
switch mspid {
|
||||
case "org1MSP":
|
||||
err = id.AssertAttributeValue("attr1", "true")
|
||||
case "org2MSP":
|
||||
err = id.AssertAttributeValue("attr2", "true")
|
||||
default:
|
||||
err = errors.New("Wrong MSP")
|
||||
}
|
||||
```
|
||||
|
||||
Although it is not required, it is more efficient to make the `cid.New` call
|
||||
to get the ClientID object if you need to perform multiple operations,
|
||||
as demonstrated above.
|
||||
|
||||
## Adding Attributes to Identities
|
||||
|
||||
This section describes how to add custom attributes to certificates when
|
||||
using Hyperledger Fabric CA as well as when using an external CA.
|
||||
|
||||
### Managing attributes with Fabric CA
|
||||
|
||||
There are two methods of adding attributes to an enrollment certificate
|
||||
with fabric-ca:
|
||||
|
||||
1. When you register an identity, you can specify that an enrollment certificate
|
||||
issued for the identity should by default contain an attribute. This behavior
|
||||
can be overridden at enrollment time, but this is useful for establishing
|
||||
default behavior and, assuming registration occurs outside of your application,
|
||||
does not require any application change.
|
||||
|
||||
The following shows how to register *user1* with two attributes:
|
||||
*app1Admin* and *email*.
|
||||
The ":ecert" suffix causes the *appAdmin* attribute to be inserted into user1's
|
||||
enrollment certificate by default. The *email* attribute is not added
|
||||
to the enrollment certificate by default.
|
||||
|
||||
```bash
|
||||
fabric-ca-client register --id.name user1 --id.secret user1pw --id.type user --id.affiliation org1 --id.attrs 'app1Admin=true:ecert,email=user1@gmail.com'
|
||||
```
|
||||
|
||||
2. When you enroll an identity, you may request that one or more attributes
|
||||
be added to the certificate.
|
||||
For each attribute requested, you may specify whether the attribute is
|
||||
optional or not. If it is not optional but does not exist for the identity,
|
||||
enrollment fails.
|
||||
|
||||
The following shows how to enroll *user1* with the *email* attribute,
|
||||
without the *app1Admin* attribute and optionally with the *phone* attribute
|
||||
(if the user possesses *phone* attribute).
|
||||
|
||||
```bash
|
||||
fabric-ca-client enroll -u http://user1:user1pw@localhost:7054 --enrollment.attrs "email,phone:opt"
|
||||
```
|
||||
|
||||
#### Attribute format in a certificate
|
||||
|
||||
Attributes are stored inside an X509 certificate as an extension with an
|
||||
ASN.1 OID (Abstract Syntax Notation Object IDentifier)
|
||||
of `1.2.3.4.5.6.7.8.1`. The value of the extension is a JSON string of the
|
||||
form `{"attrs":{<attrName>:<attrValue}}`. The following is a sample of a
|
||||
certificate which contains the `attr1` attribute with a value of `val1`.
|
||||
See the final entry in the *X509v3 extensions* section. Note also that the JSON
|
||||
entry could contain multiple attributes, though this sample shows only one.
|
||||
|
||||
```
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number:
|
||||
1e:49:98:e9:f4:4f:d0:03:53:bf:36:81:c0:a0:a4:31:96:4f:52:75
|
||||
Signature Algorithm: ecdsa-with-SHA256
|
||||
Issuer: CN=fabric-ca-server
|
||||
Validity
|
||||
Not Before: Sep 8 03:42:00 2017 GMT
|
||||
Not After : Sep 8 03:42:00 2018 GMT
|
||||
Subject: CN=MyTestUserWithAttrs
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: id-ecPublicKey
|
||||
EC Public Key:
|
||||
pub:
|
||||
04:e6:07:5a:f7:09:d5:af:38:e3:f7:a2:90:77:0e:
|
||||
32:67:5b:70:a7:37:ca:b5:c9:d8:91:77:39:ae:03:
|
||||
a0:36:ad:72:b3:3c:89:6d:1e:f6:1b:6d:2a:88:49:
|
||||
92:6e:6e:cc:bc:81:52:fa:19:88:18:5c:d7:6e:eb:
|
||||
d4:73:cc:51:79
|
||||
ASN1 OID: prime256v1
|
||||
X509v3 extensions:
|
||||
X509v3 Key Usage: critical
|
||||
Certificate Sign
|
||||
X509v3 Basic Constraints: critical
|
||||
CA:FALSE
|
||||
X509v3 Subject Key Identifier:
|
||||
D8:28:B4:C0:BC:92:4A:D3:C3:8C:54:6C:08:86:33:10:A6:8D:83:AE
|
||||
X509v3 Authority Key Identifier:
|
||||
keyid:C4:B3:FE:76:0D:E2:DE:3C:FC:75:FB:AE:55:86:04:F0:BB:7F:F6:01
|
||||
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:Anils-MacBook-Pro.local
|
||||
1.2.3.4.5.6.7.8.1:
|
||||
{"attrs":{"attr1":"val1"}}
|
||||
Signature Algorithm: ecdsa-with-SHA256
|
||||
30:45:02:21:00:fb:84:a9:65:29:b2:f4:d3:bc:1a:8b:47:92:
|
||||
5e:41:27:2d:26:ec:f7:cd:aa:86:46:a4:ac:da:25:be:40:1d:
|
||||
c5:02:20:08:3f:49:86:58:a7:20:48:64:4c:30:1b:da:a9:a2:
|
||||
f2:b4:16:28:f6:fd:e1:46:dd:6b:f2:3f:2f:37:4a:4c:72
|
||||
```
|
||||
|
||||
If you want to use the client identity library to extract or assert attribute
|
||||
values as described previously but you are not using Hyperledger Fabric CA,
|
||||
then you must ensure that the certificates which are issued by your external CA
|
||||
contain attributes of the form shown above. In particular, the certificates
|
||||
must contain the `1.2.3.4.5.6.7.8.1` X509v3 extension with a JSON value
|
||||
containing the attribute names and values for the identity.
|
||||
|
||||
If your external CA does not support extensions with an unknown OID like `1.2.3.4.5.6.7.8.1`, the OU fields of the certificate subject's DN can be used as an alternative to the attributes and the HasOUValue function of the cid library can be used in the chaincode to control access.
|
||||
@ -0,0 +1,278 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cid
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperledger/fabric-chaincode-go/v2/pkg/attrmgr"
|
||||
"github.com/hyperledger/fabric-protos-go-apiv2/msp"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// GetID returns the ID associated with the invoking identity. This ID
|
||||
// is guaranteed to be unique within the MSP.
|
||||
func GetID(stub ChaincodeStubInterface) (string, error) {
|
||||
c, err := New(stub)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return c.GetID()
|
||||
}
|
||||
|
||||
// GetMSPID returns the ID of the MSP associated with the identity that
|
||||
// submitted the transaction
|
||||
func GetMSPID(stub ChaincodeStubInterface) (string, error) {
|
||||
c, err := New(stub)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return c.GetMSPID()
|
||||
}
|
||||
|
||||
// GetAttributeValue returns value of the specified attribute
|
||||
func GetAttributeValue(stub ChaincodeStubInterface, attrName string) (value string, found bool, err error) {
|
||||
c, err := New(stub)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
return c.GetAttributeValue(attrName)
|
||||
}
|
||||
|
||||
// AssertAttributeValue checks to see if an attribute value equals the specified value
|
||||
func AssertAttributeValue(stub ChaincodeStubInterface, attrName, attrValue string) error {
|
||||
c, err := New(stub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.AssertAttributeValue(attrName, attrValue)
|
||||
}
|
||||
|
||||
// HasOUValue checks if an OU with the specified value is present
|
||||
func HasOUValue(stub ChaincodeStubInterface, OUValue string) (bool, error) {
|
||||
c, err := New(stub)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return c.HasOUValue(OUValue)
|
||||
}
|
||||
|
||||
// GetX509Certificate returns the X509 certificate associated with the client,
|
||||
// or nil if it was not identified by an X509 certificate.
|
||||
func GetX509Certificate(stub ChaincodeStubInterface) (*x509.Certificate, error) {
|
||||
c, err := New(stub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.GetX509Certificate()
|
||||
}
|
||||
|
||||
// ClientID holds the information of the transaction creator.
|
||||
type ClientID struct {
|
||||
stub ChaincodeStubInterface
|
||||
mspID string
|
||||
cert *x509.Certificate
|
||||
attrs *attrmgr.Attributes
|
||||
}
|
||||
|
||||
// New returns an instance of ClientID
|
||||
func New(stub ChaincodeStubInterface) (*ClientID, error) {
|
||||
c := &ClientID{stub: stub}
|
||||
err := c.init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// GetID returns a unique ID associated with the invoking identity.
|
||||
func (c *ClientID) GetID() (string, error) {
|
||||
// When IdeMix, c.cert is nil for x509 type
|
||||
// Here will return "", as there is no x509 type cert for generate id value with logic below.
|
||||
if c.cert == nil {
|
||||
return "", fmt.Errorf("cannot determine identity")
|
||||
}
|
||||
// The leading "x509::" distinguishes this as an X509 certificate, and
|
||||
// the subject and issuer DNs uniquely identify the X509 certificate.
|
||||
// The resulting ID will remain the same if the certificate is renewed.
|
||||
id := fmt.Sprintf("x509::%s::%s", getDN(&c.cert.Subject), getDN(&c.cert.Issuer))
|
||||
return base64.StdEncoding.EncodeToString([]byte(id)), nil
|
||||
}
|
||||
|
||||
// GetMSPID returns the ID of the MSP associated with the identity that
|
||||
// submitted the transaction
|
||||
func (c *ClientID) GetMSPID() (string, error) {
|
||||
return c.mspID, nil
|
||||
}
|
||||
|
||||
// GetAttributeValue returns value of the specified attribute
|
||||
func (c *ClientID) GetAttributeValue(attrName string) (value string, found bool, err error) {
|
||||
if c.attrs == nil {
|
||||
return "", false, nil
|
||||
}
|
||||
return c.attrs.Value(attrName)
|
||||
}
|
||||
|
||||
// AssertAttributeValue checks to see if an attribute value equals the specified value
|
||||
func (c *ClientID) AssertAttributeValue(attrName, attrValue string) error {
|
||||
val, ok, err := c.GetAttributeValue(attrName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("attribute '%s' was not found", attrName)
|
||||
}
|
||||
if val != attrValue {
|
||||
return fmt.Errorf("attribute '%s' equals '%s', not '%s'", attrName, val, attrValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasOUValue checks if an OU with the specified value is present
|
||||
func (c *ClientID) HasOUValue(OUValue string) (bool, error) {
|
||||
x509Cert := c.cert
|
||||
if x509Cert == nil {
|
||||
// Here it will return false and an error, as there is no x509 type cert to check for OU values.
|
||||
return false, fmt.Errorf("cannot obtain an X509 certificate for the identity")
|
||||
}
|
||||
|
||||
for _, OU := range x509Cert.Subject.OrganizationalUnit {
|
||||
if OU == OUValue {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GetX509Certificate returns the X509 certificate associated with the client,
|
||||
// or nil if it was not identified by an X509 certificate.
|
||||
func (c *ClientID) GetX509Certificate() (*x509.Certificate, error) {
|
||||
return c.cert, nil
|
||||
}
|
||||
|
||||
// Initialize the client
|
||||
func (c *ClientID) init() error {
|
||||
signingID, err := c.getIdentity()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.mspID = signingID.GetMspid()
|
||||
idbytes := signingID.GetIdBytes()
|
||||
block, _ := pem.Decode(idbytes)
|
||||
if block == nil {
|
||||
err := c.getAttributesFromIdemix()
|
||||
if err != nil {
|
||||
return fmt.Errorf("identity bytes are neither X509 PEM format nor an idemix credential: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse certificate: %s", err)
|
||||
}
|
||||
c.cert = cert
|
||||
attrs, err := attrmgr.New().GetAttributesFromCert(cert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get attributes from the transaction invoker's certificate: %s", err)
|
||||
}
|
||||
c.attrs = attrs
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmarshals the bytes returned by ChaincodeStubInterface.GetCreator method and
|
||||
// returns the resulting msp.SerializedIdentity object
|
||||
func (c *ClientID) getIdentity() (*msp.SerializedIdentity, error) {
|
||||
sid := &msp.SerializedIdentity{}
|
||||
creator, err := c.stub.GetCreator()
|
||||
if err != nil || creator == nil {
|
||||
return nil, fmt.Errorf("failed to get transaction invoker's identity from the chaincode stub: %w", err)
|
||||
}
|
||||
err = proto.Unmarshal(creator, sid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal transaction invoker's identity: %w", err)
|
||||
}
|
||||
return sid, nil
|
||||
}
|
||||
|
||||
func (c *ClientID) getAttributesFromIdemix() error {
|
||||
creator, err := c.stub.GetCreator()
|
||||
if err != nil || creator == nil {
|
||||
return fmt.Errorf("failed to get transaction invoker's identity from the chaincode stub: %w", err)
|
||||
}
|
||||
attrs, err := attrmgr.New().GetAttributesFromIdemix(creator)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get attributes from the transaction invoker's idemix credential: %w", err)
|
||||
}
|
||||
c.attrs = attrs
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the DN (distinguished name) associated with a pkix.Name.
|
||||
// NOTE: This code is almost a direct copy of the String() function in
|
||||
// https://go-review.googlesource.com/c/go/+/67270/1/src/crypto/x509/pkix/pkix.go#26
|
||||
// which returns a DN as defined by RFC 2253.
|
||||
func getDN(name *pkix.Name) string {
|
||||
r := name.ToRDNSequence()
|
||||
s := ""
|
||||
for i := 0; i < len(r); i++ {
|
||||
rdn := r[len(r)-1-i]
|
||||
if i > 0 {
|
||||
s += ","
|
||||
}
|
||||
for j, tv := range rdn {
|
||||
if j > 0 {
|
||||
s += "+"
|
||||
}
|
||||
typeString := tv.Type.String()
|
||||
typeName, ok := attributeTypeNames[typeString]
|
||||
if !ok {
|
||||
derBytes, err := asn1.Marshal(tv.Value)
|
||||
if err == nil {
|
||||
s += typeString + "=#" + hex.EncodeToString(derBytes)
|
||||
continue // No value escaping necessary.
|
||||
}
|
||||
typeName = typeString
|
||||
}
|
||||
valueString := fmt.Sprint(tv.Value)
|
||||
escaped := ""
|
||||
begin := 0
|
||||
for idx, c := range valueString {
|
||||
if (idx == 0 && (c == ' ' || c == '#')) ||
|
||||
(idx == len(valueString)-1 && c == ' ') {
|
||||
escaped += valueString[begin:idx]
|
||||
escaped += "\\" + string(c)
|
||||
begin = idx + 1
|
||||
continue
|
||||
}
|
||||
switch c {
|
||||
case ',', '+', '"', '\\', '<', '>', ';':
|
||||
escaped += valueString[begin:idx]
|
||||
escaped += "\\" + string(c)
|
||||
begin = idx + 1
|
||||
}
|
||||
}
|
||||
escaped += valueString[begin:]
|
||||
s += typeName + "=" + escaped
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var attributeTypeNames = map[string]string{
|
||||
"2.5.4.6": "C",
|
||||
"2.5.4.10": "O",
|
||||
"2.5.4.11": "OU",
|
||||
"2.5.4.3": "CN",
|
||||
"2.5.4.5": "SERIALNUMBER",
|
||||
"2.5.4.7": "L",
|
||||
"2.5.4.8": "ST",
|
||||
"2.5.4.9": "STREET",
|
||||
"2.5.4.17": "POSTALCODE",
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cid
|
||||
|
||||
import "crypto/x509"
|
||||
|
||||
// ChaincodeStubInterface is used by deployable chaincode apps to get identity
|
||||
// of the agent (or user) submitting the transaction.
|
||||
type ChaincodeStubInterface interface {
|
||||
// GetCreator returns `SignatureHeader.Creator` (e.g. an identity)
|
||||
// of the `SignedProposal`. This is the identity of the agent (or user)
|
||||
// submitting the transaction.
|
||||
GetCreator() ([]byte, error)
|
||||
}
|
||||
|
||||
// ClientIdentity represents information about the identity that submitted the
|
||||
// transaction
|
||||
type ClientIdentity interface {
|
||||
|
||||
// GetID returns the ID associated with the invoking identity. This ID
|
||||
// is guaranteed to be unique within the MSP.
|
||||
GetID() (string, error)
|
||||
|
||||
// Return the MSP ID of the client
|
||||
GetMSPID() (string, error)
|
||||
|
||||
// GetAttributeValue returns the value of the client's attribute named `attrName`.
|
||||
// If the client possesses the attribute, `found` is true and `value` equals the
|
||||
// value of the attribute.
|
||||
// If the client does not possess the attribute, `found` is false and `value`
|
||||
// equals "".
|
||||
GetAttributeValue(attrName string) (value string, found bool, err error)
|
||||
|
||||
// AssertAttributeValue verifies that the client has the attribute named `attrName`
|
||||
// with a value of `attrValue`; otherwise, an error is returned.
|
||||
AssertAttributeValue(attrName, attrValue string) error
|
||||
|
||||
// GetX509Certificate returns the X509 certificate associated with the client,
|
||||
// or nil if it was not identified by an X509 certificate.
|
||||
GetX509Certificate() (*x509.Certificate, error)
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package shim
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
|
||||
"github.com/hyperledger/fabric-chaincode-go/v2/shim/internal"
|
||||
"github.com/hyperledger/fabric-protos-go-apiv2/peer"
|
||||
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
// TLSProperties passed to ChaincodeServer
|
||||
type TLSProperties struct {
|
||||
//Disabled forces default to be TLS enabled
|
||||
Disabled bool
|
||||
Key []byte
|
||||
Cert []byte
|
||||
// ClientCACerts set if connecting peer should be verified
|
||||
ClientCACerts []byte
|
||||
}
|
||||
|
||||
// ChaincodeServer encapsulates basic properties needed for a chaincode server
|
||||
type ChaincodeServer struct {
|
||||
// CCID should match chaincode's package name on peer
|
||||
CCID string
|
||||
// Addesss is the listen address of the chaincode server
|
||||
Address string
|
||||
// CC is the chaincode that handles Init and Invoke
|
||||
CC Chaincode
|
||||
// TLSProps is the TLS properties passed to chaincode server
|
||||
TLSProps TLSProperties
|
||||
// KaOpts keepalive options, sensible defaults provided if nil
|
||||
KaOpts *keepalive.ServerParameters
|
||||
}
|
||||
|
||||
// Connect the bidi stream entry point called by chaincode to register with the Peer.
|
||||
func (cs *ChaincodeServer) Connect(stream peer.Chaincode_ConnectServer) error {
|
||||
return chatWithPeer(cs.CCID, stream, cs.CC)
|
||||
}
|
||||
|
||||
// Start the server
|
||||
func (cs *ChaincodeServer) Start() error {
|
||||
if cs.CCID == "" {
|
||||
return errors.New("ccid must be specified")
|
||||
}
|
||||
|
||||
if cs.Address == "" {
|
||||
return errors.New("address must be specified")
|
||||
}
|
||||
|
||||
if cs.CC == nil {
|
||||
return errors.New("chaincode must be specified")
|
||||
}
|
||||
|
||||
var tlsCfg *tls.Config
|
||||
var err error
|
||||
if !cs.TLSProps.Disabled {
|
||||
tlsCfg, err = internal.LoadTLSConfig(true, cs.TLSProps.Key, cs.TLSProps.Cert, cs.TLSProps.ClientCACerts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// create listener and grpc server
|
||||
server, err := internal.NewServer(cs.Address, tlsCfg, cs.KaOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// register the server with grpc ...
|
||||
peer.RegisterChaincodeServer(server.Server, cs)
|
||||
|
||||
// ... and start
|
||||
return server.Start()
|
||||
}
|
||||
@ -0,0 +1,708 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package shim
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/hyperledger/fabric-protos-go-apiv2/peer"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type state string
|
||||
|
||||
const (
|
||||
created state = "created" // start state
|
||||
established state = "established" // connection established
|
||||
ready state = "ready" // ready for requests
|
||||
)
|
||||
|
||||
// PeerChaincodeStream is the common stream interface for Peer - chaincode communication.
|
||||
// Both chaincode-as-server and chaincode-as-client patterns need to support this
|
||||
type PeerChaincodeStream interface {
|
||||
Send(*peer.ChaincodeMessage) error
|
||||
Recv() (*peer.ChaincodeMessage, error)
|
||||
}
|
||||
|
||||
// ClientStream supports the (original) chaincode-as-client interaction pattern
|
||||
type ClientStream interface {
|
||||
PeerChaincodeStream
|
||||
CloseSend() error
|
||||
}
|
||||
|
||||
// Handler handler implementation for shim side of chaincode.
|
||||
type Handler struct {
|
||||
// serialLock is used to prevent concurrent calls to Send on the
|
||||
// PeerChaincodeStream. This is required by gRPC.
|
||||
serialLock sync.Mutex
|
||||
// chatStream is the client used to access the chaincode support server on
|
||||
// the peer.
|
||||
chatStream PeerChaincodeStream
|
||||
|
||||
// cc is the chaincode associated with this handler.
|
||||
cc Chaincode
|
||||
// state holds the current state of this handler.
|
||||
state state
|
||||
|
||||
// Multiple queries (and one transaction) with different txids can be executing in parallel for this chaincode
|
||||
// responseChannels is the channel on which responses are communicated by the shim to the chaincodeStub.
|
||||
// need lock to protect chaincode from attempting
|
||||
// concurrent requests to the peer
|
||||
responseChannelsMutex sync.Mutex
|
||||
responseChannels map[string]chan *peer.ChaincodeMessage
|
||||
}
|
||||
|
||||
func shorttxid(txid string) string {
|
||||
if len(txid) < 8 {
|
||||
return txid
|
||||
}
|
||||
return txid[0:8]
|
||||
}
|
||||
|
||||
// serialSend serializes calls to Send on the gRPC client.
|
||||
func (h *Handler) serialSend(msg *peer.ChaincodeMessage) error {
|
||||
h.serialLock.Lock()
|
||||
defer h.serialLock.Unlock()
|
||||
|
||||
return h.chatStream.Send(msg)
|
||||
}
|
||||
|
||||
// serialSendAsync sends the provided message asynchronously in a separate
|
||||
// goroutine. The result of the send is communicated back to the caller via
|
||||
// errc.
|
||||
func (h *Handler) serialSendAsync(msg *peer.ChaincodeMessage, errc chan<- error) {
|
||||
go func() {
|
||||
errc <- h.serialSend(msg)
|
||||
}()
|
||||
}
|
||||
|
||||
// transactionContextID builds a transaction context identifier by
|
||||
// concatenating a channel ID and a transaction ID.
|
||||
func transactionContextID(chainID, txid string) string {
|
||||
return chainID + txid
|
||||
}
|
||||
|
||||
func (h *Handler) createResponseChannel(channelID, txid string) (<-chan *peer.ChaincodeMessage, error) {
|
||||
h.responseChannelsMutex.Lock()
|
||||
defer h.responseChannelsMutex.Unlock()
|
||||
|
||||
if h.responseChannels == nil {
|
||||
return nil, fmt.Errorf("[%s] cannot create response channel", shorttxid(txid))
|
||||
}
|
||||
|
||||
txCtxID := transactionContextID(channelID, txid)
|
||||
if h.responseChannels[txCtxID] != nil {
|
||||
return nil, fmt.Errorf("[%s] channel exists", shorttxid(txCtxID))
|
||||
}
|
||||
|
||||
responseChan := make(chan *peer.ChaincodeMessage)
|
||||
h.responseChannels[txCtxID] = responseChan
|
||||
return responseChan, nil
|
||||
}
|
||||
|
||||
func (h *Handler) deleteResponseChannel(channelID, txid string) {
|
||||
h.responseChannelsMutex.Lock()
|
||||
defer h.responseChannelsMutex.Unlock()
|
||||
if h.responseChannels != nil {
|
||||
txCtxID := transactionContextID(channelID, txid)
|
||||
delete(h.responseChannels, txCtxID)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleResponse(msg *peer.ChaincodeMessage) error {
|
||||
h.responseChannelsMutex.Lock()
|
||||
defer h.responseChannelsMutex.Unlock()
|
||||
|
||||
if h.responseChannels == nil {
|
||||
return fmt.Errorf("[%s] Cannot send message response channel", shorttxid(msg.Txid))
|
||||
}
|
||||
|
||||
txCtxID := transactionContextID(msg.ChannelId, msg.Txid)
|
||||
responseCh := h.responseChannels[txCtxID]
|
||||
if responseCh == nil {
|
||||
return fmt.Errorf("[%s] responseChannel does not exist", shorttxid(msg.Txid))
|
||||
}
|
||||
responseCh <- msg
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendReceive sends msg to the peer and waits for the response to arrive on
|
||||
// the provided responseChan. On success, the response message will be
|
||||
// returned. An error will be returned msg was not successfully sent to the
|
||||
// peer.
|
||||
func (h *Handler) sendReceive(msg *peer.ChaincodeMessage, responseChan <-chan *peer.ChaincodeMessage) (*peer.ChaincodeMessage, error) {
|
||||
err := h.serialSend(msg)
|
||||
if err != nil {
|
||||
return &peer.ChaincodeMessage{}, err
|
||||
}
|
||||
|
||||
outmsg := <-responseChan
|
||||
return outmsg, nil
|
||||
}
|
||||
|
||||
// NewChaincodeHandler returns a new instance of the shim side handler.
|
||||
func newChaincodeHandler(peerChatStream PeerChaincodeStream, chaincode Chaincode) *Handler {
|
||||
return &Handler{
|
||||
chatStream: peerChatStream,
|
||||
cc: chaincode,
|
||||
responseChannels: map[string]chan *peer.ChaincodeMessage{},
|
||||
state: created,
|
||||
}
|
||||
}
|
||||
|
||||
type stubHandlerFunc func(*peer.ChaincodeMessage) (*peer.ChaincodeMessage, error)
|
||||
|
||||
func (h *Handler) handleStubInteraction(handler stubHandlerFunc, msg *peer.ChaincodeMessage, errc chan<- error) {
|
||||
resp, err := handler(msg)
|
||||
if err != nil {
|
||||
resp = &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_ERROR, Payload: []byte(err.Error()), Txid: msg.Txid, ChannelId: msg.ChannelId}
|
||||
}
|
||||
h.serialSendAsync(resp, errc)
|
||||
}
|
||||
|
||||
// handleInit calls the Init function of the associated chaincode.
|
||||
func (h *Handler) handleInit(msg *peer.ChaincodeMessage) (*peer.ChaincodeMessage, error) {
|
||||
// Get the function and args from Payload
|
||||
input := &peer.ChaincodeInput{}
|
||||
err := proto.Unmarshal(msg.Payload, input)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal input: %s", err)
|
||||
}
|
||||
|
||||
// Create the ChaincodeStub which the chaincode can use to callback
|
||||
stub, err := newChaincodeStub(h, msg.ChannelId, msg.Txid, input, msg.Proposal)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new ChaincodeStub: %s", err)
|
||||
}
|
||||
|
||||
res := h.cc.Init(stub)
|
||||
if res.Status >= ERROR {
|
||||
return &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_ERROR, Payload: []byte(res.Message), Txid: msg.Txid, ChaincodeEvent: stub.chaincodeEvent, ChannelId: msg.ChannelId}, nil
|
||||
}
|
||||
|
||||
resBytes, err := proto.Marshal(res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal response: %s", err)
|
||||
}
|
||||
|
||||
return &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_COMPLETED, Payload: resBytes, Txid: msg.Txid, ChaincodeEvent: stub.chaincodeEvent, ChannelId: stub.ChannelID}, nil
|
||||
}
|
||||
|
||||
// handleTransaction calls Invoke on the associated chaincode.
|
||||
func (h *Handler) handleTransaction(msg *peer.ChaincodeMessage) (*peer.ChaincodeMessage, error) {
|
||||
// Get the function and args from Payload
|
||||
input := &peer.ChaincodeInput{}
|
||||
err := proto.Unmarshal(msg.Payload, input)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal input: %s", err)
|
||||
}
|
||||
|
||||
// Create the ChaincodeStub which the chaincode can use to callback
|
||||
stub, err := newChaincodeStub(h, msg.ChannelId, msg.Txid, input, msg.Proposal)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new ChaincodeStub: %s", err)
|
||||
}
|
||||
|
||||
res := h.cc.Invoke(stub)
|
||||
|
||||
// Endorser will handle error contained in Response.
|
||||
resBytes, err := proto.Marshal(res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal response: %s", err)
|
||||
}
|
||||
|
||||
return &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_COMPLETED, Payload: resBytes, Txid: msg.Txid, ChaincodeEvent: stub.chaincodeEvent, ChannelId: stub.ChannelID}, nil
|
||||
}
|
||||
|
||||
// callPeerWithChaincodeMsg sends a chaincode message to the peer for the given
|
||||
// txid and channel and receives the response.
|
||||
func (h *Handler) callPeerWithChaincodeMsg(msg *peer.ChaincodeMessage, channelID, txid string) (*peer.ChaincodeMessage, error) {
|
||||
// Create the channel on which to communicate the response from the peer
|
||||
respChan, err := h.createResponseChannel(channelID, txid)
|
||||
if err != nil {
|
||||
return &peer.ChaincodeMessage{}, err
|
||||
}
|
||||
defer h.deleteResponseChannel(channelID, txid)
|
||||
|
||||
return h.sendReceive(msg, respChan)
|
||||
}
|
||||
|
||||
// handleGetState communicates with the peer to fetch the requested state information from the ledger.
|
||||
func (h *Handler) handleGetState(collection string, key string, channelID string, txid string) ([]byte, error) {
|
||||
// Construct payload for GET_STATE
|
||||
payloadBytes := marshalOrPanic(&peer.GetState{Collection: collection, Key: key})
|
||||
|
||||
msg := &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_GET_STATE, Payload: payloadBytes, Txid: txid, ChannelId: channelID}
|
||||
responseMsg, err := h.callPeerWithChaincodeMsg(msg, channelID, txid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%s] error sending %s: %s", shorttxid(txid), peer.ChaincodeMessage_GET_STATE, err)
|
||||
}
|
||||
|
||||
if responseMsg.Type == peer.ChaincodeMessage_RESPONSE {
|
||||
// Success response
|
||||
return responseMsg.Payload, nil
|
||||
}
|
||||
if responseMsg.Type == peer.ChaincodeMessage_ERROR {
|
||||
// Error response
|
||||
return nil, fmt.Errorf("%s", responseMsg.Payload[:])
|
||||
}
|
||||
|
||||
// Incorrect chaincode message received
|
||||
return nil, fmt.Errorf("[%s] incorrect chaincode message %s received. Expecting %s or %s", shorttxid(responseMsg.Txid), responseMsg.Type, peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR)
|
||||
}
|
||||
|
||||
func (h *Handler) handleGetPrivateDataHash(collection string, key string, channelID string, txid string) ([]byte, error) {
|
||||
// Construct payload for GET_PRIVATE_DATA_HASH
|
||||
payloadBytes := marshalOrPanic(&peer.GetState{Collection: collection, Key: key})
|
||||
|
||||
msg := &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_GET_PRIVATE_DATA_HASH, Payload: payloadBytes, Txid: txid, ChannelId: channelID}
|
||||
responseMsg, err := h.callPeerWithChaincodeMsg(msg, channelID, txid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%s] error sending %s: %s", shorttxid(txid), peer.ChaincodeMessage_GET_PRIVATE_DATA_HASH, err)
|
||||
}
|
||||
|
||||
if responseMsg.Type == peer.ChaincodeMessage_RESPONSE {
|
||||
// Success response
|
||||
return responseMsg.Payload, nil
|
||||
}
|
||||
if responseMsg.Type == peer.ChaincodeMessage_ERROR {
|
||||
// Error response
|
||||
return nil, fmt.Errorf("%s", responseMsg.Payload[:])
|
||||
}
|
||||
|
||||
// Incorrect chaincode message received
|
||||
return nil, fmt.Errorf("[%s] incorrect chaincode message %s received. Expecting %s or %s", shorttxid(responseMsg.Txid), responseMsg.Type, peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR)
|
||||
}
|
||||
|
||||
func (h *Handler) handleGetStateMetadata(collection string, key string, channelID string, txID string) (map[string][]byte, error) {
|
||||
// Construct payload for GET_STATE_METADATA
|
||||
payloadBytes := marshalOrPanic(&peer.GetStateMetadata{Collection: collection, Key: key})
|
||||
|
||||
msg := &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_GET_STATE_METADATA, Payload: payloadBytes, Txid: txID, ChannelId: channelID}
|
||||
responseMsg, err := h.callPeerWithChaincodeMsg(msg, channelID, txID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%s] error sending %s: %s", shorttxid(txID), peer.ChaincodeMessage_GET_STATE_METADATA, err)
|
||||
}
|
||||
|
||||
if responseMsg.Type == peer.ChaincodeMessage_RESPONSE {
|
||||
// Success response
|
||||
var mdResult peer.StateMetadataResult
|
||||
err := proto.Unmarshal(responseMsg.Payload, &mdResult)
|
||||
if err != nil {
|
||||
return nil, errors.New("could not unmarshal metadata response")
|
||||
}
|
||||
metadata := make(map[string][]byte)
|
||||
for _, md := range mdResult.Entries {
|
||||
metadata[md.Metakey] = md.Value
|
||||
}
|
||||
|
||||
return metadata, nil
|
||||
}
|
||||
if responseMsg.Type == peer.ChaincodeMessage_ERROR {
|
||||
// Error response
|
||||
return nil, fmt.Errorf("%s", responseMsg.Payload[:])
|
||||
}
|
||||
|
||||
// Incorrect chaincode message received
|
||||
return nil, fmt.Errorf("[%s] incorrect chaincode message %s received. Expecting %s or %s", shorttxid(responseMsg.Txid), responseMsg.Type, peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR)
|
||||
}
|
||||
|
||||
// handlePutState communicates with the peer to put state information into the ledger.
|
||||
func (h *Handler) handlePutState(collection string, key string, value []byte, channelID string, txid string) error {
|
||||
// Construct payload for PUT_STATE
|
||||
payloadBytes := marshalOrPanic(&peer.PutState{Collection: collection, Key: key, Value: value})
|
||||
|
||||
msg := &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_PUT_STATE, Payload: payloadBytes, Txid: txid, ChannelId: channelID}
|
||||
|
||||
// Execute the request and get response
|
||||
responseMsg, err := h.callPeerWithChaincodeMsg(msg, channelID, txid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] error sending %s: %s", msg.Txid, peer.ChaincodeMessage_PUT_STATE, err)
|
||||
}
|
||||
|
||||
if responseMsg.Type == peer.ChaincodeMessage_RESPONSE {
|
||||
// Success response
|
||||
return nil
|
||||
}
|
||||
|
||||
if responseMsg.Type == peer.ChaincodeMessage_ERROR {
|
||||
// Error response
|
||||
return fmt.Errorf("%s", responseMsg.Payload[:])
|
||||
}
|
||||
|
||||
// Incorrect chaincode message received
|
||||
return fmt.Errorf("[%s] incorrect chaincode message %s received. Expecting %s or %s", shorttxid(responseMsg.Txid), responseMsg.Type, peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR)
|
||||
}
|
||||
|
||||
func (h *Handler) handlePutStateMetadataEntry(collection string, key string, metakey string, metadata []byte, channelID string, txID string) error {
|
||||
// Construct payload for PUT_STATE_METADATA
|
||||
md := &peer.StateMetadata{Metakey: metakey, Value: metadata}
|
||||
payloadBytes := marshalOrPanic(&peer.PutStateMetadata{Collection: collection, Key: key, Metadata: md})
|
||||
|
||||
msg := &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_PUT_STATE_METADATA, Payload: payloadBytes, Txid: txID, ChannelId: channelID}
|
||||
// Execute the request and get response
|
||||
responseMsg, err := h.callPeerWithChaincodeMsg(msg, channelID, txID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] error sending %s: %s", msg.Txid, peer.ChaincodeMessage_PUT_STATE_METADATA, err)
|
||||
}
|
||||
|
||||
if responseMsg.Type == peer.ChaincodeMessage_RESPONSE {
|
||||
// Success response
|
||||
return nil
|
||||
}
|
||||
|
||||
if responseMsg.Type == peer.ChaincodeMessage_ERROR {
|
||||
// Error response
|
||||
return fmt.Errorf("%s", responseMsg.Payload[:])
|
||||
}
|
||||
|
||||
// Incorrect chaincode message received
|
||||
return fmt.Errorf("[%s]incorrect chaincode message %s received. Expecting %s or %s", shorttxid(responseMsg.Txid), responseMsg.Type, peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR)
|
||||
}
|
||||
|
||||
// handleDelState communicates with the peer to delete a key from the state in the ledger.
|
||||
func (h *Handler) handleDelState(collection string, key string, channelID string, txid string) error {
|
||||
payloadBytes := marshalOrPanic(&peer.DelState{Collection: collection, Key: key})
|
||||
msg := &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_DEL_STATE, Payload: payloadBytes, Txid: txid, ChannelId: channelID}
|
||||
// Execute the request and get response
|
||||
responseMsg, err := h.callPeerWithChaincodeMsg(msg, channelID, txid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] error sending %s", shorttxid(msg.Txid), peer.ChaincodeMessage_DEL_STATE)
|
||||
}
|
||||
|
||||
if responseMsg.Type == peer.ChaincodeMessage_RESPONSE {
|
||||
// Success response
|
||||
return nil
|
||||
}
|
||||
if responseMsg.Type == peer.ChaincodeMessage_ERROR {
|
||||
// Error response
|
||||
return fmt.Errorf("%s", responseMsg.Payload[:])
|
||||
}
|
||||
|
||||
// Incorrect chaincode message received
|
||||
return fmt.Errorf("[%s] incorrect chaincode message %s received. Expecting %s or %s", shorttxid(responseMsg.Txid), responseMsg.Type, peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR)
|
||||
}
|
||||
|
||||
// handlePurgeState communicates with the peer to purge a state from private data
|
||||
func (h *Handler) handlePurgeState(collection string, key string, channelID string, txid string) error {
|
||||
payloadBytes := marshalOrPanic(&peer.DelState{Collection: collection, Key: key})
|
||||
msg := &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_PURGE_PRIVATE_DATA, Payload: payloadBytes, Txid: txid, ChannelId: channelID}
|
||||
// Execute the request and get response
|
||||
responseMsg, err := h.callPeerWithChaincodeMsg(msg, channelID, txid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] error sending %s", shorttxid(msg.Txid), peer.ChaincodeMessage_DEL_STATE)
|
||||
}
|
||||
|
||||
if responseMsg.Type == peer.ChaincodeMessage_RESPONSE {
|
||||
// Success response
|
||||
return nil
|
||||
}
|
||||
if responseMsg.Type == peer.ChaincodeMessage_ERROR {
|
||||
// Error response
|
||||
return fmt.Errorf("%s", responseMsg.Payload[:])
|
||||
}
|
||||
|
||||
// Incorrect chaincode message received
|
||||
return fmt.Errorf("[%s] incorrect chaincode message %s received. Expecting %s or %s", shorttxid(responseMsg.Txid), responseMsg.Type, peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR)
|
||||
}
|
||||
|
||||
func (h *Handler) handleGetStateByRange(collection, startKey, endKey string, metadata []byte,
|
||||
channelID string, txid string) (*peer.QueryResponse, error) {
|
||||
// Send GET_STATE_BY_RANGE message to peer chaincode support
|
||||
payloadBytes := marshalOrPanic(&peer.GetStateByRange{Collection: collection, StartKey: startKey, EndKey: endKey, Metadata: metadata})
|
||||
msg := &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_GET_STATE_BY_RANGE, Payload: payloadBytes, Txid: txid, ChannelId: channelID}
|
||||
responseMsg, err := h.callPeerWithChaincodeMsg(msg, channelID, txid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%s] error sending %s", shorttxid(msg.Txid), peer.ChaincodeMessage_GET_STATE_BY_RANGE)
|
||||
}
|
||||
|
||||
if responseMsg.Type == peer.ChaincodeMessage_RESPONSE {
|
||||
// Success response
|
||||
rangeQueryResponse := &peer.QueryResponse{}
|
||||
err = proto.Unmarshal(responseMsg.Payload, rangeQueryResponse)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%s] GetStateByRangeResponse unmarshall error", shorttxid(responseMsg.Txid))
|
||||
}
|
||||
|
||||
return rangeQueryResponse, nil
|
||||
}
|
||||
if responseMsg.Type == peer.ChaincodeMessage_ERROR {
|
||||
// Error response
|
||||
return nil, fmt.Errorf("%s", responseMsg.Payload[:])
|
||||
}
|
||||
|
||||
// Incorrect chaincode message received
|
||||
return nil, fmt.Errorf("incorrect chaincode message %s received. Expecting %s or %s", responseMsg.Type, peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR)
|
||||
}
|
||||
|
||||
func (h *Handler) handleQueryStateNext(id, channelID, txid string) (*peer.QueryResponse, error) {
|
||||
// Create the channel on which to communicate the response from validating peer
|
||||
respChan, err := h.createResponseChannel(channelID, txid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer h.deleteResponseChannel(channelID, txid)
|
||||
|
||||
// Send QUERY_STATE_NEXT message to peer chaincode support
|
||||
payloadBytes := marshalOrPanic(&peer.QueryStateNext{Id: id})
|
||||
|
||||
msg := &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_QUERY_STATE_NEXT, Payload: payloadBytes, Txid: txid, ChannelId: channelID}
|
||||
|
||||
var responseMsg *peer.ChaincodeMessage
|
||||
|
||||
if responseMsg, err = h.sendReceive(msg, respChan); err != nil {
|
||||
return nil, fmt.Errorf("[%s] error sending %s", shorttxid(msg.Txid), peer.ChaincodeMessage_QUERY_STATE_NEXT)
|
||||
}
|
||||
|
||||
if responseMsg.Type == peer.ChaincodeMessage_RESPONSE {
|
||||
// Success response
|
||||
queryResponse := &peer.QueryResponse{}
|
||||
if err = proto.Unmarshal(responseMsg.Payload, queryResponse); err != nil {
|
||||
return nil, fmt.Errorf("[%s] unmarshal error", shorttxid(responseMsg.Txid))
|
||||
}
|
||||
|
||||
return queryResponse, nil
|
||||
}
|
||||
if responseMsg.Type == peer.ChaincodeMessage_ERROR {
|
||||
// Error response
|
||||
return nil, fmt.Errorf("%s", responseMsg.Payload[:])
|
||||
}
|
||||
|
||||
// Incorrect chaincode message received
|
||||
return nil, fmt.Errorf("incorrect chaincode message %s received. Expecting %s or %s", responseMsg.Type, peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR)
|
||||
}
|
||||
|
||||
func (h *Handler) handleQueryStateClose(id, channelID, txid string) (*peer.QueryResponse, error) {
|
||||
// Create the channel on which to communicate the response from validating peer
|
||||
respChan, err := h.createResponseChannel(channelID, txid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer h.deleteResponseChannel(channelID, txid)
|
||||
|
||||
// Send QUERY_STATE_CLOSE message to peer chaincode support
|
||||
payloadBytes := marshalOrPanic(&peer.QueryStateClose{Id: id})
|
||||
|
||||
msg := &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_QUERY_STATE_CLOSE, Payload: payloadBytes, Txid: txid, ChannelId: channelID}
|
||||
|
||||
var responseMsg *peer.ChaincodeMessage
|
||||
|
||||
if responseMsg, err = h.sendReceive(msg, respChan); err != nil {
|
||||
return nil, fmt.Errorf("[%s] error sending %s", shorttxid(msg.Txid), peer.ChaincodeMessage_QUERY_STATE_CLOSE)
|
||||
}
|
||||
|
||||
if responseMsg.Type == peer.ChaincodeMessage_RESPONSE {
|
||||
// Success response
|
||||
queryResponse := &peer.QueryResponse{}
|
||||
if err = proto.Unmarshal(responseMsg.Payload, queryResponse); err != nil {
|
||||
return nil, fmt.Errorf("[%s] unmarshal error", shorttxid(responseMsg.Txid))
|
||||
}
|
||||
|
||||
return queryResponse, nil
|
||||
}
|
||||
if responseMsg.Type == peer.ChaincodeMessage_ERROR {
|
||||
// Error response
|
||||
return nil, fmt.Errorf("%s", responseMsg.Payload[:])
|
||||
}
|
||||
|
||||
// Incorrect chaincode message received
|
||||
return nil, fmt.Errorf("incorrect chaincode message %s received. Expecting %s or %s", responseMsg.Type, peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR)
|
||||
}
|
||||
|
||||
func (h *Handler) handleGetQueryResult(collection string, query string, metadata []byte,
|
||||
channelID string, txid string) (*peer.QueryResponse, error) {
|
||||
// Send GET_QUERY_RESULT message to peer chaincode support
|
||||
payloadBytes := marshalOrPanic(&peer.GetQueryResult{Collection: collection, Query: query, Metadata: metadata})
|
||||
msg := &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_GET_QUERY_RESULT, Payload: payloadBytes, Txid: txid, ChannelId: channelID}
|
||||
responseMsg, err := h.callPeerWithChaincodeMsg(msg, channelID, txid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[%s] error sending %s", shorttxid(msg.Txid), peer.ChaincodeMessage_GET_QUERY_RESULT)
|
||||
}
|
||||
|
||||
if responseMsg.Type == peer.ChaincodeMessage_RESPONSE {
|
||||
// Success response
|
||||
executeQueryResponse := &peer.QueryResponse{}
|
||||
if err = proto.Unmarshal(responseMsg.Payload, executeQueryResponse); err != nil {
|
||||
return nil, fmt.Errorf("[%s] unmarshal error", shorttxid(responseMsg.Txid))
|
||||
}
|
||||
|
||||
return executeQueryResponse, nil
|
||||
}
|
||||
if responseMsg.Type == peer.ChaincodeMessage_ERROR {
|
||||
// Error response
|
||||
return nil, fmt.Errorf("%s", responseMsg.Payload[:])
|
||||
}
|
||||
|
||||
// Incorrect chaincode message received
|
||||
return nil, fmt.Errorf("incorrect chaincode message %s received. Expecting %s or %s", responseMsg.Type, peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR)
|
||||
}
|
||||
|
||||
func (h *Handler) handleGetHistoryForKey(key string, channelID string, txid string) (*peer.QueryResponse, error) {
|
||||
// Create the channel on which to communicate the response from validating peer
|
||||
respChan, err := h.createResponseChannel(channelID, txid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer h.deleteResponseChannel(channelID, txid)
|
||||
|
||||
// Send GET_HISTORY_FOR_KEY message to peer chaincode support
|
||||
payloadBytes := marshalOrPanic(&peer.GetHistoryForKey{Key: key})
|
||||
|
||||
msg := &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_GET_HISTORY_FOR_KEY, Payload: payloadBytes, Txid: txid, ChannelId: channelID}
|
||||
var responseMsg *peer.ChaincodeMessage
|
||||
|
||||
if responseMsg, err = h.sendReceive(msg, respChan); err != nil {
|
||||
return nil, fmt.Errorf("[%s] error sending %s", shorttxid(msg.Txid), peer.ChaincodeMessage_GET_HISTORY_FOR_KEY)
|
||||
}
|
||||
|
||||
if responseMsg.Type == peer.ChaincodeMessage_RESPONSE {
|
||||
// Success response
|
||||
getHistoryForKeyResponse := &peer.QueryResponse{}
|
||||
if err = proto.Unmarshal(responseMsg.Payload, getHistoryForKeyResponse); err != nil {
|
||||
return nil, fmt.Errorf("[%s] unmarshal error", shorttxid(responseMsg.Txid))
|
||||
}
|
||||
|
||||
return getHistoryForKeyResponse, nil
|
||||
}
|
||||
if responseMsg.Type == peer.ChaincodeMessage_ERROR {
|
||||
// Error response
|
||||
return nil, fmt.Errorf("%s", responseMsg.Payload[:])
|
||||
}
|
||||
|
||||
// Incorrect chaincode message received
|
||||
return nil, fmt.Errorf("incorrect chaincode message %s received. Expecting %s or %s", responseMsg.Type, peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR)
|
||||
}
|
||||
|
||||
func (h *Handler) createResponse(status int32, payload []byte) *peer.Response {
|
||||
return &peer.Response{Status: status, Payload: payload}
|
||||
}
|
||||
|
||||
// handleInvokeChaincode communicates with the peer to invoke another chaincode.
|
||||
func (h *Handler) handleInvokeChaincode(chaincodeName string, args [][]byte, channelID string, txid string) *peer.Response {
|
||||
payloadBytes := marshalOrPanic(&peer.ChaincodeSpec{ChaincodeId: &peer.ChaincodeID{Name: chaincodeName}, Input: &peer.ChaincodeInput{Args: args}})
|
||||
|
||||
// Create the channel on which to communicate the response from validating peer
|
||||
respChan, err := h.createResponseChannel(channelID, txid)
|
||||
if err != nil {
|
||||
return h.createResponse(ERROR, []byte(err.Error()))
|
||||
}
|
||||
defer h.deleteResponseChannel(channelID, txid)
|
||||
|
||||
// Send INVOKE_CHAINCODE message to peer chaincode support
|
||||
msg := &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_INVOKE_CHAINCODE, Payload: payloadBytes, Txid: txid, ChannelId: channelID}
|
||||
|
||||
var responseMsg *peer.ChaincodeMessage
|
||||
|
||||
if responseMsg, err = h.sendReceive(msg, respChan); err != nil {
|
||||
errStr := fmt.Sprintf("[%s] error sending %s", shorttxid(msg.Txid), peer.ChaincodeMessage_INVOKE_CHAINCODE)
|
||||
return h.createResponse(ERROR, []byte(errStr))
|
||||
}
|
||||
|
||||
if responseMsg.Type == peer.ChaincodeMessage_RESPONSE {
|
||||
// Success response
|
||||
respMsg := &peer.ChaincodeMessage{}
|
||||
if err := proto.Unmarshal(responseMsg.Payload, respMsg); err != nil {
|
||||
return h.createResponse(ERROR, []byte(err.Error()))
|
||||
}
|
||||
if respMsg.Type == peer.ChaincodeMessage_COMPLETED {
|
||||
// Success response
|
||||
res := &peer.Response{}
|
||||
if err = proto.Unmarshal(respMsg.Payload, res); err != nil {
|
||||
return h.createResponse(ERROR, []byte(err.Error()))
|
||||
}
|
||||
return res
|
||||
}
|
||||
return h.createResponse(ERROR, responseMsg.Payload)
|
||||
}
|
||||
if responseMsg.Type == peer.ChaincodeMessage_ERROR {
|
||||
// Error response
|
||||
return h.createResponse(ERROR, responseMsg.Payload)
|
||||
}
|
||||
|
||||
// Incorrect chaincode message received
|
||||
return h.createResponse(ERROR, []byte(fmt.Sprintf("[%s] Incorrect chaincode message %s received. Expecting %s or %s", shorttxid(responseMsg.Txid), responseMsg.Type, peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR)))
|
||||
}
|
||||
|
||||
// handleReady handles messages received from the peer when the handler is in the "ready" state.
|
||||
func (h *Handler) handleReady(msg *peer.ChaincodeMessage, errc chan error) error {
|
||||
switch msg.Type {
|
||||
case peer.ChaincodeMessage_RESPONSE, peer.ChaincodeMessage_ERROR:
|
||||
if err := h.handleResponse(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
case peer.ChaincodeMessage_INIT:
|
||||
go h.handleStubInteraction(h.handleInit, msg, errc)
|
||||
return nil
|
||||
|
||||
case peer.ChaincodeMessage_TRANSACTION:
|
||||
go h.handleStubInteraction(h.handleTransaction, msg, errc)
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("[%s] Chaincode h cannot handle message (%s) while in state: %s", msg.Txid, msg.Type, h.state)
|
||||
}
|
||||
}
|
||||
|
||||
// handleEstablished handles messages received from the peer when the handler is in the "established" state.
|
||||
func (h *Handler) handleEstablished(msg *peer.ChaincodeMessage) error {
|
||||
if msg.Type != peer.ChaincodeMessage_READY {
|
||||
return fmt.Errorf("[%s] Chaincode h cannot handle message (%s) while in state: %s", msg.Txid, msg.Type, h.state)
|
||||
}
|
||||
|
||||
h.state = ready
|
||||
return nil
|
||||
}
|
||||
|
||||
// hanndleCreated handles messages received from the peer when the handler is in the "created" state.
|
||||
func (h *Handler) handleCreated(msg *peer.ChaincodeMessage) error {
|
||||
if msg.Type != peer.ChaincodeMessage_REGISTERED {
|
||||
return fmt.Errorf("[%s] Chaincode h cannot handle message (%s) while in state: %s", msg.Txid, msg.Type, h.state)
|
||||
}
|
||||
|
||||
h.state = established
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleMessage message handles loop for shim side of chaincode/peer stream.
|
||||
func (h *Handler) handleMessage(msg *peer.ChaincodeMessage, errc chan error) error {
|
||||
if msg.Type == peer.ChaincodeMessage_KEEPALIVE {
|
||||
h.serialSendAsync(msg, errc)
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
|
||||
switch h.state {
|
||||
case ready:
|
||||
err = h.handleReady(msg, errc)
|
||||
case established:
|
||||
err = h.handleEstablished(msg)
|
||||
case created:
|
||||
err = h.handleCreated(msg)
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid handler state: %s", h.state))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
payload := []byte(err.Error())
|
||||
errorMsg := &peer.ChaincodeMessage{Type: peer.ChaincodeMessage_ERROR, Payload: payload, Txid: msg.Txid}
|
||||
h.serialSend(errorMsg) //nolint:errcheck
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// marshalOrPanic attempts to marshal the provided protobbuf message but will panic
|
||||
// when marshaling fails instead of returning an error.
|
||||
func marshalOrPanic(msg proto.Message) []byte {
|
||||
bytes, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to marshal message: %s", err))
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
@ -0,0 +1,405 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package shim
|
||||
|
||||
import (
|
||||
"github.com/hyperledger/fabric-protos-go-apiv2/ledger/queryresult"
|
||||
"github.com/hyperledger/fabric-protos-go-apiv2/peer"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// Chaincode interface must be implemented by all chaincodes. The fabric runs
|
||||
// the transactions by calling these functions as specified.
|
||||
type Chaincode interface {
|
||||
// Init is called during Instantiate transaction after the chaincode container
|
||||
// has been established for the first time, allowing the chaincode to
|
||||
// initialize its internal data
|
||||
Init(stub ChaincodeStubInterface) *peer.Response
|
||||
|
||||
// Invoke is called to update or query the ledger in a proposal transaction.
|
||||
// Updated state variables are not committed to the ledger until the
|
||||
// transaction is committed.
|
||||
Invoke(stub ChaincodeStubInterface) *peer.Response
|
||||
}
|
||||
|
||||
// ChaincodeStubInterface is used by deployable chaincode apps to access and
|
||||
// modify their ledgers
|
||||
type ChaincodeStubInterface interface {
|
||||
// GetArgs returns the arguments intended for the chaincode Init and Invoke
|
||||
// as an array of byte arrays.
|
||||
GetArgs() [][]byte
|
||||
|
||||
// GetStringArgs returns the arguments intended for the chaincode Init and
|
||||
// Invoke as a string array. Only use GetStringArgs if the client passes
|
||||
// arguments intended to be used as strings.
|
||||
GetStringArgs() []string
|
||||
|
||||
// GetFunctionAndParameters returns the first argument as the function
|
||||
// name and the rest of the arguments as parameters in a string array.
|
||||
// Only use GetFunctionAndParameters if the client passes arguments intended
|
||||
// to be used as strings.
|
||||
GetFunctionAndParameters() (string, []string)
|
||||
|
||||
// GetArgsSlice returns the arguments intended for the chaincode Init and
|
||||
// Invoke as a byte array
|
||||
GetArgsSlice() ([]byte, error)
|
||||
|
||||
// GetTxID returns the tx_id of the transaction proposal, which is unique per
|
||||
// transaction and per client. See
|
||||
// https://godoc.org/github.com/hyperledger/fabric-protos-go-apiv2/common#ChannelHeader
|
||||
// for further details.
|
||||
GetTxID() string
|
||||
|
||||
// GetChannelID returns the channel the proposal is sent to for chaincode to process.
|
||||
// This would be the channel_id of the transaction proposal (see
|
||||
// https://godoc.org/github.com/hyperledger/fabric-protos-go-apiv2/common#ChannelHeader )
|
||||
// except where the chaincode is calling another on a different channel.
|
||||
GetChannelID() string
|
||||
|
||||
// InvokeChaincode locally calls the specified chaincode `Invoke` using the
|
||||
// same transaction context; that is, chaincode calling chaincode doesn't
|
||||
// create a new transaction message.
|
||||
// If the called chaincode is on the same channel, it simply adds the called
|
||||
// chaincode read set and write set to the calling transaction.
|
||||
// If the called chaincode is on a different channel,
|
||||
// only the Response is returned to the calling chaincode; any PutState calls
|
||||
// from the called chaincode will not have any effect on the ledger; that is,
|
||||
// the called chaincode on a different channel will not have its read set
|
||||
// and write set applied to the transaction. Only the calling chaincode's
|
||||
// read set and write set will be applied to the transaction. Effectively
|
||||
// the called chaincode on a different channel is a `Query`, which does not
|
||||
// participate in state validation checks in subsequent commit phase.
|
||||
// If `channel` is empty, the caller's channel is assumed.
|
||||
InvokeChaincode(chaincodeName string, args [][]byte, channel string) *peer.Response
|
||||
|
||||
// GetState returns the value of the specified `key` from the
|
||||
// ledger. Note that GetState doesn't read data from the writeset, which
|
||||
// has not been committed to the ledger. In other words, GetState doesn't
|
||||
// consider data modified by PutState that has not been committed.
|
||||
// If the key does not exist in the state database, (nil, nil) is returned.
|
||||
GetState(key string) ([]byte, error)
|
||||
|
||||
// PutState puts the specified `key` and `value` into the transaction's
|
||||
// writeset as a data-write proposal. PutState doesn't effect the ledger
|
||||
// until the transaction is validated and successfully committed.
|
||||
// Simple keys must not be an empty string and must not start with a
|
||||
// null character (0x00) in order to avoid range query collisions with
|
||||
// composite keys, which internally get prefixed with 0x00 as composite
|
||||
// key namespace. In addition, if using CouchDB, keys can only contain
|
||||
// valid UTF-8 strings and cannot begin with an underscore ("_").
|
||||
PutState(key string, value []byte) error
|
||||
|
||||
// DelState records the specified `key` to be deleted in the writeset of
|
||||
// the transaction proposal. The `key` and its value will be deleted from
|
||||
// the ledger when the transaction is validated and successfully committed.
|
||||
DelState(key string) error
|
||||
|
||||
// SetStateValidationParameter sets the key-level endorsement policy for `key`.
|
||||
SetStateValidationParameter(key string, ep []byte) error
|
||||
|
||||
// GetStateValidationParameter retrieves the key-level endorsement policy
|
||||
// for `key`. Note that this will introduce a read dependency on `key` in
|
||||
// the transaction's readset.
|
||||
GetStateValidationParameter(key string) ([]byte, error)
|
||||
|
||||
// GetStateByRange returns a range iterator over a set of keys in the
|
||||
// ledger. The iterator can be used to iterate over all keys
|
||||
// between the startKey (inclusive) and endKey (exclusive).
|
||||
// However, if the number of keys between startKey and endKey is greater than the
|
||||
// totalQueryLimit (defined in core.yaml), this iterator cannot be used
|
||||
// to fetch all keys (results will be capped by the totalQueryLimit).
|
||||
// The keys are returned by the iterator in lexical order. Note
|
||||
// that startKey and endKey can be empty string, which implies unbounded range
|
||||
// query on start or end.
|
||||
// Call Close() on the returned StateQueryIteratorInterface object when done.
|
||||
// The query is re-executed during validation phase to ensure result set
|
||||
// has not changed since transaction endorsement (phantom reads detected).
|
||||
GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error)
|
||||
|
||||
// GetStateByRangeWithPagination returns a range iterator over a set of keys in the
|
||||
// ledger. The iterator can be used to fetch keys between the startKey (inclusive)
|
||||
// and endKey (exclusive).
|
||||
// When an empty string is passed as a value to the bookmark argument, the returned
|
||||
// iterator can be used to fetch the first `pageSize` keys between the startKey
|
||||
// (inclusive) and endKey (exclusive).
|
||||
// When the bookmark is a non-emptry string, the iterator can be used to fetch
|
||||
// the first `pageSize` keys between the bookmark (inclusive) and endKey (exclusive).
|
||||
// Note that only the bookmark present in a prior page of query results (ResponseMetadata)
|
||||
// can be used as a value to the bookmark argument. Otherwise, an empty string must
|
||||
// be passed as bookmark.
|
||||
// The keys are returned by the iterator in lexical order. Note
|
||||
// that startKey and endKey can be empty string, which implies unbounded range
|
||||
// query on start or end.
|
||||
// Call Close() on the returned StateQueryIteratorInterface object when done.
|
||||
// This call is only supported in a read only transaction.
|
||||
GetStateByRangeWithPagination(startKey, endKey string, pageSize int32,
|
||||
bookmark string) (StateQueryIteratorInterface, *peer.QueryResponseMetadata, error)
|
||||
|
||||
// GetStateByPartialCompositeKey queries the state in the ledger based on
|
||||
// a given partial composite key. This function returns an iterator
|
||||
// which can be used to iterate over all composite keys whose prefix matches
|
||||
// the given partial composite key. However, if the number of matching composite
|
||||
// keys is greater than the totalQueryLimit (defined in core.yaml), this iterator
|
||||
// cannot be used to fetch all matching keys (results will be limited by the totalQueryLimit).
|
||||
// The `objectType` and attributes are expected to have only valid utf8 strings and
|
||||
// should not contain U+0000 (nil byte) and U+10FFFF (biggest and unallocated code point).
|
||||
// See related functions SplitCompositeKey and CreateCompositeKey.
|
||||
// Call Close() on the returned StateQueryIteratorInterface object when done.
|
||||
// The query is re-executed during validation phase to ensure result set
|
||||
// has not changed since transaction endorsement (phantom reads detected). This function should be used only for
|
||||
// a partial composite key. For a full composite key, an iter with empty response
|
||||
// would be returned.
|
||||
GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error)
|
||||
|
||||
// GetStateByPartialCompositeKeyWithPagination queries the state in the ledger based on
|
||||
// a given partial composite key. This function returns an iterator
|
||||
// which can be used to iterate over the composite keys whose
|
||||
// prefix matches the given partial composite key.
|
||||
// When an empty string is passed as a value to the bookmark argument, the returned
|
||||
// iterator can be used to fetch the first `pageSize` composite keys whose prefix
|
||||
// matches the given partial composite key.
|
||||
// When the bookmark is a non-emptry string, the iterator can be used to fetch
|
||||
// the first `pageSize` keys between the bookmark (inclusive) and the last matching
|
||||
// composite key.
|
||||
// Note that only the bookmark present in a prior page of query result (ResponseMetadata)
|
||||
// can be used as a value to the bookmark argument. Otherwise, an empty string must
|
||||
// be passed as bookmark.
|
||||
// The `objectType` and attributes are expected to have only valid utf8 strings
|
||||
// and should not contain U+0000 (nil byte) and U+10FFFF (biggest and unallocated
|
||||
// code point). See related functions SplitCompositeKey and CreateCompositeKey.
|
||||
// Call Close() on the returned StateQueryIteratorInterface object when done.
|
||||
// This call is only supported in a read only transaction. This function should be used only for
|
||||
// a partial composite key. For a full composite key, an iter with empty response
|
||||
// would be returned.
|
||||
GetStateByPartialCompositeKeyWithPagination(objectType string, keys []string,
|
||||
pageSize int32, bookmark string) (StateQueryIteratorInterface, *peer.QueryResponseMetadata, error)
|
||||
|
||||
// CreateCompositeKey combines the given `attributes` to form a composite
|
||||
// key. The objectType and attributes are expected to have only valid utf8
|
||||
// strings and should not contain U+0000 (nil byte) and U+10FFFF
|
||||
// (biggest and unallocated code point).
|
||||
// The resulting composite key can be used as the key in PutState().
|
||||
CreateCompositeKey(objectType string, attributes []string) (string, error)
|
||||
|
||||
// SplitCompositeKey splits the specified key into attributes on which the
|
||||
// composite key was formed. Composite keys found during range queries
|
||||
// or partial composite key queries can therefore be split into their
|
||||
// composite parts.
|
||||
SplitCompositeKey(compositeKey string) (string, []string, error)
|
||||
|
||||
// GetQueryResult performs a "rich" query against a state database. It is
|
||||
// only supported for state databases that support rich query,
|
||||
// e.g.CouchDB. The query string is in the native syntax
|
||||
// of the underlying state database. An iterator is returned
|
||||
// which can be used to iterate over all keys in the query result set.
|
||||
// However, if the number of keys in the query result set is greater than the
|
||||
// totalQueryLimit (defined in core.yaml), this iterator cannot be used
|
||||
// to fetch all keys in the query result set (results will be limited by
|
||||
// the totalQueryLimit).
|
||||
// The query is NOT re-executed during validation phase, phantom reads are
|
||||
// not detected. That is, other committed transactions may have added,
|
||||
// updated, or removed keys that impact the result set, and this would not
|
||||
// be detected at validation/commit time. Applications susceptible to this
|
||||
// should therefore not use GetQueryResult as part of transactions that update
|
||||
// ledger, and should limit use to read-only chaincode operations.
|
||||
GetQueryResult(query string) (StateQueryIteratorInterface, error)
|
||||
|
||||
// GetQueryResultWithPagination performs a "rich" query against a state database.
|
||||
// It is only supported for state databases that support rich query,
|
||||
// e.g., CouchDB. The query string is in the native syntax
|
||||
// of the underlying state database. An iterator is returned
|
||||
// which can be used to iterate over keys in the query result set.
|
||||
// When an empty string is passed as a value to the bookmark argument, the returned
|
||||
// iterator can be used to fetch the first `pageSize` of query results.
|
||||
// When the bookmark is a non-emptry string, the iterator can be used to fetch
|
||||
// the first `pageSize` keys between the bookmark and the last key in the query result.
|
||||
// Note that only the bookmark present in a prior page of query results (ResponseMetadata)
|
||||
// can be used as a value to the bookmark argument. Otherwise, an empty string
|
||||
// must be passed as bookmark.
|
||||
// This call is only supported in a read only transaction.
|
||||
GetQueryResultWithPagination(query string, pageSize int32,
|
||||
bookmark string) (StateQueryIteratorInterface, *peer.QueryResponseMetadata, error)
|
||||
|
||||
// GetHistoryForKey returns a history of key values across time.
|
||||
// For each historic key update, the historic value and associated
|
||||
// transaction id and timestamp are returned. The timestamp is the
|
||||
// timestamp provided by the client in the proposal header.
|
||||
// GetHistoryForKey requires peer configuration
|
||||
// core.ledger.history.enableHistoryDatabase to be true.
|
||||
// The query is NOT re-executed during validation phase, phantom reads are
|
||||
// not detected. That is, other committed transactions may have updated
|
||||
// the key concurrently, impacting the result set, and this would not be
|
||||
// detected at validation/commit time. Applications susceptible to this
|
||||
// should therefore not use GetHistoryForKey as part of transactions that
|
||||
// update ledger, and should limit use to read-only chaincode operations.
|
||||
// Starting in Fabric v2.0, the GetHistoryForKey chaincode API
|
||||
// will return results from newest to oldest in terms of ordered transaction
|
||||
// height (block height and transaction height within block).
|
||||
// This will allow applications to efficiently iterate through the top results
|
||||
// to understand recent changes to a key.
|
||||
GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error)
|
||||
|
||||
// GetPrivateData returns the value of the specified `key` from the specified
|
||||
// `collection`. Note that GetPrivateData doesn't read data from the
|
||||
// private writeset, which has not been committed to the `collection`. In
|
||||
// other words, GetPrivateData doesn't consider data modified by PutPrivateData
|
||||
// that has not been committed.
|
||||
GetPrivateData(collection, key string) ([]byte, error)
|
||||
|
||||
// GetPrivateDataHash returns the hash of the value of the specified `key` from the specified
|
||||
// `collection`
|
||||
GetPrivateDataHash(collection, key string) ([]byte, error)
|
||||
|
||||
// PutPrivateData puts the specified `key` and `value` into the transaction's
|
||||
// private writeset. Note that only hash of the private writeset goes into the
|
||||
// transaction proposal response (which is sent to the client who issued the
|
||||
// transaction) and the actual private writeset gets temporarily stored in a
|
||||
// transient store. PutPrivateData doesn't effect the `collection` until the
|
||||
// transaction is validated and successfully committed. Simple keys must not
|
||||
// be an empty string and must not start with a null character (0x00) in order
|
||||
// to avoid range query collisions with composite keys, which internally get
|
||||
// prefixed with 0x00 as composite key namespace. In addition, if using
|
||||
// CouchDB, keys can only contain valid UTF-8 strings and cannot begin with an
|
||||
// an underscore ("_").
|
||||
PutPrivateData(collection string, key string, value []byte) error
|
||||
|
||||
// DelPrivateData records the specified `key` to be deleted in the private writeset
|
||||
// of the transaction. Note that only hash of the private writeset goes into the
|
||||
// transaction proposal response (which is sent to the client who issued the
|
||||
// transaction) and the actual private writeset gets temporarily stored in a
|
||||
// transient store. The `key` and its value will be deleted from the collection
|
||||
// when the transaction is validated and successfully committed.
|
||||
DelPrivateData(collection, key string) error
|
||||
|
||||
// PurgePrivateData records the specified `key` to be purged in the private writeset
|
||||
// of the transaction. Note that only hash of the private writeset goes into the
|
||||
// transaction proposal response (which is sent to the client who issued the
|
||||
// transaction) and the actual private writeset gets temporarily stored in a
|
||||
// transient store. The `key` and its value will be deleted from the collection
|
||||
// when the transaction is validated and successfully committed, and will
|
||||
// subsequently be completely removed from the private data store (that maintains
|
||||
// the historical versions of private writesets) as a background operation.
|
||||
PurgePrivateData(collection, key string) error
|
||||
|
||||
// SetPrivateDataValidationParameter sets the key-level endorsement policy
|
||||
// for the private data specified by `key`.
|
||||
SetPrivateDataValidationParameter(collection, key string, ep []byte) error
|
||||
|
||||
// GetPrivateDataValidationParameter retrieves the key-level endorsement
|
||||
// policy for the private data specified by `key`. Note that this introduces
|
||||
// a read dependency on `key` in the transaction's readset.
|
||||
GetPrivateDataValidationParameter(collection, key string) ([]byte, error)
|
||||
|
||||
// GetPrivateDataByRange returns a range iterator over a set of keys in a
|
||||
// given private collection. The iterator can be used to iterate over all keys
|
||||
// between the startKey (inclusive) and endKey (exclusive).
|
||||
// The keys are returned by the iterator in lexical order. Note
|
||||
// that startKey and endKey can be empty string, which implies unbounded range
|
||||
// query on start or end.
|
||||
// Call Close() on the returned StateQueryIteratorInterface object when done.
|
||||
// The query is re-executed during validation phase to ensure result set
|
||||
// has not changed since transaction endorsement (phantom reads detected).
|
||||
GetPrivateDataByRange(collection, startKey, endKey string) (StateQueryIteratorInterface, error)
|
||||
|
||||
// GetPrivateDataByPartialCompositeKey queries the state in a given private
|
||||
// collection based on a given partial composite key. This function returns
|
||||
// an iterator which can be used to iterate over all composite keys whose prefix
|
||||
// matches the given partial composite key. The `objectType` and attributes are
|
||||
// expected to have only valid utf8 strings and should not contain
|
||||
// U+0000 (nil byte) and U+10FFFF (biggest and unallocated code point).
|
||||
// See related functions SplitCompositeKey and CreateCompositeKey.
|
||||
// Call Close() on the returned StateQueryIteratorInterface object when done.
|
||||
// The query is re-executed during validation phase to ensure result set
|
||||
// has not changed since transaction endorsement (phantom reads detected). This function should be used only for
|
||||
//a partial composite key. For a full composite key, an iter with empty response
|
||||
//would be returned.
|
||||
GetPrivateDataByPartialCompositeKey(collection, objectType string, keys []string) (StateQueryIteratorInterface, error)
|
||||
|
||||
// GetPrivateDataQueryResult performs a "rich" query against a given private
|
||||
// collection. It is only supported for state databases that support rich query,
|
||||
// e.g.CouchDB. The query string is in the native syntax
|
||||
// of the underlying state database. An iterator is returned
|
||||
// which can be used to iterate (next) over the query result set.
|
||||
// The query is NOT re-executed during validation phase, phantom reads are
|
||||
// not detected. That is, other committed transactions may have added,
|
||||
// updated, or removed keys that impact the result set, and this would not
|
||||
// be detected at validation/commit time. Applications susceptible to this
|
||||
// should therefore not use GetPrivateDataQueryResult as part of transactions that update
|
||||
// ledger, and should limit use to read-only chaincode operations.
|
||||
GetPrivateDataQueryResult(collection, query string) (StateQueryIteratorInterface, error)
|
||||
|
||||
// GetCreator returns `SignatureHeader.Creator` (e.g. an identity)
|
||||
// of the `SignedProposal`. This is the identity of the agent (or user)
|
||||
// submitting the transaction.
|
||||
GetCreator() ([]byte, error)
|
||||
|
||||
// GetTransient returns the `ChaincodeProposalPayload.Transient` field.
|
||||
// It is a map that contains data (e.g. cryptographic material)
|
||||
// that might be used to implement some form of application-level
|
||||
// confidentiality. The contents of this field, as prescribed by
|
||||
// `ChaincodeProposalPayload`, are supposed to always
|
||||
// be omitted from the transaction and excluded from the ledger.
|
||||
GetTransient() (map[string][]byte, error)
|
||||
|
||||
// GetBinding returns the transaction binding, which is used to enforce a
|
||||
// link between application data (like those stored in the transient field
|
||||
// above) to the proposal itself. This is useful to avoid possible replay
|
||||
// attacks.
|
||||
GetBinding() ([]byte, error)
|
||||
|
||||
// GetDecorations returns additional data (if applicable) about the proposal
|
||||
// that originated from the peer. This data is set by the decorators of the
|
||||
// peer, which append or mutate the chaincode input passed to the chaincode.
|
||||
GetDecorations() map[string][]byte
|
||||
|
||||
// GetSignedProposal returns the SignedProposal object, which contains all
|
||||
// data elements part of a transaction proposal.
|
||||
GetSignedProposal() (*peer.SignedProposal, error)
|
||||
|
||||
// GetTxTimestamp returns the timestamp when the transaction was created. This
|
||||
// is taken from the transaction ChannelHeader, therefore it will indicate the
|
||||
// client's timestamp and will have the same value across all endorsers.
|
||||
GetTxTimestamp() (*timestamppb.Timestamp, error)
|
||||
|
||||
// SetEvent allows the chaincode to set an event on the response to the
|
||||
// proposal to be included as part of a transaction. The event will be
|
||||
// available within the transaction in the committed block regardless of the
|
||||
// validity of the transaction.
|
||||
// Only a single event can be included in a transaction, and must originate
|
||||
// from the outer-most invoked chaincode in chaincode-to-chaincode scenarios.
|
||||
// The marshaled ChaincodeEvent will be available in the transaction's ChaincodeAction.events field.
|
||||
SetEvent(name string, payload []byte) error
|
||||
}
|
||||
|
||||
// CommonIteratorInterface allows a chaincode to check whether any more result
|
||||
// to be fetched from an iterator and close it when done.
|
||||
type CommonIteratorInterface interface {
|
||||
// HasNext returns true if the range query iterator contains additional keys
|
||||
// and values.
|
||||
HasNext() bool
|
||||
|
||||
// Close closes the iterator. This should be called when done
|
||||
// reading from the iterator to free up resources.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// StateQueryIteratorInterface allows a chaincode to iterate over a set of
|
||||
// key/value pairs returned by range and execute query.
|
||||
type StateQueryIteratorInterface interface {
|
||||
// Inherit HasNext() and Close()
|
||||
CommonIteratorInterface
|
||||
|
||||
// Next returns the next key and value in the range and execute query iterator.
|
||||
Next() (*queryresult.KV, error)
|
||||
}
|
||||
|
||||
// HistoryQueryIteratorInterface allows a chaincode to iterate over a set of
|
||||
// key/value pairs returned by a history query.
|
||||
type HistoryQueryIteratorInterface interface {
|
||||
// Inherit HasNext() and Close()
|
||||
CommonIteratorInterface
|
||||
|
||||
// Next returns the next key and value in the history query iterator.
|
||||
Next() (*queryresult.KeyModification, error)
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"time"
|
||||
|
||||
"github.com/hyperledger/fabric-protos-go-apiv2/peer"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
const (
|
||||
dialTimeout = 10 * time.Second
|
||||
maxRecvMessageSize = 100 * 1024 * 1024 // 100 MiB
|
||||
maxSendMessageSize = 100 * 1024 * 1024 // 100 MiB
|
||||
)
|
||||
|
||||
// NewClientConn ...
|
||||
func NewClientConn(
|
||||
address string,
|
||||
tlsConf *tls.Config,
|
||||
kaOpts keepalive.ClientParameters,
|
||||
) (*grpc.ClientConn, error) {
|
||||
|
||||
dialOpts := []grpc.DialOption{
|
||||
grpc.WithKeepaliveParams(kaOpts),
|
||||
grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallRecvMsgSize(maxRecvMessageSize),
|
||||
grpc.MaxCallSendMsgSize(maxSendMessageSize),
|
||||
),
|
||||
}
|
||||
|
||||
if tlsConf != nil {
|
||||
creds := credentials.NewTLS(tlsConf)
|
||||
dialOpts = append(dialOpts, grpc.WithTransportCredentials(creds))
|
||||
} else {
|
||||
dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
}
|
||||
|
||||
return grpc.NewClient(address, dialOpts...)
|
||||
}
|
||||
|
||||
// NewRegisterClient ...
|
||||
func NewRegisterClient(conn *grpc.ClientConn) (peer.ChaincodeSupport_RegisterClient, error) {
|
||||
return peer.NewChaincodeSupportClient(conn).Register(context.Background())
|
||||
}
|
||||
@ -0,0 +1,150 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
// Config contains chaincode's configuration
|
||||
type Config struct {
|
||||
ChaincodeName string
|
||||
TLS *tls.Config
|
||||
KaOpts keepalive.ClientParameters
|
||||
}
|
||||
|
||||
// LoadConfig loads the chaincode configuration
|
||||
func LoadConfig() (Config, error) {
|
||||
var err error
|
||||
tlsEnabled, err := strconv.ParseBool(os.Getenv("CORE_PEER_TLS_ENABLED"))
|
||||
if err != nil {
|
||||
return Config{}, errors.New("'CORE_PEER_TLS_ENABLED' must be set to 'true' or 'false'")
|
||||
}
|
||||
|
||||
conf := Config{
|
||||
ChaincodeName: os.Getenv("CORE_CHAINCODE_ID_NAME"),
|
||||
// hardcode to match chaincode server
|
||||
KaOpts: keepalive.ClientParameters{
|
||||
Time: 1 * time.Minute,
|
||||
Timeout: 20 * time.Second,
|
||||
PermitWithoutStream: true,
|
||||
},
|
||||
}
|
||||
|
||||
if !tlsEnabled {
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
var key []byte
|
||||
path, set := os.LookupEnv("CORE_TLS_CLIENT_KEY_FILE")
|
||||
if set {
|
||||
key, err = os.ReadFile(path)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("failed to read private key file: %s", err)
|
||||
}
|
||||
} else {
|
||||
data, err := os.ReadFile(os.Getenv("CORE_TLS_CLIENT_KEY_PATH"))
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("failed to read private key file: %s", err)
|
||||
}
|
||||
key, err = base64.StdEncoding.DecodeString(string(data))
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("failed to decode private key file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
var cert []byte
|
||||
path, set = os.LookupEnv("CORE_TLS_CLIENT_CERT_FILE")
|
||||
if set {
|
||||
cert, err = os.ReadFile(path)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("failed to read public key file: %s", err)
|
||||
}
|
||||
} else {
|
||||
data, err := os.ReadFile(os.Getenv("CORE_TLS_CLIENT_CERT_PATH"))
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("failed to read public key file: %s", err)
|
||||
}
|
||||
cert, err = base64.StdEncoding.DecodeString(string(data))
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("failed to decode public key file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
root, err := os.ReadFile(os.Getenv("CORE_PEER_TLS_ROOTCERT_FILE"))
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("failed to read root cert file: %s", err)
|
||||
}
|
||||
|
||||
tlscfg, err := LoadTLSConfig(false, key, cert, root)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
|
||||
conf.TLS = tlscfg
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// LoadTLSConfig loads the TLS configuration for the chaincode
|
||||
func LoadTLSConfig(isserver bool, key, cert, root []byte) (*tls.Config, error) {
|
||||
if key == nil {
|
||||
return nil, fmt.Errorf("key not provided")
|
||||
}
|
||||
|
||||
if cert == nil {
|
||||
return nil, fmt.Errorf("cert not provided")
|
||||
}
|
||||
|
||||
if !isserver && root == nil {
|
||||
return nil, fmt.Errorf("root cert not provided")
|
||||
}
|
||||
|
||||
cccert, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse client key pair: %s", err)
|
||||
}
|
||||
|
||||
var rootCertPool *x509.CertPool
|
||||
if root != nil {
|
||||
rootCertPool = x509.NewCertPool()
|
||||
if ok := rootCertPool.AppendCertsFromPEM(root); !ok {
|
||||
return nil, errors.New("failed to load root cert file")
|
||||
}
|
||||
}
|
||||
|
||||
tlscfg := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
Certificates: []tls.Certificate{cccert},
|
||||
}
|
||||
|
||||
//follow Peer's server default config properties
|
||||
if isserver {
|
||||
tlscfg.ClientCAs = rootCertPool
|
||||
tlscfg.SessionTicketsDisabled = true
|
||||
tlscfg.CipherSuites = []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
}
|
||||
if rootCertPool != nil {
|
||||
tlscfg.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
} else {
|
||||
tlscfg.RootCAs = rootCertPool
|
||||
}
|
||||
|
||||
return tlscfg, nil
|
||||
}
|
||||
@ -0,0 +1,106 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
const (
|
||||
serverInterval = time.Duration(2) * time.Hour // 2 hours - gRPC default
|
||||
serverTimeout = time.Duration(20) * time.Second // 20 sec - gRPC default
|
||||
serverMinInterval = time.Duration(1) * time.Minute
|
||||
connectionTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
// Server abstracts grpc service properties
|
||||
type Server struct {
|
||||
Listener net.Listener
|
||||
Server *grpc.Server
|
||||
}
|
||||
|
||||
// Start the server
|
||||
func (s *Server) Start() error {
|
||||
if s.Listener == nil {
|
||||
return errors.New("nil listener")
|
||||
}
|
||||
|
||||
if s.Server == nil {
|
||||
return errors.New("nil server")
|
||||
}
|
||||
|
||||
return s.Server.Serve(s.Listener)
|
||||
}
|
||||
|
||||
// Stop the server
|
||||
func (s *Server) Stop() {
|
||||
if s.Server != nil {
|
||||
s.Server.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// NewServer creates a new implementation of a GRPC Server given a
|
||||
// listen address
|
||||
func NewServer(
|
||||
address string,
|
||||
tlsConf *tls.Config,
|
||||
srvKaOpts *keepalive.ServerParameters,
|
||||
) (*Server, error) {
|
||||
if address == "" {
|
||||
return nil, errors.New("server listen address not provided")
|
||||
}
|
||||
|
||||
//create our listener
|
||||
listener, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//set up server options for keepalive and TLS
|
||||
var serverOpts []grpc.ServerOption
|
||||
|
||||
if srvKaOpts != nil {
|
||||
serverOpts = append(serverOpts, grpc.KeepaliveParams(*srvKaOpts))
|
||||
} else {
|
||||
serverKeepAliveParameters := keepalive.ServerParameters{
|
||||
Time: 1 * time.Minute,
|
||||
Timeout: 20 * time.Second,
|
||||
}
|
||||
serverOpts = append(serverOpts, grpc.KeepaliveParams(serverKeepAliveParameters))
|
||||
}
|
||||
|
||||
if tlsConf != nil {
|
||||
serverOpts = append(serverOpts, grpc.Creds(credentials.NewTLS(tlsConf)))
|
||||
}
|
||||
|
||||
// Default properties follow - let's start simple and stick with defaults for now.
|
||||
// These match Fabric peer side properties. We can expose these as user properties
|
||||
// if needed
|
||||
|
||||
// set max send and recv msg sizes
|
||||
serverOpts = append(serverOpts, grpc.MaxSendMsgSize(maxSendMessageSize))
|
||||
serverOpts = append(serverOpts, grpc.MaxRecvMsgSize(maxRecvMessageSize))
|
||||
|
||||
//set enforcement policy
|
||||
kep := keepalive.EnforcementPolicy{
|
||||
MinTime: serverMinInterval,
|
||||
// allow keepalive w/o rpc
|
||||
PermitWithoutStream: true,
|
||||
}
|
||||
serverOpts = append(serverOpts, grpc.KeepaliveEnforcementPolicy(kep))
|
||||
|
||||
//set default connection timeout
|
||||
serverOpts = append(serverOpts, grpc.ConnectionTimeout(connectionTimeout))
|
||||
|
||||
server := grpc.NewServer(serverOpts...)
|
||||
|
||||
return &Server{Listener: listener, Server: server}, nil
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package shim
|
||||
|
||||
import (
|
||||
"github.com/hyperledger/fabric-protos-go-apiv2/peer"
|
||||
)
|
||||
|
||||
const (
|
||||
// OK constant - status code less than 400, endorser will endorse it.
|
||||
// OK means init or invoke successfully.
|
||||
OK = 200
|
||||
|
||||
// ERRORTHRESHOLD constant - status code greater than or equal to 400 will be considered an error and rejected by endorser.
|
||||
ERRORTHRESHOLD = 400
|
||||
|
||||
// ERROR constant - default error value
|
||||
ERROR = 500
|
||||
)
|
||||
|
||||
// Success ...
|
||||
func Success(payload []byte) *peer.Response {
|
||||
return &peer.Response{
|
||||
Status: OK,
|
||||
Payload: payload,
|
||||
}
|
||||
}
|
||||
|
||||
// Error ...
|
||||
func Error(msg string) *peer.Response {
|
||||
return &peer.Response{
|
||||
Status: ERROR,
|
||||
Message: msg,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,153 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package shim provides APIs for the chaincode to access its state
|
||||
// variables, transaction context and call other chaincodes.
|
||||
package shim
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/hyperledger/fabric-chaincode-go/v2/shim/internal"
|
||||
"github.com/hyperledger/fabric-protos-go-apiv2/peer"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
minUnicodeRuneValue = 0 //U+0000
|
||||
maxUnicodeRuneValue = utf8.MaxRune //U+10FFFF - maximum (and unallocated) code point
|
||||
compositeKeyNamespace = "\x00"
|
||||
emptyKeySubstitute = "\x01"
|
||||
)
|
||||
|
||||
// peer as server
|
||||
var peerAddress = flag.String("peer.address", "", "peer address")
|
||||
|
||||
// this separates the chaincode stream interface establishment
|
||||
// so we can replace it with a mock peer stream
|
||||
type peerStreamGetter func(name string) (ClientStream, error)
|
||||
|
||||
// UTs to setup mock peer stream getter
|
||||
var streamGetter peerStreamGetter
|
||||
|
||||
// the non-mock user CC stream establishment func
|
||||
func userChaincodeStreamGetter(name string) (ClientStream, error) {
|
||||
if *peerAddress == "" {
|
||||
return nil, errors.New("flag 'peer.address' must be set")
|
||||
}
|
||||
|
||||
conf, err := internal.LoadConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := internal.NewClientConn(*peerAddress, conf.TLS, conf.KaOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return internal.NewRegisterClient(conn)
|
||||
}
|
||||
|
||||
// Start chaincodes
|
||||
func Start(cc Chaincode) error {
|
||||
flag.Parse()
|
||||
chaincodename := os.Getenv("CORE_CHAINCODE_ID_NAME")
|
||||
if chaincodename == "" {
|
||||
return errors.New("'CORE_CHAINCODE_ID_NAME' must be set")
|
||||
}
|
||||
|
||||
//mock stream not set up ... get real stream
|
||||
if streamGetter == nil {
|
||||
streamGetter = userChaincodeStreamGetter
|
||||
}
|
||||
|
||||
stream, err := streamGetter(chaincodename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = chaincodeAsClientChat(chaincodename, stream, cc)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// StartInProc is an entry point for system chaincodes bootstrap. It is not an
|
||||
// API for chaincodes.
|
||||
func StartInProc(chaincodename string, stream ClientStream, cc Chaincode) error {
|
||||
return chaincodeAsClientChat(chaincodename, stream, cc)
|
||||
}
|
||||
|
||||
// this is the chat stream resulting from the chaincode-as-client model where the chaincode initiates connection
|
||||
func chaincodeAsClientChat(chaincodename string, stream ClientStream, cc Chaincode) error {
|
||||
defer stream.CloseSend() //nolint:Errcheck
|
||||
return chatWithPeer(chaincodename, stream, cc)
|
||||
}
|
||||
|
||||
// chat stream for peer-chaincode interactions post connection
|
||||
func chatWithPeer(chaincodename string, stream PeerChaincodeStream, cc Chaincode) error {
|
||||
// Create the shim handler responsible for all control logic
|
||||
handler := newChaincodeHandler(stream, cc)
|
||||
|
||||
// Send the ChaincodeID during register.
|
||||
chaincodeID := &peer.ChaincodeID{Name: chaincodename}
|
||||
payload, err := proto.Marshal(chaincodeID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshalling chaincodeID during chaincode registration: %s", err)
|
||||
}
|
||||
|
||||
// Register on the stream
|
||||
if err = handler.serialSend(&peer.ChaincodeMessage{Type: peer.ChaincodeMessage_REGISTER, Payload: payload}); err != nil {
|
||||
return fmt.Errorf("error sending chaincode REGISTER: %s", err)
|
||||
|
||||
}
|
||||
|
||||
// holds return values from gRPC Recv below
|
||||
type recvMsg struct {
|
||||
msg *peer.ChaincodeMessage
|
||||
err error
|
||||
}
|
||||
msgAvail := make(chan *recvMsg, 1)
|
||||
errc := make(chan error)
|
||||
|
||||
receiveMessage := func() {
|
||||
in, err := stream.Recv()
|
||||
msgAvail <- &recvMsg{in, err}
|
||||
}
|
||||
|
||||
go receiveMessage()
|
||||
for {
|
||||
select {
|
||||
case rmsg := <-msgAvail:
|
||||
switch {
|
||||
case rmsg.err == io.EOF:
|
||||
return errors.New("received EOF, ending chaincode stream")
|
||||
case rmsg.err != nil:
|
||||
err := fmt.Errorf("receive failed: %s", rmsg.err)
|
||||
return err
|
||||
case rmsg.msg == nil:
|
||||
err := errors.New("received nil message, ending chaincode stream")
|
||||
return err
|
||||
default:
|
||||
err := handler.handleMessage(rmsg.msg, errc)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error handling message: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
go receiveMessage()
|
||||
}
|
||||
|
||||
case sendErr := <-errc:
|
||||
if sendErr != nil {
|
||||
err := fmt.Errorf("error sending: %s", sendErr)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,759 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package shim
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/hyperledger/fabric-protos-go-apiv2/common"
|
||||
"github.com/hyperledger/fabric-protos-go-apiv2/ledger/queryresult"
|
||||
"github.com/hyperledger/fabric-protos-go-apiv2/peer"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// ChaincodeStub is an object passed to chaincode for shim side handling of
|
||||
// APIs.
|
||||
type ChaincodeStub struct {
|
||||
TxID string
|
||||
ChannelID string
|
||||
chaincodeEvent *peer.ChaincodeEvent
|
||||
args [][]byte
|
||||
handler *Handler
|
||||
signedProposal *peer.SignedProposal
|
||||
proposal *peer.Proposal
|
||||
validationParameterMetakey string
|
||||
|
||||
// Additional fields extracted from the signedProposal
|
||||
creator []byte
|
||||
transient map[string][]byte
|
||||
binding []byte
|
||||
|
||||
decorations map[string][]byte
|
||||
}
|
||||
|
||||
// ChaincodeInvocation functionality
|
||||
|
||||
func newChaincodeStub(handler *Handler, channelID, txid string, input *peer.ChaincodeInput, signedProposal *peer.SignedProposal) (*ChaincodeStub, error) {
|
||||
stub := &ChaincodeStub{
|
||||
TxID: txid,
|
||||
ChannelID: channelID,
|
||||
args: input.Args,
|
||||
handler: handler,
|
||||
signedProposal: signedProposal,
|
||||
decorations: input.Decorations,
|
||||
validationParameterMetakey: peer.MetaDataKeys_VALIDATION_PARAMETER.String(),
|
||||
}
|
||||
|
||||
// TODO: sanity check: verify that every call to init with a nil
|
||||
// signedProposal is a legitimate one, meaning it is an internal call
|
||||
// to system chaincodes.
|
||||
if signedProposal != nil {
|
||||
var err error
|
||||
|
||||
stub.proposal = &peer.Proposal{}
|
||||
err = proto.Unmarshal(signedProposal.ProposalBytes, stub.proposal)
|
||||
if err != nil {
|
||||
|
||||
return nil, fmt.Errorf("failed to extract Proposal from SignedProposal: %s", err)
|
||||
}
|
||||
|
||||
// check for header
|
||||
if len(stub.proposal.GetHeader()) == 0 {
|
||||
return nil, errors.New("failed to extract Proposal fields: proposal header is nil")
|
||||
}
|
||||
|
||||
// Extract creator, transient, binding...
|
||||
hdr := &common.Header{}
|
||||
if err := proto.Unmarshal(stub.proposal.GetHeader(), hdr); err != nil {
|
||||
return nil, fmt.Errorf("failed to extract proposal header: %s", err)
|
||||
}
|
||||
|
||||
// extract and validate channel header
|
||||
chdr := &common.ChannelHeader{}
|
||||
if err := proto.Unmarshal(hdr.ChannelHeader, chdr); err != nil {
|
||||
return nil, fmt.Errorf("failed to extract channel header: %s", err)
|
||||
}
|
||||
validTypes := map[common.HeaderType]bool{
|
||||
common.HeaderType_ENDORSER_TRANSACTION: true,
|
||||
common.HeaderType_CONFIG: true,
|
||||
}
|
||||
if !validTypes[common.HeaderType(chdr.GetType())] {
|
||||
return nil, fmt.Errorf(
|
||||
"invalid channel header type. Expected %s or %s, received %s",
|
||||
common.HeaderType_ENDORSER_TRANSACTION,
|
||||
common.HeaderType_CONFIG,
|
||||
common.HeaderType(chdr.GetType()),
|
||||
)
|
||||
}
|
||||
|
||||
// extract creator from signature header
|
||||
shdr := &common.SignatureHeader{}
|
||||
if err := proto.Unmarshal(hdr.GetSignatureHeader(), shdr); err != nil {
|
||||
return nil, fmt.Errorf("failed to extract signature header: %s", err)
|
||||
}
|
||||
stub.creator = shdr.GetCreator()
|
||||
|
||||
// extract transient data from proposal payload
|
||||
payload := &peer.ChaincodeProposalPayload{}
|
||||
if err := proto.Unmarshal(stub.proposal.GetPayload(), payload); err != nil {
|
||||
return nil, fmt.Errorf("failed to extract proposal payload: %s", err)
|
||||
}
|
||||
stub.transient = payload.GetTransientMap()
|
||||
|
||||
// compute the proposal binding from the nonce, creator and epoch
|
||||
epoch := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(epoch, chdr.GetEpoch())
|
||||
digest := sha256.Sum256(append(append(shdr.GetNonce(), stub.creator...), epoch...))
|
||||
stub.binding = digest[:]
|
||||
|
||||
}
|
||||
|
||||
return stub, nil
|
||||
}
|
||||
|
||||
// GetTxID returns the transaction ID for the proposal
|
||||
func (s *ChaincodeStub) GetTxID() string {
|
||||
return s.TxID
|
||||
}
|
||||
|
||||
// GetChannelID returns the channel for the proposal
|
||||
func (s *ChaincodeStub) GetChannelID() string {
|
||||
return s.ChannelID
|
||||
}
|
||||
|
||||
// GetDecorations ...
|
||||
func (s *ChaincodeStub) GetDecorations() map[string][]byte {
|
||||
return s.decorations
|
||||
}
|
||||
|
||||
// GetMSPID returns the local mspid of the peer by checking the CORE_PEER_LOCALMSPID
|
||||
// env var and returns an error if the env var is not set
|
||||
func GetMSPID() (string, error) {
|
||||
mspid := os.Getenv("CORE_PEER_LOCALMSPID")
|
||||
|
||||
if mspid == "" {
|
||||
return "", errors.New("'CORE_PEER_LOCALMSPID' is not set")
|
||||
}
|
||||
|
||||
return mspid, nil
|
||||
}
|
||||
|
||||
// ------------- Call Chaincode functions ---------------
|
||||
|
||||
// InvokeChaincode documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) InvokeChaincode(chaincodeName string, args [][]byte, channel string) *peer.Response {
|
||||
// Internally we handle chaincode name as a composite name
|
||||
if channel != "" {
|
||||
chaincodeName = chaincodeName + "/" + channel
|
||||
}
|
||||
return s.handler.handleInvokeChaincode(chaincodeName, args, s.ChannelID, s.TxID)
|
||||
}
|
||||
|
||||
// --------- State functions ----------
|
||||
|
||||
// GetState documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetState(key string) ([]byte, error) {
|
||||
// Access public data by setting the collection to empty string
|
||||
collection := ""
|
||||
return s.handler.handleGetState(collection, key, s.ChannelID, s.TxID)
|
||||
}
|
||||
|
||||
// SetStateValidationParameter documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) SetStateValidationParameter(key string, ep []byte) error {
|
||||
return s.handler.handlePutStateMetadataEntry("", key, s.validationParameterMetakey, ep, s.ChannelID, s.TxID)
|
||||
}
|
||||
|
||||
// GetStateValidationParameter documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetStateValidationParameter(key string) ([]byte, error) {
|
||||
md, err := s.handler.handleGetStateMetadata("", key, s.ChannelID, s.TxID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ep, ok := md[s.validationParameterMetakey]; ok {
|
||||
return ep, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// PutState documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) PutState(key string, value []byte) error {
|
||||
if key == "" {
|
||||
return errors.New("key must not be an empty string")
|
||||
}
|
||||
// Access public data by setting the collection to empty string
|
||||
collection := ""
|
||||
return s.handler.handlePutState(collection, key, value, s.ChannelID, s.TxID)
|
||||
}
|
||||
|
||||
func (s *ChaincodeStub) createStateQueryIterator(response *peer.QueryResponse) *StateQueryIterator {
|
||||
return &StateQueryIterator{
|
||||
CommonIterator: &CommonIterator{
|
||||
handler: s.handler,
|
||||
channelID: s.ChannelID,
|
||||
txid: s.TxID,
|
||||
response: response,
|
||||
currentLoc: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetQueryResult documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetQueryResult(query string) (StateQueryIteratorInterface, error) {
|
||||
// Access public data by setting the collection to empty string
|
||||
collection := ""
|
||||
// ignore QueryResponseMetadata as it is not applicable for a rich query without pagination
|
||||
iterator, _, err := s.handleGetQueryResult(collection, query, nil)
|
||||
|
||||
return iterator, err
|
||||
}
|
||||
|
||||
// DelState documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) DelState(key string) error {
|
||||
// Access public data by setting the collection to empty string
|
||||
collection := ""
|
||||
return s.handler.handleDelState(collection, key, s.ChannelID, s.TxID)
|
||||
}
|
||||
|
||||
// --------- private state functions ---------
|
||||
|
||||
// GetPrivateData documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetPrivateData(collection string, key string) ([]byte, error) {
|
||||
if collection == "" {
|
||||
return nil, fmt.Errorf("collection must not be an empty string")
|
||||
}
|
||||
return s.handler.handleGetState(collection, key, s.ChannelID, s.TxID)
|
||||
}
|
||||
|
||||
// GetPrivateDataHash documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetPrivateDataHash(collection string, key string) ([]byte, error) {
|
||||
if collection == "" {
|
||||
return nil, fmt.Errorf("collection must not be an empty string")
|
||||
}
|
||||
return s.handler.handleGetPrivateDataHash(collection, key, s.ChannelID, s.TxID)
|
||||
}
|
||||
|
||||
// PutPrivateData documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) PutPrivateData(collection string, key string, value []byte) error {
|
||||
if collection == "" {
|
||||
return fmt.Errorf("collection must not be an empty string")
|
||||
}
|
||||
if key == "" {
|
||||
return fmt.Errorf("key must not be an empty string")
|
||||
}
|
||||
return s.handler.handlePutState(collection, key, value, s.ChannelID, s.TxID)
|
||||
}
|
||||
|
||||
// DelPrivateData documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) DelPrivateData(collection string, key string) error {
|
||||
if collection == "" {
|
||||
return fmt.Errorf("collection must not be an empty string")
|
||||
}
|
||||
return s.handler.handleDelState(collection, key, s.ChannelID, s.TxID)
|
||||
}
|
||||
|
||||
// PurgePrivateData documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) PurgePrivateData(collection string, key string) error {
|
||||
if collection == "" {
|
||||
return fmt.Errorf("collection must not be an empty string")
|
||||
}
|
||||
return s.handler.handlePurgeState(collection, key, s.ChannelID, s.TxID)
|
||||
}
|
||||
|
||||
// GetPrivateDataByRange documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetPrivateDataByRange(collection, startKey, endKey string) (StateQueryIteratorInterface, error) {
|
||||
if collection == "" {
|
||||
return nil, fmt.Errorf("collection must not be an empty string")
|
||||
}
|
||||
if startKey == "" {
|
||||
startKey = emptyKeySubstitute
|
||||
}
|
||||
if err := validateSimpleKeys(startKey, endKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// ignore QueryResponseMetadata as it is not applicable for a range query without pagination
|
||||
iterator, _, err := s.handleGetStateByRange(collection, startKey, endKey, nil)
|
||||
|
||||
return iterator, err
|
||||
}
|
||||
|
||||
func (s *ChaincodeStub) createRangeKeysForPartialCompositeKey(objectType string, attributes []string) (string, string, error) {
|
||||
partialCompositeKey, err := s.CreateCompositeKey(objectType, attributes)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
startKey := partialCompositeKey
|
||||
endKey := partialCompositeKey + string(maxUnicodeRuneValue)
|
||||
|
||||
return startKey, endKey, nil
|
||||
}
|
||||
|
||||
// GetPrivateDataByPartialCompositeKey documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetPrivateDataByPartialCompositeKey(collection, objectType string, attributes []string) (StateQueryIteratorInterface, error) {
|
||||
if collection == "" {
|
||||
return nil, fmt.Errorf("collection must not be an empty string")
|
||||
}
|
||||
|
||||
startKey, endKey, err := s.createRangeKeysForPartialCompositeKey(objectType, attributes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// ignore QueryResponseMetadata as it is not applicable for a partial composite key query without pagination
|
||||
iterator, _, err := s.handleGetStateByRange(collection, startKey, endKey, nil)
|
||||
|
||||
return iterator, err
|
||||
}
|
||||
|
||||
// GetPrivateDataQueryResult documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetPrivateDataQueryResult(collection, query string) (StateQueryIteratorInterface, error) {
|
||||
if collection == "" {
|
||||
return nil, fmt.Errorf("collection must not be an empty string")
|
||||
}
|
||||
// ignore QueryResponseMetadata as it is not applicable for a range query without pagination
|
||||
iterator, _, err := s.handleGetQueryResult(collection, query, nil)
|
||||
|
||||
return iterator, err
|
||||
}
|
||||
|
||||
// GetPrivateDataValidationParameter documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetPrivateDataValidationParameter(collection, key string) ([]byte, error) {
|
||||
md, err := s.handler.handleGetStateMetadata(collection, key, s.ChannelID, s.TxID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ep, ok := md[s.validationParameterMetakey]; ok {
|
||||
return ep, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// SetPrivateDataValidationParameter documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) SetPrivateDataValidationParameter(collection, key string, ep []byte) error {
|
||||
return s.handler.handlePutStateMetadataEntry(collection, key, s.validationParameterMetakey, ep, s.ChannelID, s.TxID)
|
||||
}
|
||||
|
||||
// CommonIterator documentation can be found in interfaces.go
|
||||
type CommonIterator struct {
|
||||
handler *Handler
|
||||
channelID string
|
||||
txid string
|
||||
response *peer.QueryResponse
|
||||
currentLoc int
|
||||
}
|
||||
|
||||
// StateQueryIterator documentation can be found in interfaces.go
|
||||
type StateQueryIterator struct {
|
||||
*CommonIterator
|
||||
}
|
||||
|
||||
// HistoryQueryIterator documentation can be found in interfaces.go
|
||||
type HistoryQueryIterator struct {
|
||||
*CommonIterator
|
||||
}
|
||||
|
||||
// General interface for supporting different types of query results.
|
||||
// Actual types differ for different queries
|
||||
type queryResult interface{}
|
||||
|
||||
type resultType uint8
|
||||
|
||||
// TODO: Document constants
|
||||
/*
|
||||
Constants ...
|
||||
*/
|
||||
const (
|
||||
StateQueryResult resultType = iota + 1
|
||||
HistoryQueryResult
|
||||
)
|
||||
|
||||
func createQueryResponseMetadata(metadataBytes []byte) (*peer.QueryResponseMetadata, error) {
|
||||
metadata := &peer.QueryResponseMetadata{}
|
||||
err := proto.Unmarshal(metadataBytes, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func (s *ChaincodeStub) handleGetStateByRange(collection, startKey, endKey string,
|
||||
metadata []byte) (StateQueryIteratorInterface, *peer.QueryResponseMetadata, error) {
|
||||
|
||||
response, err := s.handler.handleGetStateByRange(collection, startKey, endKey, metadata, s.ChannelID, s.TxID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
iterator := s.createStateQueryIterator(response)
|
||||
responseMetadata, err := createQueryResponseMetadata(response.Metadata)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return iterator, responseMetadata, nil
|
||||
}
|
||||
|
||||
func (s *ChaincodeStub) handleGetQueryResult(collection, query string,
|
||||
metadata []byte) (StateQueryIteratorInterface, *peer.QueryResponseMetadata, error) {
|
||||
|
||||
response, err := s.handler.handleGetQueryResult(collection, query, metadata, s.ChannelID, s.TxID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
iterator := s.createStateQueryIterator(response)
|
||||
responseMetadata, err := createQueryResponseMetadata(response.Metadata)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return iterator, responseMetadata, nil
|
||||
}
|
||||
|
||||
// GetStateByRange documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) {
|
||||
if startKey == "" {
|
||||
startKey = emptyKeySubstitute
|
||||
}
|
||||
if err := validateSimpleKeys(startKey, endKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
collection := ""
|
||||
|
||||
// ignore QueryResponseMetadata as it is not applicable for a range query without pagination
|
||||
iterator, _, err := s.handleGetStateByRange(collection, startKey, endKey, nil)
|
||||
|
||||
return iterator, err
|
||||
}
|
||||
|
||||
// GetHistoryForKey documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error) {
|
||||
response, err := s.handler.handleGetHistoryForKey(key, s.ChannelID, s.TxID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &HistoryQueryIterator{CommonIterator: &CommonIterator{s.handler, s.ChannelID, s.TxID, response, 0}}, nil
|
||||
}
|
||||
|
||||
// CreateCompositeKey documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) CreateCompositeKey(objectType string, attributes []string) (string, error) {
|
||||
return CreateCompositeKey(objectType, attributes)
|
||||
}
|
||||
|
||||
// SplitCompositeKey documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) SplitCompositeKey(compositeKey string) (string, []string, error) {
|
||||
return splitCompositeKey(compositeKey)
|
||||
}
|
||||
|
||||
// CreateCompositeKey ...
|
||||
func CreateCompositeKey(objectType string, attributes []string) (string, error) {
|
||||
if err := validateCompositeKeyAttribute(objectType); err != nil {
|
||||
return "", err
|
||||
}
|
||||
ck := compositeKeyNamespace + objectType + string(rune(minUnicodeRuneValue))
|
||||
for _, att := range attributes {
|
||||
if err := validateCompositeKeyAttribute(att); err != nil {
|
||||
return "", err
|
||||
}
|
||||
ck += att + string(rune(minUnicodeRuneValue))
|
||||
}
|
||||
return ck, nil
|
||||
}
|
||||
|
||||
func splitCompositeKey(compositeKey string) (string, []string, error) {
|
||||
componentIndex := 1
|
||||
components := []string{}
|
||||
for i := 1; i < len(compositeKey); i++ {
|
||||
if compositeKey[i] == minUnicodeRuneValue {
|
||||
components = append(components, compositeKey[componentIndex:i])
|
||||
componentIndex = i + 1
|
||||
}
|
||||
}
|
||||
return components[0], components[1:], nil
|
||||
}
|
||||
|
||||
func validateCompositeKeyAttribute(str string) error {
|
||||
if !utf8.ValidString(str) {
|
||||
return fmt.Errorf("not a valid utf8 string: [%x]", str)
|
||||
}
|
||||
for index, runeValue := range str {
|
||||
if runeValue == minUnicodeRuneValue || runeValue == maxUnicodeRuneValue {
|
||||
return fmt.Errorf(`input contains unicode %#U starting at position [%d]. %#U and %#U are not allowed in the input attribute of a composite key`,
|
||||
runeValue, index, minUnicodeRuneValue, maxUnicodeRuneValue)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// To ensure that simple keys do not go into composite key namespace,
|
||||
// we validate simplekey to check whether the key starts with 0x00 (which
|
||||
// is the namespace for compositeKey). This helps in avoiding simple/composite
|
||||
// key collisions.
|
||||
func validateSimpleKeys(simpleKeys ...string) error {
|
||||
for _, key := range simpleKeys {
|
||||
if len(key) > 0 && key[0] == compositeKeyNamespace[0] {
|
||||
return fmt.Errorf(`first character of the key [%s] contains a null character which is not allowed`, key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStateByPartialCompositeKey documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetStateByPartialCompositeKey(objectType string, attributes []string) (StateQueryIteratorInterface, error) {
|
||||
collection := ""
|
||||
startKey, endKey, err := s.createRangeKeysForPartialCompositeKey(objectType, attributes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// ignore QueryResponseMetadata as it is not applicable for a partial composite key query without pagination
|
||||
iterator, _, err := s.handleGetStateByRange(collection, startKey, endKey, nil)
|
||||
|
||||
return iterator, err
|
||||
}
|
||||
|
||||
func createQueryMetadata(pageSize int32, bookmark string) ([]byte, error) {
|
||||
// Construct the QueryMetadata with a page size and a bookmark needed for pagination
|
||||
metadata := &peer.QueryMetadata{PageSize: pageSize, Bookmark: bookmark}
|
||||
metadataBytes, err := proto.Marshal(metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return metadataBytes, nil
|
||||
}
|
||||
|
||||
// GetStateByRangeWithPagination ...
|
||||
func (s *ChaincodeStub) GetStateByRangeWithPagination(startKey, endKey string, pageSize int32,
|
||||
bookmark string) (StateQueryIteratorInterface, *peer.QueryResponseMetadata, error) {
|
||||
|
||||
if startKey == "" {
|
||||
startKey = emptyKeySubstitute
|
||||
}
|
||||
if err := validateSimpleKeys(startKey, endKey); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
collection := ""
|
||||
|
||||
metadata, err := createQueryMetadata(pageSize, bookmark)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return s.handleGetStateByRange(collection, startKey, endKey, metadata)
|
||||
}
|
||||
|
||||
// GetStateByPartialCompositeKeyWithPagination ...
|
||||
func (s *ChaincodeStub) GetStateByPartialCompositeKeyWithPagination(objectType string, keys []string,
|
||||
pageSize int32, bookmark string) (StateQueryIteratorInterface, *peer.QueryResponseMetadata, error) {
|
||||
|
||||
collection := ""
|
||||
|
||||
metadata, err := createQueryMetadata(pageSize, bookmark)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
startKey, endKey, err := s.createRangeKeysForPartialCompositeKey(objectType, keys)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return s.handleGetStateByRange(collection, startKey, endKey, metadata)
|
||||
}
|
||||
|
||||
// GetQueryResultWithPagination ...
|
||||
func (s *ChaincodeStub) GetQueryResultWithPagination(query string, pageSize int32,
|
||||
bookmark string) (StateQueryIteratorInterface, *peer.QueryResponseMetadata, error) {
|
||||
// Access public data by setting the collection to empty string
|
||||
collection := ""
|
||||
|
||||
metadata, err := createQueryMetadata(pageSize, bookmark)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return s.handleGetQueryResult(collection, query, metadata)
|
||||
}
|
||||
|
||||
// Next ...
|
||||
func (iter *StateQueryIterator) Next() (*queryresult.KV, error) {
|
||||
result, err := iter.nextResult(StateQueryResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.(*queryresult.KV), err
|
||||
}
|
||||
|
||||
// Next ...
|
||||
func (iter *HistoryQueryIterator) Next() (*queryresult.KeyModification, error) {
|
||||
result, err := iter.nextResult(HistoryQueryResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.(*queryresult.KeyModification), err
|
||||
}
|
||||
|
||||
// HasNext documentation can be found in interfaces.go
|
||||
func (iter *CommonIterator) HasNext() bool {
|
||||
if iter.currentLoc < len(iter.response.Results) || iter.response.HasMore {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getResultsFromBytes deserializes QueryResult and return either a KV struct
|
||||
// or KeyModification depending on the result type (i.e., state (range/execute)
|
||||
// query, history query). Note that queryResult is an empty golang
|
||||
// interface that can hold values of any type.
|
||||
func (iter *CommonIterator) getResultFromBytes(queryResultBytes *peer.QueryResultBytes,
|
||||
rType resultType) (queryResult, error) {
|
||||
|
||||
if rType == StateQueryResult {
|
||||
stateQueryResult := &queryresult.KV{}
|
||||
if err := proto.Unmarshal(queryResultBytes.ResultBytes, stateQueryResult); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling result from bytes: %s", err)
|
||||
}
|
||||
return stateQueryResult, nil
|
||||
|
||||
} else if rType == HistoryQueryResult {
|
||||
historyQueryResult := &queryresult.KeyModification{}
|
||||
if err := proto.Unmarshal(queryResultBytes.ResultBytes, historyQueryResult); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return historyQueryResult, nil
|
||||
}
|
||||
return nil, errors.New("wrong result type")
|
||||
}
|
||||
|
||||
func (iter *CommonIterator) fetchNextQueryResult() error {
|
||||
response, err := iter.handler.handleQueryStateNext(iter.response.Id, iter.channelID, iter.txid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iter.currentLoc = 0
|
||||
iter.response = response
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextResult returns the next QueryResult (i.e., either a KV struct or KeyModification)
|
||||
// from the state or history query iterator. Note that queryResult is an
|
||||
// empty golang interface that can hold values of any type.
|
||||
func (iter *CommonIterator) nextResult(rType resultType) (queryResult, error) {
|
||||
if iter.currentLoc < len(iter.response.Results) {
|
||||
// On valid access of an element from cached results
|
||||
queryResult, err := iter.getResultFromBytes(iter.response.Results[iter.currentLoc], rType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iter.currentLoc++
|
||||
|
||||
if iter.currentLoc == len(iter.response.Results) && iter.response.HasMore {
|
||||
// On access of last item, pre-fetch to update HasMore flag
|
||||
if err = iter.fetchNextQueryResult(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return queryResult, err
|
||||
} else if !iter.response.HasMore {
|
||||
// On call to Next() without check of HasMore
|
||||
return nil, errors.New("no such key")
|
||||
}
|
||||
|
||||
// should not fall through here
|
||||
// case: no cached results but HasMore is true.
|
||||
return nil, errors.New("invalid iterator state")
|
||||
}
|
||||
|
||||
// Close documentation can be found in interfaces.go
|
||||
func (iter *CommonIterator) Close() error {
|
||||
_, err := iter.handler.handleQueryStateClose(iter.response.Id, iter.channelID, iter.txid)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetArgs documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetArgs() [][]byte {
|
||||
return s.args
|
||||
}
|
||||
|
||||
// GetStringArgs documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetStringArgs() []string {
|
||||
args := s.GetArgs()
|
||||
strargs := make([]string, 0, len(args))
|
||||
for _, barg := range args {
|
||||
strargs = append(strargs, string(barg))
|
||||
}
|
||||
return strargs
|
||||
}
|
||||
|
||||
// GetFunctionAndParameters documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetFunctionAndParameters() (function string, params []string) {
|
||||
allargs := s.GetStringArgs()
|
||||
function = ""
|
||||
params = []string{}
|
||||
if len(allargs) >= 1 {
|
||||
function = allargs[0]
|
||||
params = allargs[1:]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetCreator documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetCreator() ([]byte, error) {
|
||||
return s.creator, nil
|
||||
}
|
||||
|
||||
// GetTransient documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetTransient() (map[string][]byte, error) {
|
||||
return s.transient, nil
|
||||
}
|
||||
|
||||
// GetBinding documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetBinding() ([]byte, error) {
|
||||
return s.binding, nil
|
||||
}
|
||||
|
||||
// GetSignedProposal documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetSignedProposal() (*peer.SignedProposal, error) {
|
||||
return s.signedProposal, nil
|
||||
}
|
||||
|
||||
// GetArgsSlice documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetArgsSlice() ([]byte, error) {
|
||||
args := s.GetArgs()
|
||||
res := []byte{}
|
||||
for _, barg := range args {
|
||||
res = append(res, barg...)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetTxTimestamp documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) GetTxTimestamp() (*timestamppb.Timestamp, error) {
|
||||
hdr := &common.Header{}
|
||||
if err := proto.Unmarshal(s.proposal.Header, hdr); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling Header: %s", err)
|
||||
}
|
||||
|
||||
chdr := &common.ChannelHeader{}
|
||||
if err := proto.Unmarshal(hdr.ChannelHeader, chdr); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling ChannelHeader: %s", err)
|
||||
}
|
||||
|
||||
return chdr.GetTimestamp(), nil
|
||||
}
|
||||
|
||||
// ------------- ChaincodeEvent API ----------------------
|
||||
|
||||
// SetEvent documentation can be found in interfaces.go
|
||||
func (s *ChaincodeStub) SetEvent(name string, payload []byte) error {
|
||||
if name == "" {
|
||||
return errors.New("event name can not be empty string")
|
||||
}
|
||||
s.chaincodeEvent = &peer.ChaincodeEvent{EventName: name, Payload: payload}
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,201 @@
|
||||
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.
|
||||
@ -0,0 +1,128 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package contractapi
|
||||
|
||||
import "github.com/hyperledger/fabric-contract-api-go/v2/metadata"
|
||||
|
||||
// IgnoreContractInterface extends ContractInterface and provides additional functionality
|
||||
// that can be used to mark which functions should not be accessible by invoking/querying
|
||||
// chaincode
|
||||
type IgnoreContractInterface interface {
|
||||
// GetIgnoredFunctions returns a list of function names for functions that should not
|
||||
// be included in the produced metadata or accessible by invoking/querying the chaincode.
|
||||
// Note these functions are still callable by the code just not directly by outside users.
|
||||
// Those that match functions in the ChaincodeInterface are ignored by default and do not
|
||||
// need to be included
|
||||
GetIgnoredFunctions() []string
|
||||
}
|
||||
|
||||
// EvaluationContractInterface extends ContractInterface and provides additional functionality
|
||||
// that can be used to improve metadata
|
||||
type EvaluationContractInterface interface {
|
||||
// GetEvaluateTransactions returns a list of function names that should be tagged in the
|
||||
// metadata as "evaluate" to indicate to a user of the chaincode that they should query
|
||||
// rather than invoke these functions
|
||||
GetEvaluateTransactions() []string
|
||||
}
|
||||
|
||||
// ContractInterface defines functions a valid contract should have. Contracts to
|
||||
// be used in chaincode must implement this interface.
|
||||
type ContractInterface interface {
|
||||
// GetInfo returns the information stored for the contract. This information will be
|
||||
// used to build up the metadata. If version is left blank in this info then "latest"
|
||||
// will be used in the metadata. If title is blank then the contract's GetName will be
|
||||
// used, if that is blank then the contract struct name
|
||||
GetInfo() metadata.InfoMetadata
|
||||
|
||||
// GetUnknownTransaction returns the unknown function to be used for a contract.
|
||||
// When the contract is used in creating a new chaincode this function is called
|
||||
// and the unknown transaction returned is stored. The unknown function is then
|
||||
// called in cases where an unknown function name is passed for a call to the
|
||||
// contract via Init/Invoke of the chaincode. If nil is returned the
|
||||
// chaincode uses its default handling for unknown function names
|
||||
GetUnknownTransaction() interface{}
|
||||
|
||||
// GetBeforeTransaction returns the before function to be used for a contract.
|
||||
// When the contract is used in creating a new chaincode this function is called
|
||||
// and the before transaction returned is stored. The before function is then
|
||||
// called before the named function on each Init/Invoke of that contract via the
|
||||
// chaincode. When called the before function is passed no extra args, only the
|
||||
// the transaction context (if specified to take it). If nil is returned
|
||||
// then no before function is called on Init/Invoke.
|
||||
GetBeforeTransaction() interface{}
|
||||
|
||||
// GetAfterTransaction returns the after function to be used for a contract.
|
||||
// When the contract is used in creating a new chaincode this function is called
|
||||
// and the after transaction returned is stored. The after function is then
|
||||
// called after the named function on each Init/Invoke of that contract via the
|
||||
// chaincode. When called the after function is passed the returned value of the
|
||||
// named function and the transaction context (if the function takes the transaction
|
||||
// context). If nil is returned then no after function is called on Init/
|
||||
// Invoke.
|
||||
GetAfterTransaction() interface{}
|
||||
|
||||
// GetName returns the name of the contract. When the contract is used
|
||||
// in creating a new chaincode this function is called and the name returned
|
||||
// is then used to identify the contract within the chaincode on Init/Invoke calls.
|
||||
// This function can return a blank string but this is undefined behaviour.
|
||||
GetName() string
|
||||
|
||||
// GetTransactionContextHandler returns the SettableTransactionContextInterface that is
|
||||
// used by the functions of the contract. When the contract is used in creating
|
||||
// a new chaincode this function is called and the transaction context returned
|
||||
// is stored. When the chaincode is called via Init/Invoke a transaction context
|
||||
// of the stored type is created and sent as a parameter to the named contract
|
||||
// function (and before/after and unknown functions) if the function requires the
|
||||
// context in its list of parameters. If functions taking the transaction context
|
||||
// take an interface as the context, the transaction context returned by this function
|
||||
// must meet that interface
|
||||
GetTransactionContextHandler() SettableTransactionContextInterface
|
||||
}
|
||||
|
||||
// Contract defines functions for setting and getting before, after and unknown transactions
|
||||
// and name. Can be embedded in structs to quickly ensure their definition meets the
|
||||
// ContractInterface.
|
||||
type Contract struct {
|
||||
Name string
|
||||
Info metadata.InfoMetadata
|
||||
UnknownTransaction interface{}
|
||||
BeforeTransaction interface{}
|
||||
AfterTransaction interface{}
|
||||
TransactionContextHandler SettableTransactionContextInterface
|
||||
}
|
||||
|
||||
// GetInfo returns the info about the contract for use in metadata
|
||||
func (c *Contract) GetInfo() metadata.InfoMetadata {
|
||||
return c.Info
|
||||
}
|
||||
|
||||
// GetUnknownTransaction returns the current set unknownTransaction, may be nil
|
||||
func (c *Contract) GetUnknownTransaction() interface{} {
|
||||
return c.UnknownTransaction
|
||||
}
|
||||
|
||||
// GetBeforeTransaction returns the current set beforeTransaction, may be nil
|
||||
func (c *Contract) GetBeforeTransaction() interface{} {
|
||||
return c.BeforeTransaction
|
||||
}
|
||||
|
||||
// GetAfterTransaction returns the current set afterTransaction, may be nil
|
||||
func (c *Contract) GetAfterTransaction() interface{} {
|
||||
return c.AfterTransaction
|
||||
}
|
||||
|
||||
// GetName returns the name of the contract
|
||||
func (c *Contract) GetName() string {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
// GetTransactionContextHandler returns the current transaction context set for
|
||||
// the contract. If none has been set then TransactionContext will be returned
|
||||
func (c *Contract) GetTransactionContextHandler() SettableTransactionContextInterface {
|
||||
if c.TransactionContextHandler == nil {
|
||||
return new(TransactionContext)
|
||||
}
|
||||
|
||||
return c.TransactionContextHandler
|
||||
}
|
||||
@ -0,0 +1,513 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package contractapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/hyperledger/fabric-chaincode-go/v2/pkg/cid"
|
||||
"github.com/hyperledger/fabric-chaincode-go/v2/shim"
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/internal"
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/internal/utils"
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/metadata"
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/serializer"
|
||||
"github.com/hyperledger/fabric-protos-go-apiv2/peer"
|
||||
)
|
||||
|
||||
//lint:ignore U1000 used for testing
|
||||
type chaincodeStubInterface interface {
|
||||
shim.ChaincodeStubInterface
|
||||
}
|
||||
|
||||
type contractChaincodeContract struct {
|
||||
info metadata.InfoMetadata
|
||||
functions map[string]*internal.ContractFunction
|
||||
unknownTransaction *internal.TransactionHandler
|
||||
beforeTransaction *internal.TransactionHandler
|
||||
afterTransaction *internal.TransactionHandler
|
||||
transactionContextHandler reflect.Type
|
||||
}
|
||||
|
||||
// ContractChaincode a struct to meet the chaincode interface and provide routing of calls to contracts
|
||||
type ContractChaincode struct {
|
||||
DefaultContract string
|
||||
contracts map[string]contractChaincodeContract
|
||||
metadata metadata.ContractChaincodeMetadata
|
||||
Info metadata.InfoMetadata
|
||||
TransactionSerializer serializer.TransactionSerializer
|
||||
}
|
||||
|
||||
const (
|
||||
// SystemContractName the name of the system smart contract
|
||||
SystemContractName = "org.hyperledger.fabric"
|
||||
serverAddressVariable = "CHAINCODE_SERVER_ADDRESS"
|
||||
chaincodeIdVariable = "CORE_CHAINCODE_ID_NAME"
|
||||
tlsEnabledVariable = "CORE_PEER_TLS_ENABLED"
|
||||
rootCertVariable = "CORE_PEER_TLS_ROOTCERT_FILE"
|
||||
clientKeyVariable = "CORE_TLS_CLIENT_KEY_FILE"
|
||||
clientCertVariable = "CORE_TLS_CLIENT_CERT_FILE"
|
||||
)
|
||||
|
||||
// NewChaincode creates a new chaincode using contracts passed. The function parses each
|
||||
// of the passed functions and stores details about their make-up to be used by the chaincode.
|
||||
// Public functions of the contracts are stored and are made callable in the chaincode. The function
|
||||
// will error if contracts are invalid e.g. public functions take in illegal types. A system contract is added
|
||||
// to the chaincode which provides functionality for getting the metadata of the chaincode. The generated
|
||||
// metadata is a JSON formatted MetadataContractChaincode containing each contract as a name and details
|
||||
// of the public functions and types they take in/return. It also outlines version details for contracts and the
|
||||
// chaincode. If these are blank strings this is set to latest. The names for parameters do not match those used
|
||||
// in the functions, instead they are recorded as param0, param1, ..., paramN. If there exists a file
|
||||
// contract-metadata/metadata.json then this will overwrite the generated metadata. The contents of this file must
|
||||
// validate against the schema. The transaction serializer for the contract is set to be the JSONSerializer by
|
||||
// default. This can be updated using by changing the TransactionSerializer property
|
||||
func NewChaincode(contracts ...ContractInterface) (*ContractChaincode, error) {
|
||||
ciMethods := getCiMethods()
|
||||
|
||||
cc := new(ContractChaincode)
|
||||
cc.contracts = make(map[string]contractChaincodeContract)
|
||||
|
||||
for _, contract := range contracts {
|
||||
additionalExcludes := []string{}
|
||||
if castContract, ok := contract.(IgnoreContractInterface); ok {
|
||||
additionalExcludes = castContract.GetIgnoredFunctions()
|
||||
}
|
||||
|
||||
err := cc.addContract(contract, append(ciMethods, additionalExcludes...))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
sysC := new(SystemContract)
|
||||
sysC.Name = SystemContractName
|
||||
|
||||
if err := cc.addContract(sysC, ciMethods); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := cc.augmentMetadata(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metadataJSON, _ := json.Marshal(cc.metadata)
|
||||
|
||||
sysC.setMetadata(string(metadataJSON))
|
||||
|
||||
cc.TransactionSerializer = new(serializer.JSONSerializer)
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Start starts the chaincode in the fabric shim
|
||||
func (cc *ContractChaincode) Start() error {
|
||||
server, err := loadChaincodeServerConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if server != nil {
|
||||
server.CC = cc
|
||||
return server.Start()
|
||||
}
|
||||
|
||||
return shim.Start(cc)
|
||||
}
|
||||
|
||||
// Init is called during Instantiate transaction after the chaincode container
|
||||
// has been established for the first time, passes off details of the request to Invoke
|
||||
// for handling the request if a function name is passed, otherwise returns shim.Success
|
||||
func (cc *ContractChaincode) Init(stub shim.ChaincodeStubInterface) *peer.Response {
|
||||
nsFcn, _ := stub.GetFunctionAndParameters()
|
||||
if nsFcn == "" {
|
||||
return shim.Success([]byte("Default initiator successful."))
|
||||
}
|
||||
|
||||
return cc.Invoke(stub)
|
||||
}
|
||||
|
||||
// Invoke is called to update or query the ledger in a proposal transaction. Takes the
|
||||
// args passed in the transaction and uses the first argument to identify the contract
|
||||
// and function of that contract to be called. The remaining args are then used as
|
||||
// parameters to that function. Args are converted from strings to the expected parameter
|
||||
// types of the function before being passed using the set transaction serializer for the ContractChaincode.
|
||||
// A transaction context is generated and is passed, if required, as the first parameter to the named function.
|
||||
// Before and after functions are called before and after the named function passed if the contract defines such
|
||||
// functions to exist. If the before function returns an error the named function is not called and its error
|
||||
// is returned in shim.Error. If the after function returns an error then its value is returned
|
||||
// to shim.Error otherwise the value returned from the named function is returned as shim.Success (formatted by
|
||||
// the transaction serializer). If an unknown name is passed as part of the first arg a shim.Error is returned.
|
||||
// If a valid name is passed but the function name is unknown then the contract with that name's
|
||||
// unknown function is called and its value returned as success or error depending on its return. If no
|
||||
// unknown function is defined for the contract then shim.Error is returned by Invoke. In the case of
|
||||
// unknown function names being passed (and the unknown handler returns an error) or the named function
|
||||
// returning an error then the after function if defined is not called. If the named function or unknown
|
||||
// function handler returns a non-error type then then the after transaction is sent this value. The same
|
||||
// transaction context is passed as a pointer to before, after, named and unknown functions on each Invoke.
|
||||
// If no contract name is passed then the default contract is used.
|
||||
func (cc *ContractChaincode) Invoke(stub shim.ChaincodeStubInterface) *peer.Response {
|
||||
|
||||
ns, fn, params := cc.getNamespaceFunctionAndParams(stub)
|
||||
|
||||
nsContract, ok := cc.contracts[ns]
|
||||
if !ok {
|
||||
return shim.Error(fmt.Sprintf("Contract not found with name %s", ns))
|
||||
}
|
||||
|
||||
if fn == "" {
|
||||
return shim.Error("Blank function name passed")
|
||||
}
|
||||
|
||||
ctx := reflect.New(nsContract.transactionContextHandler)
|
||||
ctxIface := ctx.Interface().(SettableTransactionContextInterface)
|
||||
ctxIface.SetStub(stub)
|
||||
|
||||
ci, _ := cid.New(stub)
|
||||
ctxIface.SetClientIdentity(ci)
|
||||
|
||||
beforeTransaction := nsContract.beforeTransaction
|
||||
|
||||
if beforeTransaction != nil {
|
||||
_, _, errRes := beforeTransaction.Call(ctx, nil, nil)
|
||||
|
||||
if errRes != nil {
|
||||
return shim.Error(errRes.Error())
|
||||
}
|
||||
}
|
||||
|
||||
var successReturn string
|
||||
var successIFace interface{}
|
||||
var errorReturn error
|
||||
|
||||
serializer := cc.TransactionSerializer
|
||||
|
||||
if contractFn, ok := nsContract.functions[toFirstRuneUpperCase(fn)]; !ok {
|
||||
unknownTransaction := nsContract.unknownTransaction
|
||||
if unknownTransaction == nil {
|
||||
return shim.Error(fmt.Sprintf("Function %s not found in contract %s", fn, ns))
|
||||
}
|
||||
|
||||
successReturn, successIFace, errorReturn = unknownTransaction.Call(ctx, nil, serializer)
|
||||
} else {
|
||||
var transactionSchema *metadata.TransactionMetadata
|
||||
|
||||
for i, v := range cc.metadata.Contracts[ns].Transactions {
|
||||
if v.Name == fn {
|
||||
transactionSchema = &cc.metadata.Contracts[ns].Transactions[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
successReturn, successIFace, errorReturn = contractFn.Call(ctx, transactionSchema, &cc.metadata.Components, serializer, params...)
|
||||
}
|
||||
|
||||
if errorReturn != nil {
|
||||
return shim.Error(errorReturn.Error())
|
||||
}
|
||||
|
||||
afterTransaction := nsContract.afterTransaction
|
||||
|
||||
if afterTransaction != nil {
|
||||
_, _, errRes := afterTransaction.Call(ctx, successIFace, nil)
|
||||
|
||||
if errRes != nil {
|
||||
return shim.Error(errRes.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return shim.Success([]byte(successReturn))
|
||||
}
|
||||
|
||||
func (cc *ContractChaincode) getNamespaceFunctionAndParams(stub shim.ChaincodeStubInterface) (string, string, []string) {
|
||||
nsFn, params := stub.GetFunctionAndParameters()
|
||||
|
||||
nsIndex := strings.LastIndex(nsFn, ":")
|
||||
|
||||
if nsIndex == -1 {
|
||||
return cc.DefaultContract, nsFn, params
|
||||
}
|
||||
|
||||
return nsFn[:nsIndex], nsFn[nsIndex+1:], params
|
||||
}
|
||||
|
||||
func (cc *ContractChaincode) addContract(contract ContractInterface, excludeFuncs []string) error {
|
||||
ns := contract.GetName()
|
||||
|
||||
if ns == "" {
|
||||
ns = reflect.TypeOf(contract).Elem().Name()
|
||||
}
|
||||
|
||||
if _, ok := cc.contracts[ns]; ok {
|
||||
return fmt.Errorf("multiple contracts being merged into chaincode with name %s", ns)
|
||||
}
|
||||
|
||||
ccn := contractChaincodeContract{}
|
||||
ccn.transactionContextHandler = reflect.ValueOf(contract.GetTransactionContextHandler()).Elem().Type()
|
||||
transactionContextPtrHandler := reflect.ValueOf(contract.GetTransactionContextHandler()).Type()
|
||||
ccn.functions = make(map[string]*internal.ContractFunction)
|
||||
ccn.info = contract.GetInfo()
|
||||
|
||||
if ccn.info.Version == "" {
|
||||
ccn.info.Version = "latest"
|
||||
}
|
||||
|
||||
if ccn.info.Title == "" {
|
||||
ccn.info.Title = ns
|
||||
}
|
||||
|
||||
contractType := reflect.PtrTo(reflect.TypeOf(contract).Elem())
|
||||
contractValue := reflect.ValueOf(contract).Elem().Addr()
|
||||
|
||||
ut := contract.GetUnknownTransaction()
|
||||
|
||||
if ut != nil {
|
||||
var err error
|
||||
ccn.unknownTransaction, err = internal.NewTransactionHandler(ut, transactionContextPtrHandler, internal.TransactionHandlerTypeUnknown)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
bt := contract.GetBeforeTransaction()
|
||||
|
||||
if bt != nil {
|
||||
var err error
|
||||
ccn.beforeTransaction, err = internal.NewTransactionHandler(bt, transactionContextPtrHandler, internal.TransactionHandlerTypeBefore)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
at := contract.GetAfterTransaction()
|
||||
|
||||
if at != nil {
|
||||
var err error
|
||||
ccn.afterTransaction, err = internal.NewTransactionHandler(at, transactionContextPtrHandler, internal.TransactionHandlerTypeAfter)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
evaluateMethods := []string{}
|
||||
|
||||
if eci, ok := contract.(EvaluationContractInterface); ok {
|
||||
evaluateMethods = eci.GetEvaluateTransactions()
|
||||
}
|
||||
|
||||
for i := 0; i < contractType.NumMethod(); i++ {
|
||||
typeMethod := contractType.Method(i)
|
||||
valueMethod := contractValue.Method(i)
|
||||
|
||||
if !utils.StringInSlice(typeMethod.Name, excludeFuncs) {
|
||||
var err error
|
||||
|
||||
var callType internal.CallType = internal.CallTypeSubmit
|
||||
|
||||
if utils.StringInSlice(typeMethod.Name, evaluateMethods) {
|
||||
callType = internal.CallTypeEvaluate
|
||||
}
|
||||
|
||||
ccn.functions[typeMethod.Name], err = internal.NewContractFunctionFromReflect(typeMethod, valueMethod, callType, transactionContextPtrHandler)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(ccn.functions) == 0 {
|
||||
return fmt.Errorf("contracts are required to have at least 1 (non-ignored) public method. Contract %s has none. Method names that have been ignored: %s", ns, utils.SliceAsCommaSentence(excludeFuncs))
|
||||
}
|
||||
|
||||
cc.contracts[ns] = ccn
|
||||
|
||||
if cc.DefaultContract == "" {
|
||||
cc.DefaultContract = ns
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc *ContractChaincode) reflectMetadata() metadata.ContractChaincodeMetadata {
|
||||
reflectedMetadata := metadata.ContractChaincodeMetadata{}
|
||||
reflectedMetadata.Contracts = make(map[string]metadata.ContractMetadata)
|
||||
reflectedMetadata.Components.Schemas = make(map[string]metadata.ObjectMetadata)
|
||||
reflectedMetadata.Info = &cc.Info
|
||||
|
||||
if cc.Info.Version == "" {
|
||||
reflectedMetadata.Info.Version = "latest"
|
||||
}
|
||||
|
||||
if cc.Info.Title == "" {
|
||||
reflectedMetadata.Info.Title = "undefined"
|
||||
}
|
||||
|
||||
for key, contract := range cc.contracts {
|
||||
contractMetadata := metadata.ContractMetadata{}
|
||||
contractMetadata.Name = key
|
||||
infoCopy := contract.info
|
||||
contractMetadata.Info = &infoCopy
|
||||
|
||||
if cc.DefaultContract == key {
|
||||
contractMetadata.Default = true
|
||||
}
|
||||
|
||||
for key, fn := range contract.functions {
|
||||
fnMetadata := fn.ReflectMetadata(key, &reflectedMetadata.Components)
|
||||
|
||||
contractMetadata.Transactions = append(contractMetadata.Transactions, fnMetadata)
|
||||
}
|
||||
|
||||
sort.Slice(contractMetadata.Transactions, func(i, j int) bool {
|
||||
return contractMetadata.Transactions[i].Name < contractMetadata.Transactions[j].Name
|
||||
})
|
||||
|
||||
reflectedMetadata.Contracts[key] = contractMetadata
|
||||
}
|
||||
|
||||
return reflectedMetadata
|
||||
}
|
||||
|
||||
func (cc *ContractChaincode) augmentMetadata() error {
|
||||
fileMetadata, err := metadata.ReadMetadataFile()
|
||||
|
||||
if err != nil && !strings.Contains(err.Error(), "failed to read metadata from file") {
|
||||
return err
|
||||
}
|
||||
|
||||
reflectedMetadata := cc.reflectMetadata()
|
||||
|
||||
fileMetadata.Append(reflectedMetadata)
|
||||
err = fileMetadata.CompileSchemas()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = metadata.ValidateAgainstSchema(fileMetadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cc.metadata = fileMetadata
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCiMethods() []string {
|
||||
contractInterfaceType := reflect.TypeOf((*ContractInterface)(nil)).Elem()
|
||||
ignoreContractInterfaceType := reflect.TypeOf((*IgnoreContractInterface)(nil)).Elem()
|
||||
evaluateContractInterfaceType := reflect.TypeOf((*EvaluationContractInterface)(nil)).Elem()
|
||||
|
||||
interfaceTypes := []reflect.Type{contractInterfaceType, ignoreContractInterfaceType, evaluateContractInterfaceType}
|
||||
|
||||
var ciMethods []string
|
||||
for _, interfaceType := range interfaceTypes {
|
||||
for i := 0; i < interfaceType.NumMethod(); i++ {
|
||||
ciMethods = append(ciMethods, interfaceType.Method(i).Name)
|
||||
}
|
||||
}
|
||||
|
||||
return ciMethods
|
||||
}
|
||||
|
||||
func loadChaincodeServerConfig() (*shim.ChaincodeServer, error) {
|
||||
address := getStringEnv(serverAddressVariable, "")
|
||||
ccid := getStringEnv(chaincodeIdVariable, "")
|
||||
|
||||
if address == "" || ccid == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
tlsProps, err := loadTLSProperties()
|
||||
if err != nil {
|
||||
log.Panicf("error creating getting TLS properties: %v", err)
|
||||
}
|
||||
|
||||
server := &shim.ChaincodeServer{
|
||||
CCID: ccid,
|
||||
Address: address,
|
||||
TLSProps: *tlsProps,
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func loadTLSProperties() (*shim.TLSProperties, error) {
|
||||
tlsEnabled := getBoolEnv(tlsEnabledVariable, false)
|
||||
if !tlsEnabled {
|
||||
return &shim.TLSProperties{Disabled: true}, nil
|
||||
}
|
||||
|
||||
key := getStringEnv(clientKeyVariable, "")
|
||||
cert := getStringEnv(clientCertVariable, "")
|
||||
root := getStringEnv(rootCertVariable, "")
|
||||
|
||||
var keyBytes, certBytes, rootBytes []byte
|
||||
var err error
|
||||
|
||||
keyBytes, err = os.ReadFile(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while reading the crypto file: %s", err)
|
||||
}
|
||||
|
||||
certBytes, err = os.ReadFile(cert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while reading the crypto file: %s", err)
|
||||
}
|
||||
|
||||
if root != "" {
|
||||
rootBytes, err = os.ReadFile(root)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while reading the crypto file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &shim.TLSProperties{
|
||||
Disabled: false,
|
||||
Key: keyBytes,
|
||||
Cert: certBytes,
|
||||
ClientCACerts: rootBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getStringEnv(key, defaultVal string) string {
|
||||
value, ok := os.LookupEnv(key)
|
||||
if !ok {
|
||||
value = defaultVal
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func getBoolEnv(key string, defaultVal bool) bool {
|
||||
value, err := strconv.ParseBool(os.Getenv(key))
|
||||
if err != nil {
|
||||
return defaultVal
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func toFirstRuneUpperCase(text string) string {
|
||||
if len(text) == 0 {
|
||||
return text
|
||||
}
|
||||
|
||||
runes := []rune(text)
|
||||
|
||||
if unicode.IsUpper(runes[0]) {
|
||||
return text
|
||||
}
|
||||
|
||||
runes[0] = unicode.ToUpper(runes[0])
|
||||
return string(runes)
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package contractapi
|
||||
|
||||
// SystemContract contract added to all chaincode to provide access to metdata
|
||||
type SystemContract struct {
|
||||
Contract
|
||||
metadata string
|
||||
}
|
||||
|
||||
func (sc *SystemContract) setMetadata(metadata string) {
|
||||
sc.metadata = metadata
|
||||
}
|
||||
|
||||
// GetMetadata returns JSON formatted metadata of chaincode
|
||||
// the system contract is part of. This metadata is composed
|
||||
// of reflected metadata combined with the metadata file
|
||||
// if used
|
||||
func (sc *SystemContract) GetMetadata() string {
|
||||
return sc.metadata
|
||||
}
|
||||
|
||||
// GetEvaluateTransactions returns the transactions that
|
||||
// exist in system contract which should be marked as
|
||||
// evaluate transaction in the metadata. I.e. should be called
|
||||
// by query transaction
|
||||
func (sc *SystemContract) GetEvaluateTransactions() []string {
|
||||
return []string{"GetMetadata"}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package contractapi
|
||||
|
||||
import (
|
||||
"github.com/hyperledger/fabric-chaincode-go/v2/pkg/cid"
|
||||
"github.com/hyperledger/fabric-chaincode-go/v2/shim"
|
||||
)
|
||||
|
||||
// TransactionContextInterface defines the interface which TransactionContext
|
||||
// meets. This can be taken by transacton functions on a contract which has not set
|
||||
// a custom transaction context to allow transaction functions to take an interface
|
||||
// to simplify unit testing.
|
||||
type TransactionContextInterface interface {
|
||||
// GetStub should provide a way to access the stub set by Init/Invoke
|
||||
GetStub() shim.ChaincodeStubInterface
|
||||
// GetClientIdentity should provide a way to access the client identity set by Init/Invoke
|
||||
GetClientIdentity() cid.ClientIdentity
|
||||
}
|
||||
|
||||
// SettableTransactionContextInterface defines functions a valid transaction context
|
||||
// should have. Transaction context's set for contracts to be used in chaincode
|
||||
// must implement this interface.
|
||||
type SettableTransactionContextInterface interface {
|
||||
// SetStub should provide a way to pass the stub from a chaincode transaction
|
||||
// call to the transaction context so that it can be used by contract functions.
|
||||
// This is called by Init/Invoke with the stub passed.
|
||||
SetStub(shim.ChaincodeStubInterface)
|
||||
// SetClientIdentity should provide a way to pass the client identity from a chaincode
|
||||
// transaction call to the transaction context so that it can be used by contract functions.
|
||||
// This is called by Init/Invoke with the stub passed.
|
||||
SetClientIdentity(ci cid.ClientIdentity)
|
||||
}
|
||||
|
||||
// TransactionContext is a basic transaction context to be used in contracts,
|
||||
// containing minimal required functionality use in contracts as part of
|
||||
// chaincode. Provides access to the stub and clientIdentity of a transaction.
|
||||
// If a contract implements the ContractInterface using the Contract struct then
|
||||
// this is the default transaction context that will be used.
|
||||
type TransactionContext struct {
|
||||
stub shim.ChaincodeStubInterface
|
||||
clientIdentity cid.ClientIdentity
|
||||
}
|
||||
|
||||
// SetStub stores the passed stub in the transaction context
|
||||
func (ctx *TransactionContext) SetStub(stub shim.ChaincodeStubInterface) {
|
||||
ctx.stub = stub
|
||||
}
|
||||
|
||||
// SetClientIdentity stores the passed stub in the transaction context
|
||||
func (ctx *TransactionContext) SetClientIdentity(ci cid.ClientIdentity) {
|
||||
ctx.clientIdentity = ci
|
||||
}
|
||||
|
||||
// GetStub returns the current set stub
|
||||
func (ctx *TransactionContext) GetStub() shim.ChaincodeStubInterface {
|
||||
return ctx.stub
|
||||
}
|
||||
|
||||
// GetClientIdentity returns the current set client identity
|
||||
func (ctx *TransactionContext) GetClientIdentity() cid.ClientIdentity {
|
||||
return ctx.clientIdentity
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package utils
|
||||
|
||||
// UndefinedInterface the type of nil passed to an after transaction when
|
||||
// the contract function called as part of the transaction does not specify
|
||||
// a success return type or its return type is interface{} and value nil
|
||||
type UndefinedInterface struct{}
|
||||
@ -0,0 +1,375 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/internal/types"
|
||||
metadata "github.com/hyperledger/fabric-contract-api-go/v2/metadata"
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/serializer"
|
||||
)
|
||||
|
||||
type contractFunctionParams struct {
|
||||
context reflect.Type
|
||||
fields []reflect.Type
|
||||
}
|
||||
|
||||
type contractFunctionReturns struct {
|
||||
success reflect.Type
|
||||
error bool
|
||||
}
|
||||
|
||||
// CallType enum for type of call that should be used for method submit vs evaluate
|
||||
type CallType int
|
||||
|
||||
const (
|
||||
// CallTypeNA contract function isnt callabale by invoke/query
|
||||
CallTypeNA = iota
|
||||
// CallTypeSubmit contract function should be called by invoke
|
||||
CallTypeSubmit
|
||||
// CallTypeEvaluate contract function should be called by query
|
||||
CallTypeEvaluate
|
||||
)
|
||||
|
||||
// ContractFunction contains a description of a function so that it can be called by a chaincode
|
||||
type ContractFunction struct {
|
||||
function reflect.Value
|
||||
callType CallType
|
||||
params contractFunctionParams
|
||||
returns contractFunctionReturns
|
||||
}
|
||||
|
||||
// Call calls function in a contract using string args and handles formatting the response into useful types
|
||||
func (cf ContractFunction) Call(ctx reflect.Value, supplementaryMetadata *metadata.TransactionMetadata, components *metadata.ComponentMetadata, serializer serializer.TransactionSerializer, params ...string) (string, interface{}, error) {
|
||||
var parameterMetadata []metadata.ParameterMetadata
|
||||
if supplementaryMetadata != nil {
|
||||
parameterMetadata = supplementaryMetadata.Parameters
|
||||
}
|
||||
|
||||
values, err := cf.formatArgs(ctx, parameterMetadata, components, params, serializer)
|
||||
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
someResp := cf.function.Call(values)
|
||||
|
||||
var returnsMetadata *metadata.ReturnMetadata
|
||||
if supplementaryMetadata != nil {
|
||||
returnsMetadata = &supplementaryMetadata.Returns
|
||||
}
|
||||
|
||||
return cf.handleResponse(someResp, returnsMetadata, components, serializer)
|
||||
}
|
||||
|
||||
// ReflectMetadata returns the metadata for contract function
|
||||
func (cf ContractFunction) ReflectMetadata(name string, existingComponents *metadata.ComponentMetadata) metadata.TransactionMetadata {
|
||||
transactionMetadata := metadata.TransactionMetadata{}
|
||||
transactionMetadata.Name = name
|
||||
transactionMetadata.Tag = []string{}
|
||||
|
||||
txType := "SUBMIT"
|
||||
txTypeDeprecated := "submit"
|
||||
|
||||
if cf.callType == CallTypeEvaluate {
|
||||
txType = "EVALUATE"
|
||||
txTypeDeprecated = "evaluate"
|
||||
}
|
||||
|
||||
transactionMetadata.Tag = append(transactionMetadata.Tag, txTypeDeprecated)
|
||||
transactionMetadata.Tag = append(transactionMetadata.Tag, txType)
|
||||
|
||||
for index, field := range cf.params.fields {
|
||||
schema, _ := metadata.GetSchema(field, existingComponents)
|
||||
|
||||
param := metadata.ParameterMetadata{}
|
||||
param.Name = fmt.Sprintf("param%d", index)
|
||||
param.Schema = schema
|
||||
|
||||
transactionMetadata.Parameters = append(transactionMetadata.Parameters, param)
|
||||
}
|
||||
|
||||
if cf.returns.success != nil {
|
||||
schema, _ := metadata.GetSchema(cf.returns.success, existingComponents)
|
||||
|
||||
transactionMetadata.Returns = metadata.ReturnMetadata{Schema: schema}
|
||||
}
|
||||
|
||||
return transactionMetadata
|
||||
}
|
||||
|
||||
type formatArgResult struct {
|
||||
paramName string
|
||||
converted reflect.Value
|
||||
err error
|
||||
}
|
||||
|
||||
func (cf *ContractFunction) formatArgs(ctx reflect.Value, supplementaryMetadata []metadata.ParameterMetadata, components *metadata.ComponentMetadata, params []string, serializer serializer.TransactionSerializer) ([]reflect.Value, error) {
|
||||
numParams := len(cf.params.fields)
|
||||
|
||||
if supplementaryMetadata != nil {
|
||||
if len(supplementaryMetadata) != numParams {
|
||||
return nil, fmt.Errorf("incorrect number of params in supplementary metadata. Expected %d, received %d", numParams, len(supplementaryMetadata))
|
||||
}
|
||||
}
|
||||
|
||||
values := []reflect.Value{}
|
||||
|
||||
if cf.params.context != nil {
|
||||
values = append(values, ctx)
|
||||
}
|
||||
|
||||
if len(params) < numParams {
|
||||
return nil, fmt.Errorf("incorrect number of params. Expected %d, received %d", numParams, len(params))
|
||||
}
|
||||
|
||||
channels := []chan formatArgResult{}
|
||||
|
||||
for i := 0; i < numParams; i++ {
|
||||
|
||||
fieldType := cf.params.fields[i]
|
||||
|
||||
var paramMetadata *metadata.ParameterMetadata
|
||||
|
||||
if supplementaryMetadata != nil {
|
||||
paramMetadata = &supplementaryMetadata[i]
|
||||
}
|
||||
|
||||
c := make(chan formatArgResult)
|
||||
go func(i int) {
|
||||
defer close(c)
|
||||
c <- cf.formatArg(params[i], fieldType, paramMetadata, components, serializer)
|
||||
}(i)
|
||||
channels = append(channels, c)
|
||||
}
|
||||
|
||||
for _, channel := range channels {
|
||||
for res := range channel {
|
||||
|
||||
if res.err != nil {
|
||||
return nil, fmt.Errorf("error managing parameter%s. %s", res.paramName, res.err.Error())
|
||||
}
|
||||
|
||||
values = append(values, res.converted)
|
||||
}
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func (cf *ContractFunction) formatArg(param string, fieldType reflect.Type, parameterMetadata *metadata.ParameterMetadata, components *metadata.ComponentMetadata, serializer serializer.TransactionSerializer) formatArgResult {
|
||||
converted, err := serializer.FromString(param, fieldType, parameterMetadata, components)
|
||||
|
||||
var paramName string
|
||||
|
||||
if parameterMetadata != nil {
|
||||
paramName = " " + parameterMetadata.Name
|
||||
}
|
||||
|
||||
return formatArgResult{
|
||||
paramName: paramName,
|
||||
converted: converted,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (cf *ContractFunction) handleResponse(response []reflect.Value, returnsMetadata *metadata.ReturnMetadata, components *metadata.ComponentMetadata, serializer serializer.TransactionSerializer) (string, interface{}, error) {
|
||||
expectedLength := 0
|
||||
|
||||
returnsSuccess := cf.returns.success != nil
|
||||
|
||||
if returnsSuccess {
|
||||
expectedLength++
|
||||
}
|
||||
if cf.returns.error {
|
||||
expectedLength++
|
||||
}
|
||||
|
||||
if len(response) != expectedLength {
|
||||
return "", nil, errors.New("response does not match expected return for given function")
|
||||
}
|
||||
|
||||
var successResponse reflect.Value
|
||||
var errorResponse reflect.Value
|
||||
|
||||
if returnsSuccess {
|
||||
successResponse = response[0]
|
||||
}
|
||||
if cf.returns.error {
|
||||
errorResponse = response[len(response)-1]
|
||||
}
|
||||
|
||||
var successString string
|
||||
var errorError error
|
||||
var iface interface{}
|
||||
|
||||
if successResponse.IsValid() {
|
||||
if serializer != nil {
|
||||
var err error
|
||||
successString, err = serializer.ToString(successResponse, cf.returns.success, returnsMetadata, components)
|
||||
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("error handling success response. %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
iface = successResponse.Interface()
|
||||
}
|
||||
|
||||
if errorResponse.IsValid() && !errorResponse.IsNil() {
|
||||
errorError = errorResponse.Interface().(error)
|
||||
}
|
||||
|
||||
return successString, iface, errorError
|
||||
}
|
||||
|
||||
func newContractFunction(fnValue reflect.Value, callType CallType, paramDetails contractFunctionParams, returnDetails contractFunctionReturns) *ContractFunction {
|
||||
cf := ContractFunction{}
|
||||
cf.callType = callType
|
||||
cf.function = fnValue
|
||||
cf.params = paramDetails
|
||||
cf.returns = returnDetails
|
||||
|
||||
return &cf
|
||||
}
|
||||
|
||||
// NewContractFunctionFromFunc creates a new contract function from a given function
|
||||
func NewContractFunctionFromFunc(fn interface{}, callType CallType, contextHandlerType reflect.Type) (*ContractFunction, error) {
|
||||
fnType := reflect.TypeOf(fn)
|
||||
fnValue := reflect.ValueOf(fn)
|
||||
|
||||
if fnType.Kind() != reflect.Func {
|
||||
return nil, fmt.Errorf("cannot create new contract function from %s. Can only use func", fnType.Kind())
|
||||
}
|
||||
|
||||
myMethod := reflect.Method{}
|
||||
myMethod.Func = fnValue
|
||||
myMethod.Type = fnType
|
||||
|
||||
paramDetails, returnDetails, err := parseMethod(myMethod, contextHandlerType)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newContractFunction(fnValue, callType, paramDetails, returnDetails), nil
|
||||
}
|
||||
|
||||
// NewContractFunctionFromReflect creates a new contract function from a reflected method
|
||||
func NewContractFunctionFromReflect(typeMethod reflect.Method, valueMethod reflect.Value, callType CallType, contextHandlerType reflect.Type) (*ContractFunction, error) {
|
||||
paramDetails, returnDetails, err := parseMethod(typeMethod, contextHandlerType)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newContractFunction(valueMethod, callType, paramDetails, returnDetails), nil
|
||||
}
|
||||
|
||||
// Setup
|
||||
|
||||
func parseMethod(typeMethod reflect.Method, contextHandlerType reflect.Type) (contractFunctionParams, contractFunctionReturns, error) {
|
||||
myContractFnParams, err := methodToContractFunctionParams(typeMethod, contextHandlerType)
|
||||
|
||||
if err != nil {
|
||||
return contractFunctionParams{}, contractFunctionReturns{}, err
|
||||
}
|
||||
|
||||
myContractFnReturns, err := methodToContractFunctionReturns(typeMethod)
|
||||
|
||||
if err != nil {
|
||||
return contractFunctionParams{}, contractFunctionReturns{}, err
|
||||
}
|
||||
|
||||
return myContractFnParams, myContractFnReturns, nil
|
||||
}
|
||||
|
||||
func methodToContractFunctionParams(typeMethod reflect.Method, contextHandlerType reflect.Type) (contractFunctionParams, error) {
|
||||
myContractFnParams := contractFunctionParams{}
|
||||
|
||||
usesCtx := (reflect.Type)(nil)
|
||||
|
||||
numIn := typeMethod.Type.NumIn()
|
||||
|
||||
startIndex := 1
|
||||
methodName := typeMethod.Name
|
||||
|
||||
if methodName == "" {
|
||||
startIndex = 0
|
||||
methodName = "Function"
|
||||
}
|
||||
|
||||
for i := startIndex; i < numIn; i++ {
|
||||
inType := typeMethod.Type.In(i)
|
||||
|
||||
typeError := typeIsValid(inType, nil, false)
|
||||
|
||||
isCtx := inType == contextHandlerType
|
||||
|
||||
if typeError != nil && !isCtx && i == startIndex && inType.Kind() == reflect.Interface {
|
||||
invalidInterfaceTypeErr := fmt.Sprintf("%s contains invalid transaction context interface type. Set transaction context for contract does not meet interface used in method.", methodName)
|
||||
|
||||
err := typeMatchesInterface(contextHandlerType, inType)
|
||||
|
||||
if err != nil {
|
||||
return contractFunctionParams{}, fmt.Errorf("%s %s", invalidInterfaceTypeErr, err.Error())
|
||||
}
|
||||
|
||||
isCtx = true
|
||||
}
|
||||
|
||||
if typeError != nil && !isCtx {
|
||||
return contractFunctionParams{}, fmt.Errorf("%s contains invalid parameter type. %s", methodName, typeError.Error())
|
||||
} else if i != startIndex && isCtx {
|
||||
return contractFunctionParams{}, fmt.Errorf("functions requiring the TransactionContext must require it as the first parameter. %s takes it in as parameter %d", methodName, i-startIndex)
|
||||
} else if isCtx {
|
||||
usesCtx = contextHandlerType
|
||||
} else {
|
||||
myContractFnParams.fields = append(myContractFnParams.fields, inType)
|
||||
}
|
||||
}
|
||||
|
||||
myContractFnParams.context = usesCtx
|
||||
return myContractFnParams, nil
|
||||
}
|
||||
|
||||
func methodToContractFunctionReturns(typeMethod reflect.Method) (contractFunctionReturns, error) {
|
||||
numOut := typeMethod.Type.NumOut()
|
||||
|
||||
methodName := typeMethod.Name
|
||||
|
||||
if methodName == "" {
|
||||
methodName = "Function"
|
||||
}
|
||||
|
||||
if numOut > 2 {
|
||||
return contractFunctionReturns{}, fmt.Errorf("functions may only return a maximum of two values. %s returns %d", methodName, numOut)
|
||||
} else if numOut == 1 {
|
||||
outType := typeMethod.Type.Out(0)
|
||||
|
||||
typeError := typeIsValid(outType, nil, true)
|
||||
|
||||
if typeError != nil {
|
||||
return contractFunctionReturns{}, fmt.Errorf("%s contains invalid single return type. %s", methodName, typeError.Error())
|
||||
} else if outType == types.ErrorType {
|
||||
return contractFunctionReturns{nil, true}, nil
|
||||
}
|
||||
return contractFunctionReturns{outType, false}, nil
|
||||
} else if numOut == 2 {
|
||||
firstOut := typeMethod.Type.Out(0)
|
||||
secondOut := typeMethod.Type.Out(1)
|
||||
|
||||
firstTypeError := typeIsValid(firstOut, nil, true)
|
||||
if firstTypeError != nil && firstOut != types.ErrorType {
|
||||
return contractFunctionReturns{}, fmt.Errorf("%s contains invalid first return type. %s", methodName, firstTypeError.Error())
|
||||
} else if secondOut.String() != "error" {
|
||||
return contractFunctionReturns{}, fmt.Errorf("%s contains invalid second return type. Type %s is not valid. Expected error", methodName, secondOut.String())
|
||||
}
|
||||
return contractFunctionReturns{firstOut, true}, nil
|
||||
}
|
||||
return contractFunctionReturns{nil, false}, nil
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi/utils"
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/serializer"
|
||||
)
|
||||
|
||||
// TransactionHandlerType enum for type of transaction handled
|
||||
type TransactionHandlerType int
|
||||
|
||||
const (
|
||||
// TransactionHandlerTypeBefore before transaction type
|
||||
TransactionHandlerTypeBefore TransactionHandlerType = iota + 1
|
||||
// TransactionHandlerTypeUnknown before transaction type
|
||||
TransactionHandlerTypeUnknown
|
||||
// TransactionHandlerTypeAfter before transaction type
|
||||
TransactionHandlerTypeAfter
|
||||
)
|
||||
|
||||
func (tht TransactionHandlerType) String() (string, error) {
|
||||
switch tht {
|
||||
case TransactionHandlerTypeBefore:
|
||||
return "Before", nil
|
||||
case TransactionHandlerTypeAfter:
|
||||
return "After", nil
|
||||
case TransactionHandlerTypeUnknown:
|
||||
return "Unknown", nil
|
||||
default:
|
||||
return "", errors.New("invalid transaction handler type")
|
||||
}
|
||||
}
|
||||
|
||||
// TransactionHandler extension of contract function that manages function which handles calls
|
||||
// to before, after and unknown transaction functions
|
||||
type TransactionHandler struct {
|
||||
ContractFunction
|
||||
handlesType TransactionHandlerType
|
||||
}
|
||||
|
||||
// Call calls tranaction function using string args and handles formatting the response into useful types
|
||||
func (th TransactionHandler) Call(ctx reflect.Value, data interface{}, serializer serializer.TransactionSerializer) (string, interface{}, error) {
|
||||
values := []reflect.Value{}
|
||||
|
||||
if th.params.context != nil {
|
||||
values = append(values, ctx)
|
||||
}
|
||||
|
||||
if th.handlesType == TransactionHandlerTypeAfter && len(th.params.fields) == 1 {
|
||||
if data == nil {
|
||||
values = append(values, reflect.Zero(reflect.TypeOf(new(utils.UndefinedInterface))))
|
||||
} else {
|
||||
values = append(values, reflect.ValueOf(data))
|
||||
}
|
||||
}
|
||||
|
||||
someResp := th.function.Call(values)
|
||||
|
||||
return th.handleResponse(someResp, nil, nil, serializer)
|
||||
}
|
||||
|
||||
// NewTransactionHandler create a new transaction handler from a given function
|
||||
func NewTransactionHandler(fn interface{}, contextHandlerType reflect.Type, handlesType TransactionHandlerType) (*TransactionHandler, error) {
|
||||
cf, err := NewContractFunctionFromFunc(fn, 0, contextHandlerType)
|
||||
|
||||
if err != nil {
|
||||
str, _ := handlesType.String()
|
||||
return nil, fmt.Errorf("error creating %s. %s", str, err.Error())
|
||||
} else if handlesType != TransactionHandlerTypeAfter && len(cf.params.fields) > 0 {
|
||||
str, _ := handlesType.String()
|
||||
return nil, fmt.Errorf("%s transactions may not take any params other than the transaction context", str)
|
||||
} else if handlesType == TransactionHandlerTypeAfter && len(cf.params.fields) > 1 {
|
||||
return nil, errors.New("after transactions must take at most one non-context param")
|
||||
} else if handlesType == TransactionHandlerTypeAfter && len(cf.params.fields) == 1 && cf.params.fields[0].Kind() != reflect.Interface {
|
||||
return nil, errors.New("after transaction must take type interface{} as their only non-context param")
|
||||
}
|
||||
|
||||
th := TransactionHandler{
|
||||
*cf,
|
||||
handlesType,
|
||||
}
|
||||
|
||||
return &th, nil
|
||||
}
|
||||
@ -0,0 +1,366 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
)
|
||||
|
||||
type basicType interface {
|
||||
Convert(string) (reflect.Value, error)
|
||||
GetSchema() *spec.Schema
|
||||
}
|
||||
|
||||
type stringType struct{}
|
||||
|
||||
func (st *stringType) Convert(value string) (reflect.Value, error) {
|
||||
return reflect.ValueOf(value), nil
|
||||
}
|
||||
|
||||
func (st *stringType) GetSchema() *spec.Schema {
|
||||
return spec.StringProperty()
|
||||
}
|
||||
|
||||
type boolType struct{}
|
||||
|
||||
func (bt *boolType) Convert(value string) (reflect.Value, error) {
|
||||
var boolVal bool
|
||||
var err error
|
||||
if value != "" {
|
||||
boolVal, err = strconv.ParseBool(value)
|
||||
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("cannot convert passed value %s to bool", value)
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.ValueOf(boolVal), nil
|
||||
}
|
||||
|
||||
func (bt *boolType) GetSchema() *spec.Schema {
|
||||
return spec.BooleanProperty()
|
||||
}
|
||||
|
||||
type intType struct{}
|
||||
|
||||
func (it *intType) Convert(value string) (reflect.Value, error) {
|
||||
var intVal int
|
||||
var err error
|
||||
if value != "" {
|
||||
intVal, err = strconv.Atoi(value)
|
||||
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("cannot convert passed value %s to int", value)
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.ValueOf(intVal), nil
|
||||
}
|
||||
|
||||
func (it *intType) GetSchema() *spec.Schema {
|
||||
return spec.Int64Property()
|
||||
}
|
||||
|
||||
type int8Type struct{}
|
||||
|
||||
func (it *int8Type) Convert(value string) (reflect.Value, error) {
|
||||
var intVal int8
|
||||
if value != "" {
|
||||
int64val, err := strconv.ParseInt(value, 10, 8)
|
||||
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("cannot convert passed value %s to int8", value)
|
||||
}
|
||||
|
||||
intVal = int8(int64val)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(intVal), nil
|
||||
}
|
||||
|
||||
func (it *int8Type) GetSchema() *spec.Schema {
|
||||
return spec.Int8Property()
|
||||
}
|
||||
|
||||
type int16Type struct{}
|
||||
|
||||
func (it *int16Type) Convert(value string) (reflect.Value, error) {
|
||||
var intVal int16
|
||||
if value != "" {
|
||||
int64val, err := strconv.ParseInt(value, 10, 16)
|
||||
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("cannot convert passed value %s to int16", value)
|
||||
}
|
||||
|
||||
intVal = int16(int64val)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(intVal), nil
|
||||
}
|
||||
|
||||
func (it *int16Type) GetSchema() *spec.Schema {
|
||||
return spec.Int16Property()
|
||||
}
|
||||
|
||||
type int32Type struct{}
|
||||
|
||||
func (it *int32Type) Convert(value string) (reflect.Value, error) {
|
||||
var intVal int32
|
||||
if value != "" {
|
||||
int64val, err := strconv.ParseInt(value, 10, 32)
|
||||
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("cannot convert passed value %s to int32", value)
|
||||
}
|
||||
|
||||
intVal = int32(int64val)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(intVal), nil
|
||||
}
|
||||
|
||||
func (it *int32Type) GetSchema() *spec.Schema {
|
||||
return spec.Int32Property()
|
||||
}
|
||||
|
||||
type int64Type struct{}
|
||||
|
||||
func (it *int64Type) Convert(value string) (reflect.Value, error) {
|
||||
var intVal int64
|
||||
var err error
|
||||
if value != "" {
|
||||
intVal, err = strconv.ParseInt(value, 10, 64)
|
||||
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("cannot convert passed value %s to int64", value)
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.ValueOf(intVal), nil
|
||||
}
|
||||
|
||||
func (it *int64Type) GetSchema() *spec.Schema {
|
||||
return spec.Int64Property()
|
||||
}
|
||||
|
||||
type uintType struct{}
|
||||
|
||||
func (ut *uintType) Convert(value string) (reflect.Value, error) {
|
||||
var uintVal uint
|
||||
if value != "" {
|
||||
uint64Val, err := strconv.ParseUint(value, 10, 64)
|
||||
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("cannot convert passed value %s to uint", value)
|
||||
}
|
||||
|
||||
uintVal = uint(uint64Val)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uintVal), nil
|
||||
}
|
||||
|
||||
func (ut *uintType) GetSchema() *spec.Schema {
|
||||
schema := spec.Float64Property()
|
||||
|
||||
multOf := float64(1)
|
||||
schema.MultipleOf = &multOf
|
||||
minimum := float64(0)
|
||||
schema.Minimum = &minimum
|
||||
maximum := float64(math.MaxUint64)
|
||||
schema.Maximum = &maximum
|
||||
return schema
|
||||
}
|
||||
|
||||
type uint8Type struct{}
|
||||
|
||||
func (ut *uint8Type) Convert(value string) (reflect.Value, error) {
|
||||
var uintVal uint8
|
||||
if value != "" {
|
||||
uint64Val, err := strconv.ParseUint(value, 10, 8)
|
||||
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("cannot convert passed value %s to uint8", value)
|
||||
}
|
||||
|
||||
uintVal = uint8(uint64Val)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uintVal), nil
|
||||
}
|
||||
|
||||
func (ut *uint8Type) GetSchema() *spec.Schema {
|
||||
schema := spec.Int32Property()
|
||||
minimum := float64(0)
|
||||
schema.Minimum = &minimum
|
||||
maximum := float64(math.MaxUint8)
|
||||
schema.Maximum = &maximum
|
||||
return schema
|
||||
}
|
||||
|
||||
type uint16Type struct{}
|
||||
|
||||
func (ut *uint16Type) Convert(value string) (reflect.Value, error) {
|
||||
var uintVal uint16
|
||||
if value != "" {
|
||||
uint64Val, err := strconv.ParseUint(value, 10, 16)
|
||||
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("cannot convert passed value %s to uint16", value)
|
||||
}
|
||||
|
||||
uintVal = uint16(uint64Val)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uintVal), nil
|
||||
}
|
||||
|
||||
func (ut *uint16Type) GetSchema() *spec.Schema {
|
||||
schema := spec.Int64Property()
|
||||
minimum := float64(0)
|
||||
schema.Minimum = &minimum
|
||||
maximum := float64(math.MaxUint16)
|
||||
schema.Maximum = &maximum
|
||||
return schema
|
||||
}
|
||||
|
||||
type uint32Type struct{}
|
||||
|
||||
func (ut *uint32Type) Convert(value string) (reflect.Value, error) {
|
||||
var uintVal uint32
|
||||
if value != "" {
|
||||
uint64Val, err := strconv.ParseUint(value, 10, 32)
|
||||
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("cannot convert passed value %s to uint32", value)
|
||||
}
|
||||
|
||||
uintVal = uint32(uint64Val)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uintVal), nil
|
||||
}
|
||||
|
||||
func (ut *uint32Type) GetSchema() *spec.Schema {
|
||||
schema := spec.Int64Property()
|
||||
minimum := float64(0)
|
||||
schema.Minimum = &minimum
|
||||
maximum := float64(4294967295)
|
||||
schema.Maximum = &maximum
|
||||
return schema
|
||||
}
|
||||
|
||||
type uint64Type struct{}
|
||||
|
||||
func (ut *uint64Type) Convert(value string) (reflect.Value, error) {
|
||||
var uintVal uint64
|
||||
var err error
|
||||
if value != "" {
|
||||
uintVal, err = strconv.ParseUint(value, 10, 64)
|
||||
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("cannot convert passed value %s to uint64", value)
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uintVal), nil
|
||||
}
|
||||
|
||||
func (ut *uint64Type) GetSchema() *spec.Schema {
|
||||
schema := spec.Float64Property()
|
||||
multOf := float64(1)
|
||||
schema.MultipleOf = &multOf
|
||||
minimum := float64(0)
|
||||
schema.Minimum = &minimum
|
||||
maximum := float64(18446744073709551615)
|
||||
schema.Maximum = &maximum
|
||||
return schema
|
||||
}
|
||||
|
||||
type float32Type struct{}
|
||||
|
||||
func (ft *float32Type) Convert(value string) (reflect.Value, error) {
|
||||
var floatVal float32
|
||||
if value != "" {
|
||||
float64Val, err := strconv.ParseFloat(value, 32)
|
||||
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("cannot convert passed value %s to float32", value)
|
||||
}
|
||||
|
||||
floatVal = float32(float64Val)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(floatVal), nil
|
||||
}
|
||||
|
||||
func (ft *float32Type) GetSchema() *spec.Schema {
|
||||
return spec.Float32Property()
|
||||
}
|
||||
|
||||
type float64Type struct{}
|
||||
|
||||
func (ft *float64Type) Convert(value string) (reflect.Value, error) {
|
||||
var floatVal float64
|
||||
var err error
|
||||
if value != "" {
|
||||
floatVal, err = strconv.ParseFloat(value, 64)
|
||||
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("cannot convert passed value %s to float64", value)
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.ValueOf(floatVal), nil
|
||||
}
|
||||
|
||||
func (ft *float64Type) GetSchema() *spec.Schema {
|
||||
return spec.Float64Property()
|
||||
}
|
||||
|
||||
type interfaceType struct{}
|
||||
|
||||
func (st *interfaceType) Convert(value string) (reflect.Value, error) {
|
||||
return reflect.ValueOf(value), nil
|
||||
}
|
||||
|
||||
func (st *interfaceType) GetSchema() *spec.Schema {
|
||||
return new(spec.Schema)
|
||||
}
|
||||
|
||||
// BasicTypes the base types usable in the contract api
|
||||
var BasicTypes = map[reflect.Kind]basicType{
|
||||
reflect.Bool: new(boolType),
|
||||
reflect.Float32: new(float32Type),
|
||||
reflect.Float64: new(float64Type),
|
||||
reflect.Int: new(intType),
|
||||
reflect.Int8: new(int8Type),
|
||||
reflect.Int16: new(int16Type),
|
||||
reflect.Int32: new(int32Type),
|
||||
reflect.Int64: new(int64Type),
|
||||
reflect.String: new(stringType),
|
||||
reflect.Uint: new(uintType),
|
||||
reflect.Uint8: new(uint8Type),
|
||||
reflect.Uint16: new(uint16Type),
|
||||
reflect.Uint32: new(uint32Type),
|
||||
reflect.Uint64: new(uint64Type),
|
||||
reflect.Interface: new(interfaceType),
|
||||
}
|
||||
|
||||
func IsBytes(t reflect.Type) bool {
|
||||
return (t.Kind() == reflect.Array || t.Kind() == reflect.Slice) && t.Elem().Kind() == reflect.Uint8
|
||||
}
|
||||
|
||||
// ErrorType reflect type for errors
|
||||
var ErrorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
// TimeType reflect type for time
|
||||
var TimeType = reflect.TypeOf(time.Time{})
|
||||
@ -0,0 +1,157 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"unicode"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/internal/types"
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/internal/utils"
|
||||
)
|
||||
|
||||
func basicTypesAsSlice() []string {
|
||||
typesArr := []string{}
|
||||
|
||||
for el := range types.BasicTypes {
|
||||
typesArr = append(typesArr, el.String())
|
||||
}
|
||||
sort.Strings(typesArr)
|
||||
|
||||
return typesArr
|
||||
}
|
||||
|
||||
func listBasicTypes() string {
|
||||
return utils.SliceAsCommaSentence(basicTypesAsSlice())
|
||||
}
|
||||
|
||||
func arrayOfValidType(array reflect.Value, additionalTypes []reflect.Type) error {
|
||||
if array.Len() < 1 {
|
||||
return errors.New("arrays must have length greater than 0")
|
||||
}
|
||||
|
||||
return typeIsValid(array.Index(0).Type(), additionalTypes, false)
|
||||
}
|
||||
|
||||
func structOfValidType(obj reflect.Type, additionalTypes []reflect.Type) error {
|
||||
if obj.Kind() == reflect.Ptr {
|
||||
obj = obj.Elem()
|
||||
}
|
||||
|
||||
for i := 0; i < obj.NumField(); i++ {
|
||||
field := obj.Field(i)
|
||||
|
||||
if runes := []rune(field.Name); len(runes) > 0 && !unicode.IsUpper(runes[0]) && field.Tag.Get("metadata") == "" {
|
||||
// Skip validation for private fields, except those tagged as metadata
|
||||
continue
|
||||
}
|
||||
|
||||
err := typeIsValid(field.Type, additionalTypes, false)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func typeInSlice(a reflect.Type, list []reflect.Type) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func typeIsValid(t reflect.Type, additionalTypes []reflect.Type, allowError bool) error {
|
||||
kind := t.Kind()
|
||||
if kind == reflect.Array {
|
||||
array := reflect.New(t).Elem()
|
||||
return arrayOfValidType(array, additionalTypes)
|
||||
} else if kind == reflect.Slice {
|
||||
slice := reflect.MakeSlice(t, 1, 1)
|
||||
return typeIsValid(slice.Index(0).Type(), additionalTypes, false)
|
||||
} else if kind == reflect.Map {
|
||||
if t.Key().Kind() != reflect.String {
|
||||
return fmt.Errorf("map key type %s is not valid. Expected string", t.Key().String())
|
||||
}
|
||||
|
||||
return typeIsValid(t.Elem(), additionalTypes, false)
|
||||
} else if !typeInSlice(t, additionalTypes) {
|
||||
if kind == reflect.Struct {
|
||||
additionalTypes = append(additionalTypes, t)
|
||||
additionalTypes = append(additionalTypes, reflect.PointerTo(t))
|
||||
// add self for cyclic
|
||||
return structOfValidType(t, additionalTypes)
|
||||
} else if kind == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
|
||||
additionalTypes = append(additionalTypes, t)
|
||||
additionalTypes = append(additionalTypes, t.Elem())
|
||||
// add self for cyclic
|
||||
return structOfValidType(t, additionalTypes)
|
||||
} else if _, ok := types.BasicTypes[t.Kind()]; !ok || (!allowError && t == types.ErrorType) || (t.Kind() == reflect.Interface && t.String() != "interface {}" && t.String() != "error") {
|
||||
errStr := ""
|
||||
|
||||
if allowError {
|
||||
errStr = " error,"
|
||||
}
|
||||
|
||||
return fmt.Errorf("type %s is not valid. Expected a struct or one of the basic types%s %s or an array/slice of these", t.String(), errStr, listBasicTypes())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func typeMatchesInterface(toMatch reflect.Type, iface reflect.Type) error {
|
||||
if iface.Kind() != reflect.Interface {
|
||||
return errors.New("type passed for interface is not an interface")
|
||||
}
|
||||
|
||||
for i := 0; i < iface.NumMethod(); i++ {
|
||||
ifaceMethod := iface.Method(i)
|
||||
matchMethod, exists := toMatch.MethodByName(ifaceMethod.Name)
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("missing function %s", ifaceMethod.Name)
|
||||
}
|
||||
|
||||
ifaceNumIn := ifaceMethod.Type.NumIn()
|
||||
matchNumIn := matchMethod.Type.NumIn() - 1 // skip over which the function is acting on
|
||||
|
||||
if ifaceNumIn != matchNumIn {
|
||||
return fmt.Errorf("parameter mismatch in method %s. Expected %d, got %d", ifaceMethod.Name, ifaceNumIn, matchNumIn)
|
||||
}
|
||||
|
||||
for j := 0; j < ifaceNumIn; j++ {
|
||||
ifaceIn := ifaceMethod.Type.In(j)
|
||||
matchIn := matchMethod.Type.In(j + 1)
|
||||
|
||||
if ifaceIn.Kind() != matchIn.Kind() {
|
||||
return fmt.Errorf("parameter mismatch in method %s at parameter %d. Expected %s, got %s", ifaceMethod.Name, j, ifaceIn.Name(), matchIn.Name())
|
||||
}
|
||||
}
|
||||
|
||||
ifaceNumOut := ifaceMethod.Type.NumOut()
|
||||
matchNumOut := matchMethod.Type.NumOut()
|
||||
if ifaceNumOut != matchNumOut {
|
||||
return fmt.Errorf("return mismatch in method %s. Expected %d, got %d", ifaceMethod.Name, ifaceNumOut, matchNumOut)
|
||||
}
|
||||
|
||||
for j := 0; j < ifaceNumOut; j++ {
|
||||
ifaceOut := ifaceMethod.Type.Out(j)
|
||||
matchOut := matchMethod.Type.Out(j)
|
||||
|
||||
if ifaceOut.Kind() != matchOut.Kind() {
|
||||
return fmt.Errorf("return mismatch in method %s at return %d. Expected %s, got %s", ifaceMethod.Name, j, ifaceOut.Name(), matchOut.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
)
|
||||
|
||||
// ValidateErrorsToString converts errors from JSON schema output into readable string
|
||||
func ValidateErrorsToString(resErrors []gojsonschema.ResultError) string {
|
||||
toReturn := ""
|
||||
|
||||
sort.Slice(resErrors[:], func(i, j int) bool {
|
||||
return resErrors[i].String() < resErrors[j].String()
|
||||
})
|
||||
|
||||
for i, v := range resErrors {
|
||||
toReturn += strconv.Itoa(i+1) + ". " + v.String() + "\n"
|
||||
}
|
||||
|
||||
return strings.Trim(toReturn, "\n")
|
||||
}
|
||||
|
||||
// StringInSlice returns whether string exists in string slice
|
||||
func StringInSlice(a string, list []string) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SliceAsCommaSentence returns string slice as comma separated sentence
|
||||
func SliceAsCommaSentence(slice []string) string {
|
||||
return strings.Replace(strings.Join(slice, " and "), " and ", ", ", len(slice)-2)
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
os "os"
|
||||
"path"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// For testing!
|
||||
type ioutilInterface interface {
|
||||
ReadFile(string) ([]byte, error)
|
||||
}
|
||||
|
||||
type ioutilFront struct{}
|
||||
|
||||
func (i ioutilFront) ReadFile(filename string) ([]byte, error) {
|
||||
return os.ReadFile(filename)
|
||||
}
|
||||
|
||||
var ioutilAbs ioutilInterface = ioutilFront{}
|
||||
|
||||
func readLocalFile(localPath string) ([]byte, error) {
|
||||
_, filename, _, _ := runtime.Caller(1)
|
||||
|
||||
schemaPath := path.Join(path.Dir(filename), localPath)
|
||||
|
||||
file, err := ioutilAbs.ReadFile(schemaPath)
|
||||
|
||||
return file, err
|
||||
}
|
||||
@ -0,0 +1,294 @@
|
||||
// Copyright the Hyperledger Fabric contributors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
os "os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/hyperledger/fabric-contract-api-go/v2/internal/utils"
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
)
|
||||
|
||||
// MetadataFolder name of the main folder metadata should be placed in
|
||||
const MetadataFolder = "META-INF"
|
||||
|
||||
// MetadataFolderSecondary name of the secondary folder metadata should be placed in
|
||||
const MetadataFolderSecondary = "contract-metadata"
|
||||
|
||||
// MetadataFile name of file metadata should be written in
|
||||
const MetadataFile = "metadata.json"
|
||||
|
||||
// Helpers for testing
|
||||
type osInterface interface {
|
||||
Executable() (string, error)
|
||||
Stat(string) (os.FileInfo, error)
|
||||
IsNotExist(error) bool
|
||||
}
|
||||
|
||||
type osFront struct{}
|
||||
|
||||
func (o osFront) Executable() (string, error) {
|
||||
return os.Executable()
|
||||
}
|
||||
|
||||
func (o osFront) Stat(name string) (os.FileInfo, error) {
|
||||
return os.Stat(name)
|
||||
}
|
||||
|
||||
func (o osFront) IsNotExist(err error) bool {
|
||||
return os.IsNotExist(err)
|
||||
}
|
||||
|
||||
var osAbs osInterface = osFront{}
|
||||
|
||||
//go:embed schema/schema.json
|
||||
var contractSchemaJson []byte
|
||||
|
||||
// GetJSONSchema returns the JSON schema used for metadata
|
||||
func GetJSONSchema() []byte {
|
||||
return contractSchemaJson
|
||||
}
|
||||
|
||||
// ParameterMetadata details about a parameter used for a transaction.
|
||||
type ParameterMetadata struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Schema *spec.Schema `json:"schema"`
|
||||
CompiledSchema *gojsonschema.Schema `json:"-"`
|
||||
}
|
||||
|
||||
// ReturnMetadata details about the return type for a transaction
|
||||
type ReturnMetadata struct {
|
||||
Schema *spec.Schema
|
||||
CompiledSchema *gojsonschema.Schema
|
||||
}
|
||||
|
||||
// TransactionMetadata contains information on what makes up a transaction
|
||||
// When JSON serialized the Returns object is flattened to contain the schema
|
||||
type TransactionMetadata struct {
|
||||
Parameters []ParameterMetadata `json:"parameters,omitempty"`
|
||||
Returns ReturnMetadata `json:"-"`
|
||||
Tag []string `json:"tag,omitempty"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type tmAlias TransactionMetadata
|
||||
type jsonTransactionMetadata struct {
|
||||
*tmAlias
|
||||
ReturnsSchema *spec.Schema `json:"returns,omitempty"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON handles converting JSON to TransactionMetadata since returns is flattened
|
||||
// in swagger
|
||||
func (tm *TransactionMetadata) UnmarshalJSON(data []byte) error {
|
||||
jtm := jsonTransactionMetadata{tmAlias: (*tmAlias)(tm)}
|
||||
|
||||
err := json.Unmarshal(data, &jtm)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tm.Returns = ReturnMetadata{}
|
||||
tm.Returns.Schema = jtm.ReturnsSchema
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON handles converting TransactionMetadata to JSON since returns is flattened
|
||||
// in swagger
|
||||
func (tm *TransactionMetadata) MarshalJSON() ([]byte, error) {
|
||||
jtm := jsonTransactionMetadata{tmAlias: (*tmAlias)(tm), ReturnsSchema: tm.Returns.Schema}
|
||||
|
||||
return json.Marshal(&jtm)
|
||||
}
|
||||
|
||||
// ContactMetadata contains contact details about an author of a contract/chaincode
|
||||
type ContactMetadata struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
// LicenseMetadata contains licensing information for contract/chaincode
|
||||
type LicenseMetadata struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// InfoMetadata contains additional information to clarify use of contract/chaincode
|
||||
type InfoMetadata struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Contact *ContactMetadata `json:"contact,omitempty"`
|
||||
License *LicenseMetadata `json:"license,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// ContractMetadata contains information about what makes up a contract
|
||||
type ContractMetadata struct {
|
||||
Info *InfoMetadata `json:"info,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Transactions []TransactionMetadata `json:"transactions"`
|
||||
Default bool `json:"default"`
|
||||
}
|
||||
|
||||
// ObjectMetadata description of a component
|
||||
type ObjectMetadata struct {
|
||||
ID string `json:"$id"`
|
||||
Properties map[string]spec.Schema `json:"properties"`
|
||||
Required []string `json:"required,omitempty"`
|
||||
AdditionalProperties bool `json:"additionalProperties"`
|
||||
}
|
||||
|
||||
// ComponentMetadata stores map of schemas of all components
|
||||
type ComponentMetadata struct {
|
||||
Schemas map[string]ObjectMetadata `json:"schemas,omitempty"`
|
||||
}
|
||||
|
||||
// ContractChaincodeMetadata describes a chaincode made using the contract api
|
||||
type ContractChaincodeMetadata struct {
|
||||
Info *InfoMetadata `json:"info,omitempty"`
|
||||
Contracts map[string]ContractMetadata `json:"contracts"`
|
||||
Components ComponentMetadata `json:"components"`
|
||||
}
|
||||
|
||||
// Append merge two sets of metadata. Source value will override the original
|
||||
// values only in fields that are not yet set i.e. when info nil, contracts nil or
|
||||
// zero length array, components empty.
|
||||
func (ccm *ContractChaincodeMetadata) Append(source ContractChaincodeMetadata) {
|
||||
if ccm.Info == nil {
|
||||
ccm.Info = source.Info
|
||||
}
|
||||
|
||||
if len(ccm.Contracts) == 0 {
|
||||
if ccm.Contracts == nil {
|
||||
ccm.Contracts = make(map[string]ContractMetadata)
|
||||
}
|
||||
|
||||
for key, value := range source.Contracts {
|
||||
ccm.Contracts[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ccm.Components, ComponentMetadata{}) {
|
||||
ccm.Components = source.Components
|
||||
}
|
||||
}
|
||||
|
||||
// CompileSchemas compile parameter and return schemas for use by gojsonschema.
|
||||
// When validating against the compiled schema you will need to make the
|
||||
// comparison json have a key of the parameter name for parameters or
|
||||
// return for return values e.g {"param1": "value"}. Compilation process
|
||||
// resolves references to components
|
||||
func (ccm *ContractChaincodeMetadata) CompileSchemas() error {
|
||||
compileSchema := func(propName string, schema *spec.Schema, components ComponentMetadata) (*gojsonschema.Schema, error) {
|
||||
combined := make(map[string]interface{})
|
||||
combined["components"] = components
|
||||
combined["properties"] = make(map[string]interface{})
|
||||
combined["properties"].(map[string]interface{})[propName] = schema
|
||||
|
||||
combinedLoader := gojsonschema.NewGoLoader(combined)
|
||||
|
||||
return gojsonschema.NewSchema(combinedLoader)
|
||||
}
|
||||
|
||||
for contractName, contract := range ccm.Contracts {
|
||||
for txIdx, tx := range contract.Transactions {
|
||||
for paramIdx, param := range tx.Parameters {
|
||||
gjsSchema, err := compileSchema(param.Name, param.Schema, ccm.Components)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error compiling schema for %s [%s]. %s schema invalid. %s", contractName, tx.Name, param.Name, err.Error())
|
||||
}
|
||||
|
||||
param.CompiledSchema = gjsSchema
|
||||
tx.Parameters[paramIdx] = param
|
||||
}
|
||||
|
||||
if tx.Returns.Schema != nil {
|
||||
gjsSchema, err := compileSchema("return", tx.Returns.Schema, ccm.Components)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error compiling schema for %s [%s]. Return schema invalid. %s", contractName, tx.Name, err.Error())
|
||||
}
|
||||
|
||||
tx.Returns.CompiledSchema = gjsSchema
|
||||
}
|
||||
|
||||
contract.Transactions[txIdx] = tx
|
||||
}
|
||||
ccm.Contracts[contractName] = contract
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadMetadataFile return the contents of metadata file as ContractChaincodeMetadata
|
||||
func ReadMetadataFile() (ContractChaincodeMetadata, error) {
|
||||
|
||||
fileMetadata := ContractChaincodeMetadata{}
|
||||
|
||||
ex, execErr := osAbs.Executable()
|
||||
if execErr != nil {
|
||||
return ContractChaincodeMetadata{}, fmt.Errorf("failed to read metadata from file. Could not find location of executable. %s", execErr.Error())
|
||||
}
|
||||
exPath := filepath.Dir(ex)
|
||||
metadataPath := filepath.Join(exPath, MetadataFolder, MetadataFile)
|
||||
|
||||
_, err := osAbs.Stat(metadataPath)
|
||||
|
||||
if osAbs.IsNotExist(err) {
|
||||
metadataPath = filepath.Join(exPath, MetadataFolderSecondary, MetadataFile)
|
||||
_, err = osAbs.Stat(metadataPath)
|
||||
if osAbs.IsNotExist(err) {
|
||||
return ContractChaincodeMetadata{}, errors.New("failed to read metadata from file. Metadata file does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
fileMetadata.Contracts = make(map[string]ContractMetadata)
|
||||
|
||||
metadataBytes, err := ioutilAbs.ReadFile(metadataPath)
|
||||
|
||||
if err != nil {
|
||||
return ContractChaincodeMetadata{}, fmt.Errorf("failed to read metadata from file. Could not read file %s. %s", metadataPath, err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(metadataBytes, &fileMetadata)
|
||||
if err != nil {
|
||||
return ContractChaincodeMetadata{}, err
|
||||
}
|
||||
|
||||
return fileMetadata, nil
|
||||
}
|
||||
|
||||
// ValidateAgainstSchema takes a ContractChaincodeMetadata and runs it against the
|
||||
// schema that defines valid metadata structure. If it does not meet the schema it
|
||||
// returns an error detailing why
|
||||
func ValidateAgainstSchema(metadata ContractChaincodeMetadata) error {
|
||||
metadataBytes, _ := json.Marshal(metadata)
|
||||
|
||||
schemaLoader := gojsonschema.NewBytesLoader(GetJSONSchema())
|
||||
metadataLoader := gojsonschema.NewBytesLoader(metadataBytes)
|
||||
|
||||
schema, _ := gojsonschema.NewSchema(schemaLoader)
|
||||
|
||||
result, err := schema.Validate(metadataLoader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !result.Valid() {
|
||||
return fmt.Errorf("cannot use metadata. Metadata did not match schema:\n%s", utils.ValidateErrorsToString(result.Errors()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue