[chore] Bump go swagger (#2871)

* bump go swagger version

* bump swagger version
This commit is contained in:
tobi
2024-04-26 11:31:10 +02:00
committed by GitHub
parent 3a369d834a
commit fd8a724e77
251 changed files with 10841 additions and 11896 deletions

View File

@ -4,53 +4,58 @@ linters-settings:
golint:
min-confidence: 0
gocyclo:
min-complexity: 40
gocognit:
min-complexity: 40
min-complexity: 45
maligned:
suggest-new: true
dupl:
threshold: 150
threshold: 200
goconst:
min-len: 2
min-occurrences: 4
min-occurrences: 3
linters:
enable-all: true
disable:
- maligned
- unparam
- lll
- gochecknoglobals
- gochecknoinits
# scopelint is useful, but also reports false positives
# that unfortunately can't be disabled. So we disable the
# linter rather than changing code that works.
# see: https://github.com/kyoh86/scopelint/issues/4
- scopelint
- gochecknoglobals
- funlen
- godox
- gocognit
#- whitespace
- whitespace
- wsl
- funlen
- testpackage
- wrapcheck
#- nlreturn
- testpackage
- nlreturn
- gomnd
- goerr113
- exhaustivestruct
#- errorlint
#- nestif
- gofumpt
- goerr113
- errorlint
- nestif
- godot
- gci
- dogsled
- gofumpt
- paralleltest
- tparallel
- thelper
- ifshort
- forbidigo
- cyclop
- varnamelen
- exhaustruct
- varnamelen
- gci
- depguard
- errchkjson
- inamedparam
- nonamedreturns
- musttag
- ireturn
- forcetypeassert
- cyclop
# deprecated linters
- deadcode
- interfacer
- scopelint
- varcheck
- structcheck
- golint
- nosnakecase

View File

@ -1,8 +1,5 @@
# OpenAPI initiative analysis
# OpenAPI analysis [![Build Status](https://github.com/go-openapi/analysis/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/analysis/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/analysis/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/analysis)
[![Build Status](https://travis-ci.org/go-openapi/analysis.svg?branch=master)](https://travis-ci.org/go-openapi/analysis)
[![Build status](https://ci.appveyor.com/api/projects/status/x377t5o9ennm847o/branch/master?svg=true)](https://ci.appveyor.com/project/casualjim/go-openapi/analysis/branch/master)
[![codecov](https://codecov.io/gh/go-openapi/analysis/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/analysis)
[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/analysis/master/LICENSE)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/analysis.svg)](https://pkg.go.dev/github.com/go-openapi/analysis)
@ -13,12 +10,12 @@ A foundational library to analyze an OAI specification document for easier reaso
## What's inside?
* A analyzer providing methods to walk the functional content of a specification
* An analyzer providing methods to walk the functional content of a specification
* A spec flattener producing a self-contained document bundle, while preserving `$ref`s
* A spec merger ("mixin") to merge several spec documents into a primary spec
* A spec "fixer" ensuring that response descriptions are non empty
[Documentation](https://godoc.org/github.com/go-openapi/analysis)
[Documentation](https://pkg.go.dev/github.com/go-openapi/analysis)
## FAQ
@ -28,4 +25,3 @@ A foundational library to analyze an OAI specification document for easier reaso
> 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.
>

View File

@ -1,32 +0,0 @@
version: "0.1.{build}"
clone_folder: C:\go-openapi\analysis
shallow_clone: true # for startup speed
pull_requests:
do_not_increment_build_number: true
#skip_tags: true
#skip_branch_with_pr: true
# appveyor.yml
build: off
environment:
GOPATH: c:\gopath
stack: go 1.16
test_script:
- go test -v -timeout 20m ./...
deploy: off
notifications:
- provider: Slack
incoming_webhook: https://hooks.slack.com/services/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
auth_token:
secure: Sf7kZf7ZGbnwWUMpffHwMu5A0cHkLK2MYY32LNTPj4+/3qC3Ghl7+9v4TSLOqOlCwdRNjOGblAq7s+GDJed6/xgRQl1JtCi1klzZNrYX4q01pgTPvvGcwbBkIYgeMaPeIRcK9OZnud7sRXdttozgTOpytps2U6Js32ip7uj5mHSg2ub0FwoSJwlS6dbezZ8+eDhoha0F/guY99BEwx8Bd+zROrT2TFGsSGOFGN6wFc7moCqTHO/YkWib13a2QNXqOxCCVBy/lt76Wp+JkeFppjHlzs/2lP3EAk13RIUAaesdEUHvIHrzCyNJEd3/+KO2DzsWOYfpktd+KBCvgaYOsoo7ubdT3IROeAegZdCgo/6xgCEsmFc9ZcqCfN5yNx2A+BZ2Vwmpws+bQ1E1+B5HDzzaiLcYfG4X2O210QVGVDLWsv1jqD+uPYeHY2WRfh5ZsIUFvaqgUEnwHwrK44/8REAhQavt1QAj5uJpsRd7CkRVPWRNK+yIky+wgbVUFEchRNmS55E7QWf+W4+4QZkQi7vUTMc9nbTUu2Es9NfvfudOpM2wZbn98fjpb/qq/nRv6Bk+ca+7XD5/IgNLMbWp2ouDdzbiHLCOfDUiHiDJhLfFZx9Bwo7ZwfzeOlbrQX66bx7xRKYmOe4DLrXhNcpbsMa8qbfxlZRCmYbubB/Y8h4=
channel: bots
on_build_success: false
on_build_failure: true
on_build_status_changed: true

View File

@ -16,27 +16,27 @@
Package analysis provides methods to work with a Swagger specification document from
package go-openapi/spec.
Analyzing a specification
## Analyzing a specification
An analysed specification object (type Spec) provides methods to work with swagger definition.
Flattening or expanding a specification
## Flattening or expanding a specification
Flattening a specification bundles all remote $ref in the main spec document.
Depending on flattening options, additional preprocessing may take place:
- full flattening: replacing all inline complex constructs by a named entry in #/definitions
- expand: replace all $ref's in the document by their expanded content
Merging several specifications
## Merging several specifications
Mixin several specifications merges all Swagger constructs, and warns about found conflicts.
Fixing a specification
## Fixing a specification
Unmarshalling a specification with golang json unmarshalling may lead to
some unwanted result on present but empty fields.
Analyzing a Swagger schema
## Analyzing a Swagger schema
Swagger schemas are analyzed to determine their complexity and qualify their content.
*/

View File

@ -62,28 +62,26 @@ func newContext() *context {
//
// There is a minimal and a full flattening mode.
//
//
// Minimally flattening a spec means:
// - Expanding parameters, responses, path items, parameter items and header items (references to schemas are left
// unscathed)
// - Importing external (http, file) references so they become internal to the document
// - Moving every JSON pointer to a $ref to a named definition (i.e. the reworked spec does not contain pointers
// like "$ref": "#/definitions/myObject/allOfs/1")
// - Expanding parameters, responses, path items, parameter items and header items (references to schemas are left
// unscathed)
// - Importing external (http, file) references so they become internal to the document
// - Moving every JSON pointer to a $ref to a named definition (i.e. the reworked spec does not contain pointers
// like "$ref": "#/definitions/myObject/allOfs/1")
//
// A minimally flattened spec thus guarantees the following properties:
// - all $refs point to a local definition (i.e. '#/definitions/...')
// - definitions are unique
// - all $refs point to a local definition (i.e. '#/definitions/...')
// - definitions are unique
//
// NOTE: arbitrary JSON pointers (other than $refs to top level definitions) are rewritten as definitions if they
// represent a complex schema or express commonality in the spec.
// Otherwise, they are simply expanded.
// Self-referencing JSON pointers cannot resolve to a type and trigger an error.
//
//
// Minimal flattening is necessary and sufficient for codegen rendering using go-swagger.
//
// Fully flattening a spec means:
// - Moving every complex inline schema to be a definition with an auto-generated name in a depth-first fashion.
// - Moving every complex inline schema to be a definition with an auto-generated name in a depth-first fashion.
//
// By complex, we mean every JSON object with some properties.
// Arrays, when they do not define a tuple,
@ -93,22 +91,21 @@ func newContext() *context {
// have been created.
//
// Available flattening options:
// - Minimal: stops flattening after minimal $ref processing, leaving schema constructs untouched
// - Expand: expand all $ref's in the document (inoperant if Minimal set to true)
// - Verbose: croaks about name conflicts detected
// - RemoveUnused: removes unused parameters, responses and definitions after expansion/flattening
// - Minimal: stops flattening after minimal $ref processing, leaving schema constructs untouched
// - Expand: expand all $ref's in the document (inoperant if Minimal set to true)
// - Verbose: croaks about name conflicts detected
// - RemoveUnused: removes unused parameters, responses and definitions after expansion/flattening
//
// NOTE: expansion removes all $ref save circular $ref, which remain in place
//
// TODO: additional options
// - ProgagateNameExtensions: ensure that created entries properly follow naming rules when their parent have set a
// x-go-name extension
// - LiftAllOfs:
// - limit the flattening of allOf members when simple objects
// - merge allOf with validation only
// - merge allOf with extensions only
// - ...
//
// - ProgagateNameExtensions: ensure that created entries properly follow naming rules when their parent have set a
// x-go-name extension
// - LiftAllOfs:
// - limit the flattening of allOf members when simple objects
// - merge allOf with validation only
// - merge allOf with extensions only
// - ...
func Flatten(opts FlattenOpts) error {
debugLog("FlattenOpts: %#v", opts)
@ -270,6 +267,12 @@ func nameInlinedSchemas(opts *FlattenOpts) error {
}
func removeUnused(opts *FlattenOpts) {
for removeUnusedSinglePass(opts) {
// continue until no unused definition remains
}
}
func removeUnusedSinglePass(opts *FlattenOpts) (hasRemoved bool) {
expected := make(map[string]struct{})
for k := range opts.Swagger().Definitions {
expected[path.Join(definitionsPath, jsonpointer.Escape(k))] = struct{}{}
@ -280,6 +283,7 @@ func removeUnused(opts *FlattenOpts) {
}
for k := range expected {
hasRemoved = true
debugLog("removing unused definition %s", path.Base(k))
if opts.Verbose {
log.Printf("info: removing unused definition: %s", path.Base(k))
@ -288,6 +292,8 @@ func removeUnused(opts *FlattenOpts) {
}
opts.Spec.reload() // re-analyze
return hasRemoved
}
func importKnownRef(entry sortref.RefRevIdx, refStr, newName string, opts *FlattenOpts) error {
@ -334,7 +340,7 @@ func importNewRef(entry sortref.RefRevIdx, refStr string, opts *FlattenOpts) err
}
// generate a unique name - isOAIGen means that a naming conflict was resolved by changing the name
newName, isOAIGen = uniqifyName(opts.Swagger().Definitions, nameFromRef(entry.Ref))
newName, isOAIGen = uniqifyName(opts.Swagger().Definitions, nameFromRef(entry.Ref, opts))
debugLog("new name for [%s]: %s - with name conflict:%t", strings.Join(entry.Keys, ", "), newName, isOAIGen)
opts.flattenContext.resolved[refStr] = newName
@ -488,9 +494,9 @@ func stripPointersAndOAIGen(opts *FlattenOpts) error {
// stripOAIGen strips the spec from unnecessary OAIGen constructs, initially created to dedupe flattened definitions.
//
// A dedupe is deemed unnecessary whenever:
// - the only conflict is with its (single) parent: OAIGen is merged into its parent (reinlining)
// - there is a conflict with multiple parents: merge OAIGen in first parent, the rewrite other parents to point to
// the first parent.
// - the only conflict is with its (single) parent: OAIGen is merged into its parent (reinlining)
// - there is a conflict with multiple parents: merge OAIGen in first parent, the rewrite other parents to point to
// the first parent.
//
// This function returns true whenever it re-inlined a complex schema, so the caller may chose to iterate
// pointer and name resolution again.
@ -652,6 +658,7 @@ func namePointers(opts *FlattenOpts) error {
refsToReplace := make(map[string]SchemaRef, len(opts.Spec.references.schemas))
for k, ref := range opts.Spec.references.allRefs {
debugLog("name pointers: %q => %#v", k, ref)
if path.Dir(ref.String()) == definitionsPath {
// this a ref to a top-level definition: ok
continue
@ -769,6 +776,10 @@ func flattenAnonPointer(key string, v SchemaRef, refsToReplace map[string]Schema
// identifying edge case when the namer did nothing because we point to a non-schema object
// no definition is created and we expand the $ref for all callers
debugLog("decide what to do with the schema pointed to: asch.IsSimpleSchema=%t, len(callers)=%d, parts.IsSharedParam=%t, parts.IsSharedResponse=%t",
asch.IsSimpleSchema, len(callers), parts.IsSharedParam(), parts.IsSharedResponse(),
)
if (!asch.IsSimpleSchema || len(callers) > 1) && !parts.IsSharedParam() && !parts.IsSharedResponse() {
debugLog("replace JSON pointer at [%s] by definition: %s", key, v.Ref.String())
if err := namer.Name(v.Ref.String(), v.Schema, asch); err != nil {
@ -791,6 +802,7 @@ func flattenAnonPointer(key string, v SchemaRef, refsToReplace map[string]Schema
return nil
}
// everything that is a simple schema and not factorizable is expanded
debugLog("expand JSON pointer for key=%s", key)
if err := replace.UpdateRefWithSchema(opts.Swagger(), key, v.Schema); err != nil {

View File

@ -33,12 +33,14 @@ func (isn *InlineSchemaNamer) Name(key string, schema *spec.Schema, aschema *Ana
}
// create unique name
newName, isOAIGen := uniqifyName(isn.Spec.Definitions, swag.ToJSONName(name))
mangle := mangler(isn.opts)
newName, isOAIGen := uniqifyName(isn.Spec.Definitions, mangle(name))
// clone schema
sch := schutils.Clone(schema)
// replace values on schema
debugLog("rewriting schema to ref: key=%s with new name: %s", key, newName)
if err := replace.RewriteSchemaToRef(isn.Spec, key,
spec.MustCreateRef(path.Join(definitionsPath, newName))); err != nil {
return fmt.Errorf("error while creating definition %q from inline schema: %w", newName, err)
@ -149,13 +151,15 @@ func namesFromKey(parts sortref.SplitKey, aschema *AnalyzedSchema, operations ma
startIndex int
)
if parts.IsOperation() {
switch {
case parts.IsOperation():
baseNames, startIndex = namesForOperation(parts, operations)
}
// definitions
if parts.IsDefinition() {
case parts.IsDefinition():
baseNames, startIndex = namesForDefinition(parts)
default:
// this a non-standard pointer: build a name by concatenating its parts
baseNames = [][]string{parts}
startIndex = len(baseNames) + 1
}
result := make([]string, 0, len(baseNames))
@ -169,6 +173,7 @@ func namesFromKey(parts sortref.SplitKey, aschema *AnalyzedSchema, operations ma
}
sort.Strings(result)
debugLog("names from parts: %v => %v", parts, result)
return result
}
@ -256,10 +261,20 @@ func partAdder(aschema *AnalyzedSchema) sortref.PartAdder {
}
}
func nameFromRef(ref spec.Ref) string {
func mangler(o *FlattenOpts) func(string) string {
if o.KeepNames {
return func(in string) string { return in }
}
return swag.ToJSONName
}
func nameFromRef(ref spec.Ref, o *FlattenOpts) string {
mangle := mangler(o)
u := ref.GetURL()
if u.Fragment != "" {
return swag.ToJSONName(path.Base(u.Fragment))
return mangle(path.Base(u.Fragment))
}
if u.Path != "" {
@ -267,19 +282,19 @@ func nameFromRef(ref spec.Ref) string {
if bn != "" && bn != "/" {
ext := path.Ext(bn)
if ext != "" {
return swag.ToJSONName(bn[:len(bn)-len(ext)])
return mangle(bn[:len(bn)-len(ext)])
}
return swag.ToJSONName(bn)
return mangle(bn)
}
}
return swag.ToJSONName(strings.ReplaceAll(u.Host, ".", " "))
return mangle(strings.ReplaceAll(u.Host, ".", " "))
}
// GenLocation indicates from which section of the specification (models or operations) a definition has been created.
//
// This is reflected in the output spec with a "x-go-gen-location" extension. At the moment, this is is provided
// This is reflected in the output spec with a "x-go-gen-location" extension. At the moment, this is provided
// for information only.
func GenLocation(parts sortref.SplitKey) string {
switch {

View File

@ -26,6 +26,7 @@ type FlattenOpts struct {
Verbose bool // enable some reporting on possible name conflicts detected
RemoveUnused bool // When true, remove unused parameters, responses and definitions after expansion/flattening
ContinueOnError bool // Continue when spec expansion issues are found
KeepNames bool // Do not attempt to jsonify names from references when flattening
/* Extra keys */
_ struct{} // require keys

View File

@ -29,7 +29,7 @@ var (
// GetLogger provides a prefix debug logger
func GetLogger(prefix string, debug bool) func(string, ...interface{}) {
if debug {
logger := log.New(output, fmt.Sprintf("%s:", prefix), log.LstdFlags)
logger := log.New(output, prefix+":", log.LstdFlags)
return func(msg string, args ...interface{}) {
_, file1, pos1, _ := runtime.Caller(1)
@ -37,5 +37,5 @@ func GetLogger(prefix string, debug bool) func(string, ...interface{}) {
}
}
return func(msg string, args ...interface{}) {}
return func(_ string, _ ...interface{}) {}
}

View File

@ -1,6 +1,7 @@
package replace
import (
"encoding/json"
"fmt"
"net/url"
"os"
@ -40,6 +41,8 @@ func RewriteSchemaToRef(sp *spec.Swagger, key string, ref spec.Ref) error {
if refable.Schema != nil {
refable.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
}
case map[string]interface{}: // this happens e.g. if a schema points to an extension unmarshaled as map[string]interface{}
return rewriteParentRef(sp, key, ref)
default:
return fmt.Errorf("no schema with ref found at %s for %T", key, value)
}
@ -120,6 +123,9 @@ func rewriteParentRef(sp *spec.Swagger, key string, ref spec.Ref) error {
case spec.SchemaProperties:
container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
case *interface{}:
*container = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
// NOTE: can't have case *spec.SchemaOrBool = parent in this case is *Schema
default:
@ -318,8 +324,8 @@ type DeepestRefResult struct {
}
// DeepestRef finds the first definition ref, from a cascade of nested refs which are not definitions.
// - if no definition is found, returns the deepest ref.
// - pointers to external files are expanded
// - if no definition is found, returns the deepest ref.
// - pointers to external files are expanded
//
// NOTE: all external $ref's are assumed to be already expanded at this stage.
func DeepestRef(sp *spec.Swagger, opts *spec.ExpandOptions, ref spec.Ref) (*DeepestRefResult, error) {
@ -385,8 +391,9 @@ DOWNREF:
err := asSchema.UnmarshalJSON(asJSON)
if err != nil {
return nil,
fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T",
currentRef.String(), value)
fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T (%v)",
currentRef.String(), value, err,
)
}
warnings = append(warnings, fmt.Sprintf("found $ref %q (response) interpreted as schema", currentRef.String()))
@ -402,8 +409,9 @@ DOWNREF:
var asSchema spec.Schema
if err := asSchema.UnmarshalJSON(asJSON); err != nil {
return nil,
fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T",
currentRef.String(), value)
fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T (%v)",
currentRef.String(), value, err,
)
}
warnings = append(warnings, fmt.Sprintf("found $ref %q (parameter) interpreted as schema", currentRef.String()))
@ -414,9 +422,25 @@ DOWNREF:
currentRef = asSchema.Ref
default:
return nil,
fmt.Errorf("unhandled type to resolve JSON pointer %s. Expected a Schema, got: %T",
currentRef.String(), value)
// fallback: attempts to resolve the pointer as a schema
if refable == nil {
break DOWNREF
}
asJSON, _ := json.Marshal(refable)
var asSchema spec.Schema
if err := asSchema.UnmarshalJSON(asJSON); err != nil {
return nil,
fmt.Errorf("unhandled type to resolve JSON pointer %s. Expected a Schema, got: %T (%v)",
currentRef.String(), value, err,
)
}
warnings = append(warnings, fmt.Sprintf("found $ref %q (%T) interpreted as schema", currentRef.String(), refable))
if asSchema.Ref.String() == "" {
break DOWNREF
}
currentRef = asSchema.Ref
}
}

View File

@ -69,7 +69,7 @@ func KeyParts(key string) SplitKey {
return res
}
// SplitKey holds of the parts of a /-separated key, soi that their location may be determined.
// SplitKey holds of the parts of a /-separated key, so that their location may be determined.
type SplitKey []string
// IsDefinition is true when the split key is in the #/definitions section of a spec

View File

@ -53,7 +53,7 @@ import (
// collisions.
func Mixin(primary *spec.Swagger, mixins ...*spec.Swagger) []string {
skipped := make([]string, 0, len(mixins))
opIds := getOpIds(primary)
opIDs := getOpIDs(primary)
initPrimary(primary)
for i, m := range mixins {
@ -74,7 +74,7 @@ func Mixin(primary *spec.Swagger, mixins ...*spec.Swagger) []string {
skipped = append(skipped, mergeDefinitions(primary, m)...)
// merging paths requires a map of operationIDs to work with
skipped = append(skipped, mergePaths(primary, m, opIds, i)...)
skipped = append(skipped, mergePaths(primary, m, opIDs, i)...)
skipped = append(skipped, mergeParameters(primary, m)...)
@ -84,9 +84,9 @@ func Mixin(primary *spec.Swagger, mixins ...*spec.Swagger) []string {
return skipped
}
// getOpIds extracts all the paths.<path>.operationIds from the given
// getOpIDs extracts all the paths.<path>.operationIds from the given
// spec and returns them as the keys in a map with 'true' values.
func getOpIds(s *spec.Swagger) map[string]bool {
func getOpIDs(s *spec.Swagger) map[string]bool {
rv := make(map[string]bool)
if s.Paths == nil {
return rv
@ -179,7 +179,7 @@ func mergeDefinitions(primary *spec.Swagger, m *spec.Swagger) (skipped []string)
return
}
func mergePaths(primary *spec.Swagger, m *spec.Swagger, opIds map[string]bool, mixIndex int) (skipped []string) {
func mergePaths(primary *spec.Swagger, m *spec.Swagger, opIDs map[string]bool, mixIndex int) (skipped []string) {
if m.Paths != nil {
for k, v := range m.Paths.Paths {
if _, exists := primary.Paths.Paths[k]; exists {
@ -198,10 +198,10 @@ func mergePaths(primary *spec.Swagger, m *spec.Swagger, opIds map[string]bool, m
// all the proivded specs are already unique.
piops := pathItemOps(v)
for _, piop := range piops {
if opIds[piop.ID] {
if opIDs[piop.ID] {
piop.ID = fmt.Sprintf("%v%v%v", piop.ID, "Mixin", mixIndex)
}
opIds[piop.ID] = true
opIDs[piop.ID] = true
}
primary.Paths.Paths[k] = v
}
@ -367,7 +367,7 @@ func mergeSwaggerProps(primary *spec.Swagger, m *spec.Swagger) []string {
return skipped
}
// nolint: unparam
//nolint:unparam
func mergeExternalDocs(primary *spec.ExternalDocumentation, m *spec.ExternalDocumentation) []string {
if primary.Description == "" {
primary.Description = m.Description

View File

@ -1,7 +1,7 @@
package analysis
import (
"fmt"
"errors"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
@ -19,7 +19,7 @@ type SchemaOpts struct {
// patterns.
func Schema(opts SchemaOpts) (*AnalyzedSchema, error) {
if opts.Schema == nil {
return nil, fmt.Errorf("no schema to analyze")
return nil, errors.New("no schema to analyze")
}
a := &AnalyzedSchema{
@ -247,10 +247,10 @@ func (a *AnalyzedSchema) isArrayType() bool {
// isAnalyzedAsComplex determines if an analyzed schema is eligible to flattening (i.e. it is "complex").
//
// Complex means the schema is any of:
// - a simple type (primitive)
// - an array of something (items are possibly complex ; if this is the case, items will generate a definition)
// - a map of something (additionalProperties are possibly complex ; if this is the case, additionalProperties will
// generate a definition)
// - a simple type (primitive)
// - an array of something (items are possibly complex ; if this is the case, items will generate a definition)
// - a map of something (additionalProperties are possibly complex ; if this is the case, additionalProperties will
// generate a definition)
func (a *AnalyzedSchema) isAnalyzedAsComplex() bool {
return !a.IsSimpleSchema && !a.IsArray && !a.IsMap
}

View File

@ -4,45 +4,59 @@ linters-settings:
golint:
min-confidence: 0
gocyclo:
min-complexity: 30
min-complexity: 45
maligned:
suggest-new: true
dupl:
threshold: 100
threshold: 200
goconst:
min-len: 2
min-occurrences: 4
min-occurrences: 3
linters:
enable-all: true
disable:
- errname # this repo doesn't follow the convention advised by this linter
- maligned
- unparam
- lll
- gochecknoinits
- gochecknoglobals
- funlen
- godox
- gocognit
- whitespace
- wsl
- funlen
- gochecknoglobals
- gochecknoinits
- scopelint
- wrapcheck
- exhaustivestruct
- exhaustive
- nlreturn
- testpackage
- gci
- gofumpt
- goerr113
- nlreturn
- gomnd
- tparallel
- exhaustivestruct
- goerr113
- errorlint
- nestif
- godot
- errorlint
- gofumpt
- paralleltest
- tparallel
- cyclop
- errname
- varnamelen
- thelper
- ifshort
- exhaustruct
- maintidx
- varnamelen
- gci
- depguard
- errchkjson
- inamedparam
- nonamedreturns
- musttag
- ireturn
- forcetypeassert
- cyclop
# deprecated linters
- deadcode
- interfacer
- scopelint
- varcheck
- structcheck
- golint
- nosnakecase

View File

@ -1,11 +1,8 @@
# OpenAPI errors
# OpenAPI errors [![Build Status](https://github.com/go-openapi/errors/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/errors/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/errors/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/errors)
[![Build Status](https://travis-ci.org/go-openapi/errors.svg?branch=master)](https://travis-ci.org/go-openapi/errors)
[![codecov](https://codecov.io/gh/go-openapi/errors/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/errors)
[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/errors/master/LICENSE)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/errors.svg)](https://pkg.go.dev/github.com/go-openapi/errors)
[![GolangCI](https://golangci.com/badges/github.com/go-openapi/errors.svg)](https://golangci.com)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/errors)](https://goreportcard.com/report/github.com/go-openapi/errors)
Shared errors and error interface used throughout the various libraries found in the go-openapi toolkit.

View File

@ -55,9 +55,15 @@ func (a apiError) MarshalJSON() ([]byte, error) {
// New creates a new API error with a code and a message
func New(code int32, message string, args ...interface{}) Error {
if len(args) > 0 {
return &apiError{code, fmt.Sprintf(message, args...)}
return &apiError{
code: code,
message: fmt.Sprintf(message, args...),
}
}
return &apiError{
code: code,
message: message,
}
return &apiError{code, message}
}
// NotFound creates a new not found error
@ -130,10 +136,14 @@ func flattenComposite(errs *CompositeError) *CompositeError {
// MethodNotAllowed creates a new method not allowed error
func MethodNotAllowed(requested string, allow []string) Error {
msg := fmt.Sprintf("method %s is not allowed, but [%s] are", requested, strings.Join(allow, ","))
return &MethodNotAllowedError{code: http.StatusMethodNotAllowed, Allowed: allow, message: msg}
return &MethodNotAllowedError{
code: http.StatusMethodNotAllowed,
Allowed: allow,
message: msg,
}
}
// ServeError the error handler interface implementation
// ServeError implements the http error handler interface
func ServeError(rw http.ResponseWriter, r *http.Request, err error) {
rw.Header().Set("Content-Type", "application/json")
switch e := err.(type) {

View File

@ -120,6 +120,10 @@ func (c *CompositeError) Error() string {
return c.message
}
func (c *CompositeError) Unwrap() []error {
return c.Errors
}
// MarshalJSON implements the JSON encoding interface
func (c CompositeError) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
@ -133,7 +137,7 @@ func (c CompositeError) MarshalJSON() ([]byte, error) {
func CompositeValidationError(errors ...error) *CompositeError {
return &CompositeError{
code: CompositeErrorCode,
Errors: append([]error{}, errors...),
Errors: append(make([]error, 0, len(errors)), errors...),
message: "validation failure list",
}
}

5
vendor/github.com/go-openapi/inflect/.gitignore generated vendored Normal file
View File

@ -0,0 +1,5 @@
secrets.yml
coverage.out
coverage.txt
*.cov
.idea

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

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

View File

@ -1 +0,0 @@
swp$

View File

@ -1,168 +0,0 @@
INSTALLATION
go get bitbucket.org/pkg/inflect
PACKAGE
package inflect
FUNCTIONS
func AddAcronym(word string)
func AddHuman(suffix, replacement string)
func AddIrregular(singular, plural string)
func AddPlural(suffix, replacement string)
func AddSingular(suffix, replacement string)
func AddUncountable(word string)
func Asciify(word string) string
func Camelize(word string) string
func CamelizeDownFirst(word string) string
func Capitalize(word string) string
func Dasherize(word string) string
func ForeignKey(word string) string
func ForeignKeyCondensed(word string) string
func Humanize(word string) string
func Ordinalize(word string) string
func Parameterize(word string) string
func ParameterizeJoin(word, sep string) string
func Pluralize(word string) string
func Singularize(word string) string
func Tableize(word string) string
func Titleize(word string) string
func Typeify(word string) string
func Uncountables() map[string]bool
func Underscore(word string) string
TYPES
type Rule struct {
// contains filtered or unexported fields
}
used by rulesets
type Ruleset struct {
// contains filtered or unexported fields
}
a Ruleset is the config of pluralization rules
you can extend the rules with the Add* methods
func NewDefaultRuleset() *Ruleset
create a new ruleset and load it with the default
set of common English pluralization rules
func NewRuleset() *Ruleset
create a blank ruleset. Unless you are going to
build your own rules from scratch you probably
won't need this and can just use the defaultRuleset
via the global inflect.* methods
func (rs *Ruleset) AddAcronym(word string)
if you use acronym you may need to add them to the ruleset
to prevent Underscored words of things like "HTML" coming out
as "h_t_m_l"
func (rs *Ruleset) AddHuman(suffix, replacement string)
Human rules are applied by humanize to show more friendly
versions of words
func (rs *Ruleset) AddIrregular(singular, plural string)
Add any inconsistant pluralizing/sinularizing rules
to the set here.
func (rs *Ruleset) AddPlural(suffix, replacement string)
add a pluralization rule
func (rs *Ruleset) AddPluralExact(suffix, replacement string, exact bool)
add a pluralization rule with full string match
func (rs *Ruleset) AddSingular(suffix, replacement string)
add a singular rule
func (rs *Ruleset) AddSingularExact(suffix, replacement string, exact bool)
same as AddSingular but you can set `exact` to force
a full string match
func (rs *Ruleset) AddUncountable(word string)
add a word to this ruleset that has the same singular and plural form
for example: "rice"
func (rs *Ruleset) Asciify(word string) string
transforms latin characters like é -> e
func (rs *Ruleset) Camelize(word string) string
"dino_party" -> "DinoParty"
func (rs *Ruleset) CamelizeDownFirst(word string) string
same as Camelcase but with first letter downcased
func (rs *Ruleset) Capitalize(word string) string
uppercase first character
func (rs *Ruleset) Dasherize(word string) string
"SomeText" -> "some-text"
func (rs *Ruleset) ForeignKey(word string) string
an underscored foreign key name "Person" -> "person_id"
func (rs *Ruleset) ForeignKeyCondensed(word string) string
a foreign key (with an underscore) "Person" -> "personid"
func (rs *Ruleset) Humanize(word string) string
First letter of sentance captitilized
Uses custom friendly replacements via AddHuman()
func (rs *Ruleset) Ordinalize(str string) string
"1031" -> "1031st"
func (rs *Ruleset) Parameterize(word string) string
param safe dasherized names like "my-param"
func (rs *Ruleset) ParameterizeJoin(word, sep string) string
param safe dasherized names with custom seperator
func (rs *Ruleset) Pluralize(word string) string
returns the plural form of a singular word
func (rs *Ruleset) Singularize(word string) string
returns the singular form of a plural word
func (rs *Ruleset) Tableize(word string) string
Rails style pluralized table names: "SuperPerson" -> "super_people"
func (rs *Ruleset) Titleize(word string) string
Captitilize every word in sentance "hello there" -> "Hello There"
func (rs *Ruleset) Typeify(word string) string
"something_like_this" -> "SomethingLikeThis"
func (rs *Ruleset) Uncountables() map[string]bool
func (rs *Ruleset) Underscore(word string) string
lowercase underscore version "BigBen" -> "big_ben"

18
vendor/github.com/go-openapi/inflect/README.md generated vendored Normal file
View File

@ -0,0 +1,18 @@
# inflect [![Build Status](https://github.com/go-openapi/inflect/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/inflect/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/inflect/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/inflect)
[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/inflect/master/LICENSE)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/inflect.svg)](https://pkg.go.dev/github.com/go-openapi/inflect)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/inflect)](https://goreportcard.com/report/github.com/go-openapi/inflect)
A package to pluralize words.
Originally forked from fork of https://bitbucket.org/pkg/inflect under a MIT License.
A golang library applying grammar rules to English words.
> This package provides a basic set of functions applying
> grammar rules to inflect English words, modify case style
> (Capitalize, camelCase, snake_case, etc.).
>
> Acronyms are properly handled. A common use case is word pluralization.

View File

@ -19,12 +19,11 @@ type Rule struct {
// a Ruleset is the config of pluralization rules
// you can extend the rules with the Add* methods
type Ruleset struct {
uncountables map[string]bool
plurals []*Rule
singulars []*Rule
humans []*Rule
acronyms []*Rule
acronymMatcher *regexp.Regexp
uncountables map[string]bool
plurals []*Rule
singulars []*Rule
humans []*Rule
acronyms []*Rule
}
// create a blank ruleset. Unless you are going to
@ -282,7 +281,7 @@ func (rs *Ruleset) AddHuman(suffix, replacement string) {
rs.humans = append([]*Rule{r}, rs.humans...)
}
// Add any inconsistant pluralizing/sinularizing rules
// Add any inconsistent pluralizing/sinularizing rules
// to the set here.
func (rs *Ruleset) AddIrregular(singular, plural string) {
delete(rs.uncountables, singular)
@ -387,7 +386,7 @@ func (rs *Ruleset) Titleize(word string) string {
func (rs *Ruleset) safeCaseAcronyms(word string) string {
// convert an acroymn like HTML into Html
for _, rule := range rs.acronyms {
word = strings.Replace(word, rule.suffix, rule.replacement, -1)
word = strings.ReplaceAll(word, rule.suffix, rule.replacement)
}
return word
}
@ -409,7 +408,7 @@ func (rs *Ruleset) Humanize(word string) string {
word = replaceLast(word, "_id", "") // strip foreign key kinds
// replace and strings in humans list
for _, rule := range rs.humans {
word = strings.Replace(word, rule.suffix, rule.replacement, -1)
word = strings.ReplaceAll(word, rule.suffix, rule.replacement)
}
sentance := rs.seperatedWords(word, " ")
return strings.ToUpper(sentance[:1]) + sentance[1:]
@ -430,19 +429,19 @@ func (rs *Ruleset) Tableize(word string) string {
return rs.Pluralize(rs.Underscore(rs.Typeify(word)))
}
var notUrlSafe *regexp.Regexp = regexp.MustCompile(`[^\w\d\-_ ]`)
var notURLSafe = regexp.MustCompile(`[^\w\d\-_ ]`)
// param safe dasherized names like "my-param"
func (rs *Ruleset) Parameterize(word string) string {
return ParameterizeJoin(word, "-")
}
// param safe dasherized names with custom seperator
// param safe dasherized names with custom separator
func (rs *Ruleset) ParameterizeJoin(word, sep string) string {
word = strings.ToLower(word)
word = rs.Asciify(word)
word = notUrlSafe.ReplaceAllString(word, "")
word = strings.Replace(word, " ", sep, -1)
word = notURLSafe.ReplaceAllString(word, "")
word = strings.ReplaceAll(word, " ", sep)
if len(sep) > 0 {
squash, err := regexp.Compile(sep + "+")
if err == nil {
@ -453,7 +452,7 @@ func (rs *Ruleset) ParameterizeJoin(word, sep string) string {
return word
}
var lookalikes map[string]*regexp.Regexp = map[string]*regexp.Regexp{
var lookalikes = map[string]*regexp.Regexp{
"A": regexp.MustCompile(`À|Á|Â|Ã|Ä|Å`),
"AE": regexp.MustCompile(`Æ`),
"C": regexp.MustCompile(`Ç`),
@ -487,7 +486,7 @@ func (rs *Ruleset) Asciify(word string) string {
return word
}
var tablePrefix *regexp.Regexp = regexp.MustCompile(`^[^.]*\.`)
var tablePrefix = regexp.MustCompile(`^[^.]*\.`)
// "something_like_this" -> "SomethingLikeThis"
func (rs *Ruleset) Typeify(word string) string {
@ -642,13 +641,13 @@ func reverse(s string) string {
func isSpacerChar(c rune) bool {
switch {
case c == rune("_"[0]):
case c == '_':
return true
case c == rune(" "[0]):
case c == ':':
return true
case c == rune(":"[0]):
case c == '-':
return true
case c == rune("-"[0]):
case unicode.IsSpace(c):
return true
}
return false

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

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,19 @@
# gojsonreference [![Build Status](https://travis-ci.org/go-openapi/jsonreference.svg?branch=master)](https://travis-ci.org/go-openapi/jsonreference) [![codecov](https://codecov.io/gh/go-openapi/jsonreference/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/jsonreference) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
# gojsonreference [![Build Status](https://github.com/go-openapi/jsonreference/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/jsonreference/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/jsonreference/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/jsonreference)
[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/jsonreference/master/LICENSE)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/jsonreference.svg)](https://pkg.go.dev/github.com/go-openapi/jsonreference)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/jsonreference)](https://goreportcard.com/report/github.com/go-openapi/jsonreference)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/jsonreference/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/jsonreference?status.svg)](http://godoc.org/github.com/go-openapi/jsonreference)
An implementation of JSON Reference - Go language
## Status
Feature complete. Stable API
## Dependencies
https://github.com/go-openapi/jsonpointer
* 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
* http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07
* http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03

View File

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

View File

@ -1,4 +1,4 @@
# Loads OAI specs [![Build Status](https://travis-ci.org/go-openapi/loads.svg?branch=master)](https://travis-ci.org/go-openapi/loads) [![codecov](https://codecov.io/gh/go-openapi/loads/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/loads) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io) [![Actions/Go Test Status](https://github.com/go-openapi/loads/workflows/Go%20Test/badge.svg)](https://github.com/go-openapi/loads/actions?query=workflow%3A"Go+Test")
# Loads OAI specs [![Build Status](https://github.com/go-openapi/loads/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/loads/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/loads/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/loads)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/loads/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/loads?status.svg)](http://godoc.org/github.com/go-openapi/loads)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/loads)](https://goreportcard.com/report/github.com/go-openapi/loads)

View File

@ -12,10 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
/*
Package loads provides document loading methods for swagger (OAI) specifications.
It is used by other go-openapi packages to load and run analysis on local or remote spec documents.
*/
// Package loads provides document loading methods for swagger (OAI) specifications.
//
// It is used by other go-openapi packages to load and run analysis on local or remote spec documents.
package loads

View File

@ -21,7 +21,7 @@ var (
func init() {
jsonLoader := &loader{
DocLoaderWithMatch: DocLoaderWithMatch{
Match: func(pth string) bool {
Match: func(_ string) bool {
return true
},
Fn: JSONDoc,
@ -86,7 +86,7 @@ func (l *loader) Load(path string) (json.RawMessage, error) {
return nil, erp
}
var lastErr error = errors.New("no loader matched") // default error if no match was found
lastErr := errors.New("no loader matched") // default error if no match was found
for ldr := l; ldr != nil; ldr = ldr.Next {
if ldr.Match != nil && !ldr.Match(path) {
continue
@ -118,9 +118,8 @@ func JSONDoc(path string) (json.RawMessage, error) {
// This sets the configuration at the package level.
//
// NOTE:
// * this updates the default loader used by github.com/go-openapi/spec
// * since this sets package level globals, you shouln't call this concurrently
//
// - this updates the default loader used by github.com/go-openapi/spec
// - since this sets package level globals, you shouln't call this concurrently
func AddLoader(predicate DocMatcher, load DocLoader) {
loaders = loaders.WithHead(&loader{
DocLoaderWithMatch: DocLoaderWithMatch{

View File

@ -38,8 +38,8 @@ type Document struct {
specFilePath string
origSpec *spec.Swagger
schema *spec.Schema
raw json.RawMessage
pathLoader *loader
raw json.RawMessage
}
// JSONSpec loads a spec from a json document
@ -49,7 +49,14 @@ func JSONSpec(path string, options ...LoaderOption) (*Document, error) {
return nil, err
}
// convert to json
return Analyzed(data, "", options...)
doc, err := Analyzed(data, "", options...)
if err != nil {
return nil, err
}
doc.specFilePath = path
return doc, nil
}
// Embedded returns a Document based on embedded specs. No analysis is required
@ -71,7 +78,6 @@ func Embedded(orig, flat json.RawMessage, options ...LoaderOption) (*Document, e
// Spec loads a new spec document from a local or remote path
func Spec(path string, options ...LoaderOption) (*Document, error) {
ldr := loaderFromOptions(options)
b, err := ldr.Load(path)
@ -84,12 +90,10 @@ func Spec(path string, options ...LoaderOption) (*Document, error) {
return nil, err
}
if document != nil {
document.specFilePath = path
document.pathLoader = ldr
}
document.specFilePath = path
document.pathLoader = ldr
return document, err
return document, nil
}
// Analyzed creates a new analyzed spec document for a root json.RawMessage.
@ -117,7 +121,7 @@ func Analyzed(data json.RawMessage, version string, options ...LoaderOption) (*D
}
d := &Document{
Analyzer: analysis.New(swspec),
Analyzer: analysis.New(swspec), // NOTE: at this moment, analysis does not follow $refs to documents outside the root doc
schema: spec.MustLoadSwagger20Schema(),
spec: swspec,
raw: raw,
@ -152,9 +156,8 @@ func trimData(in json.RawMessage) (json.RawMessage, error) {
return d, nil
}
// Expanded expands the ref fields in the spec document and returns a new spec document
// Expanded expands the $ref fields in the spec document and returns a new spec document
func (d *Document) Expanded(options ...*spec.ExpandOptions) (*Document, error) {
swspec := new(spec.Swagger)
if err := json.Unmarshal(d.raw, swspec); err != nil {
return nil, err
@ -163,6 +166,9 @@ func (d *Document) Expanded(options ...*spec.ExpandOptions) (*Document, error) {
var expandOptions *spec.ExpandOptions
if len(options) > 0 {
expandOptions = options[0]
if expandOptions.RelativeBase == "" {
expandOptions.RelativeBase = d.specFilePath
}
} else {
expandOptions = &spec.ExpandOptions{
RelativeBase: d.specFilePath,
@ -194,7 +200,7 @@ func (d *Document) Expanded(options ...*spec.ExpandOptions) (*Document, error) {
return dd, nil
}
// BasePath the base path for this spec
// BasePath the base path for the API specified by this spec
func (d *Document) BasePath() string {
return d.spec.BasePath
}
@ -242,8 +248,11 @@ func (d *Document) ResetDefinitions() *Document {
// Pristine creates a new pristine document instance based on the input data
func (d *Document) Pristine() *Document {
dd, _ := Analyzed(d.Raw(), d.Version())
raw, _ := json.Marshal(d.Spec())
dd, _ := Analyzed(raw, d.Version())
dd.pathLoader = d.pathLoader
dd.specFilePath = d.specFilePath
return dd
}

View File

@ -1,44 +1,62 @@
linters-settings:
govet:
# Using err repeatedly considered as shadowing.
check-shadowing: false
check-shadowing: true
golint:
min-confidence: 0
gocyclo:
min-complexity: 30
min-complexity: 45
maligned:
suggest-new: true
dupl:
threshold: 100
threshold: 200
goconst:
min-len: 2
min-occurrences: 4
min-occurrences: 3
linters:
enable-all: true
disable:
- nilerr # nilerr crashes on this repo
- maligned
- unparam
- lll
- gochecknoinits
- gochecknoglobals
- funlen
- godox
- gocognit
- whitespace
- wsl
- funlen
- gochecknoglobals
- gochecknoinits
- scopelint
- wrapcheck
- exhaustivestruct
- exhaustive
- nlreturn
- testpackage
- gci
- gofumpt
- goerr113
- nlreturn
- gomnd
- tparallel
- exhaustivestruct
- goerr113
- errorlint
- nestif
- godot
- errorlint
- noctx
- gofumpt
- paralleltest
- tparallel
- thelper
- ifshort
- exhaustruct
- varnamelen
- gci
- depguard
- errchkjson
- inamedparam
- nonamedreturns
- musttag
- ireturn
- forcetypeassert
- cyclop
# deprecated linters
- deadcode
- interfacer
- nilerr
- scopelint
- varcheck
- structcheck
- golint
- nosnakecase

View File

@ -1,7 +1,10 @@
# runtime [![Build Status](https://travis-ci.org/go-openapi/runtime.svg?branch=client-context)](https://travis-ci.org/go-openapi/runtime) [![codecov](https://codecov.io/gh/go-openapi/runtime/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/runtime) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
# runtime [![Build Status](https://github.com/go-openapi/runtime/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/runtime/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/runtime/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/runtime)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/runtime/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/runtime?status.svg)](http://godoc.org/github.com/go-openapi/runtime)
[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/runtime/master/LICENSE)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/runtime.svg)](https://pkg.go.dev/github.com/go-openapi/runtime)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/runtime)](https://goreportcard.com/report/github.com/go-openapi/runtime)
# golang Open-API toolkit - runtime
# go OpenAPI toolkit runtime
The runtime component for use in codegeneration or as untyped usage.
The runtime component for use in code generation or as untyped usage.

View File

@ -38,9 +38,16 @@ type byteStreamOpts struct {
Close bool
}
// ByteStreamConsumer creates a consumer for byte streams,
// takes a Writer/BinaryUnmarshaler interface or binary slice by reference,
// and reads from the provided reader
// ByteStreamConsumer creates a consumer for byte streams.
//
// The consumer consumes from a provided reader into the data passed by reference.
//
// Supported output underlying types and interfaces, prioritized in this order:
// - io.ReaderFrom (for maximum control)
// - io.Writer (performs io.Copy)
// - encoding.BinaryUnmarshaler
// - *string
// - *[]byte
func ByteStreamConsumer(opts ...byteStreamOpt) Consumer {
var vals byteStreamOpts
for _, opt := range opts {
@ -51,44 +58,70 @@ func ByteStreamConsumer(opts ...byteStreamOpt) Consumer {
if reader == nil {
return errors.New("ByteStreamConsumer requires a reader") // early exit
}
if data == nil {
return errors.New("nil destination for ByteStreamConsumer")
}
close := defaultCloser
closer := defaultCloser
if vals.Close {
if cl, ok := reader.(io.Closer); ok {
close = cl.Close
if cl, isReaderCloser := reader.(io.Closer); isReaderCloser {
closer = cl.Close
}
}
//nolint:errcheck // closing a reader wouldn't fail.
defer close()
defer func() {
_ = closer()
}()
if wrtr, ok := data.(io.Writer); ok {
_, err := io.Copy(wrtr, reader)
if readerFrom, isReaderFrom := data.(io.ReaderFrom); isReaderFrom {
_, err := readerFrom.ReadFrom(reader)
return err
}
buf := new(bytes.Buffer)
if writer, isDataWriter := data.(io.Writer); isDataWriter {
_, err := io.Copy(writer, reader)
return err
}
// buffers input before writing to data
var buf bytes.Buffer
_, err := buf.ReadFrom(reader)
if err != nil {
return err
}
b := buf.Bytes()
if bu, ok := data.(encoding.BinaryUnmarshaler); ok {
return bu.UnmarshalBinary(b)
}
switch destinationPointer := data.(type) {
case encoding.BinaryUnmarshaler:
return destinationPointer.UnmarshalBinary(b)
case *any:
switch (*destinationPointer).(type) {
case string:
*destinationPointer = string(b)
return nil
case []byte:
*destinationPointer = b
if data != nil {
if str, ok := data.(*string); ok {
*str = string(b)
return nil
}
}
default:
// check for the underlying type to be pointer to []byte or string,
if ptr := reflect.TypeOf(data); ptr.Kind() != reflect.Ptr {
return errors.New("destination must be a pointer")
}
if t := reflect.TypeOf(data); data != nil && t.Kind() == reflect.Ptr {
v := reflect.Indirect(reflect.ValueOf(data))
if t = v.Type(); t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
t := v.Type()
switch {
case t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8:
v.SetBytes(b)
return nil
case t.Kind() == reflect.String:
v.SetString(string(b))
return nil
}
}
@ -97,67 +130,87 @@ func ByteStreamConsumer(opts ...byteStreamOpt) Consumer {
})
}
// ByteStreamProducer creates a producer for byte streams,
// takes a Reader/BinaryMarshaler interface or binary slice,
// and writes to a writer (essentially a pipe)
// ByteStreamProducer creates a producer for byte streams.
//
// The producer takes input data then writes to an output writer (essentially as a pipe).
//
// Supported input underlying types and interfaces, prioritized in this order:
// - io.WriterTo (for maximum control)
// - io.Reader (performs io.Copy). A ReadCloser is closed before exiting.
// - encoding.BinaryMarshaler
// - error (writes as a string)
// - []byte
// - string
// - struct, other slices: writes as JSON
func ByteStreamProducer(opts ...byteStreamOpt) Producer {
var vals byteStreamOpts
for _, opt := range opts {
opt(&vals)
}
return ProducerFunc(func(writer io.Writer, data interface{}) error {
if writer == nil {
return errors.New("ByteStreamProducer requires a writer") // early exit
}
close := defaultCloser
if data == nil {
return errors.New("nil data for ByteStreamProducer")
}
closer := defaultCloser
if vals.Close {
if cl, ok := writer.(io.Closer); ok {
close = cl.Close
if cl, isWriterCloser := writer.(io.Closer); isWriterCloser {
closer = cl.Close
}
}
//nolint:errcheck // TODO: closing a writer would fail.
defer close()
defer func() {
_ = closer()
}()
if rc, ok := data.(io.ReadCloser); ok {
if rc, isDataCloser := data.(io.ReadCloser); isDataCloser {
defer rc.Close()
}
if rdr, ok := data.(io.Reader); ok {
_, err := io.Copy(writer, rdr)
switch origin := data.(type) {
case io.WriterTo:
_, err := origin.WriteTo(writer)
return err
}
if bm, ok := data.(encoding.BinaryMarshaler); ok {
bytes, err := bm.MarshalBinary()
case io.Reader:
_, err := io.Copy(writer, origin)
return err
case encoding.BinaryMarshaler:
bytes, err := origin.MarshalBinary()
if err != nil {
return err
}
_, err = writer.Write(bytes)
return err
}
if data != nil {
if str, ok := data.(string); ok {
_, err := writer.Write([]byte(str))
return err
}
if e, ok := data.(error); ok {
_, err := writer.Write([]byte(e.Error()))
return err
}
case error:
_, err := writer.Write([]byte(origin.Error()))
return err
default:
v := reflect.Indirect(reflect.ValueOf(data))
if t := v.Type(); t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
t := v.Type()
switch {
case t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8:
_, err := writer.Write(v.Bytes())
return err
}
if t := v.Type(); t.Kind() == reflect.Struct || t.Kind() == reflect.Slice {
case t.Kind() == reflect.String:
_, err := writer.Write([]byte(v.String()))
return err
case t.Kind() == reflect.Struct || t.Kind() == reflect.Slice:
b, err := swag.WriteJSON(data)
if err != nil {
return err
}
_, err = writer.Write(b)
return err
}

View File

@ -30,12 +30,12 @@ type ClientOperation struct {
AuthInfo ClientAuthInfoWriter
Params ClientRequestWriter
Reader ClientResponseReader
Context context.Context
Context context.Context //nolint:containedctx // we precisely want this type to contain the request context
Client *http.Client
}
// A ClientTransport implementor knows how to submit Request objects to some destination
type ClientTransport interface {
//Submit(string, RequestWriter, ResponseReader, AuthInfoWriter) (interface{}, error)
// Submit(string, RequestWriter, ResponseReader, AuthInfoWriter) (interface{}, error)
Submit(*ClientOperation) (interface{}, error)
}

View File

@ -37,8 +37,8 @@ type ClientRequestWriter interface {
}
// ClientRequest is an interface for things that know how to
// add information to a swagger client request
type ClientRequest interface {
// add information to a swagger client request.
type ClientRequest interface { //nolint:interfacebloat // a swagger-capable request is quite rich, hence the many getter/setters
SetHeaderParam(string, ...string) error
GetHeaderParams() http.Header

View File

@ -16,62 +16,335 @@ package runtime
import (
"bytes"
"context"
"encoding"
"encoding/csv"
"errors"
"fmt"
"io"
"reflect"
"golang.org/x/sync/errgroup"
)
// CSVConsumer creates a new CSV consumer
func CSVConsumer() Consumer {
// CSVConsumer creates a new CSV consumer.
//
// The consumer consumes CSV records from a provided reader into the data passed by reference.
//
// CSVOpts options may be specified to alter the default CSV behavior on the reader and the writer side (e.g. separator, skip header, ...).
// The defaults are those of the standard library's csv.Reader and csv.Writer.
//
// Supported output underlying types and interfaces, prioritized in this order:
// - *csv.Writer
// - CSVWriter (writer options are ignored)
// - io.Writer (as raw bytes)
// - io.ReaderFrom (as raw bytes)
// - encoding.BinaryUnmarshaler (as raw bytes)
// - *[][]string (as a collection of records)
// - *[]byte (as raw bytes)
// - *string (a raw bytes)
//
// The consumer prioritizes situations where buffering the input is not required.
func CSVConsumer(opts ...CSVOpt) Consumer {
o := csvOptsWithDefaults(opts)
return ConsumerFunc(func(reader io.Reader, data interface{}) error {
if reader == nil {
return errors.New("CSVConsumer requires a reader")
}
if data == nil {
return errors.New("nil destination for CSVConsumer")
}
csvReader := csv.NewReader(reader)
writer, ok := data.(io.Writer)
if !ok {
return errors.New("data type must be io.Writer")
}
csvWriter := csv.NewWriter(writer)
records, err := csvReader.ReadAll()
if err != nil {
return err
}
for _, r := range records {
if err := csvWriter.Write(r); err != nil {
return err
o.applyToReader(csvReader)
closer := defaultCloser
if o.closeStream {
if cl, isReaderCloser := reader.(io.Closer); isReaderCloser {
closer = cl.Close
}
}
defer func() {
_ = closer()
}()
switch destination := data.(type) {
case *csv.Writer:
csvWriter := destination
o.applyToWriter(csvWriter)
return pipeCSV(csvWriter, csvReader, o)
case CSVWriter:
csvWriter := destination
// no writer options available
return pipeCSV(csvWriter, csvReader, o)
case io.Writer:
csvWriter := csv.NewWriter(destination)
o.applyToWriter(csvWriter)
return pipeCSV(csvWriter, csvReader, o)
case io.ReaderFrom:
var buf bytes.Buffer
csvWriter := csv.NewWriter(&buf)
o.applyToWriter(csvWriter)
if err := bufferedCSV(csvWriter, csvReader, o); err != nil {
return err
}
_, err := destination.ReadFrom(&buf)
return err
case encoding.BinaryUnmarshaler:
var buf bytes.Buffer
csvWriter := csv.NewWriter(&buf)
o.applyToWriter(csvWriter)
if err := bufferedCSV(csvWriter, csvReader, o); err != nil {
return err
}
return destination.UnmarshalBinary(buf.Bytes())
default:
// support *[][]string, *[]byte, *string
if ptr := reflect.TypeOf(data); ptr.Kind() != reflect.Ptr {
return errors.New("destination must be a pointer")
}
v := reflect.Indirect(reflect.ValueOf(data))
t := v.Type()
switch {
case t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Slice && t.Elem().Elem().Kind() == reflect.String:
csvWriter := &csvRecordsWriter{}
// writer options are ignored
if err := pipeCSV(csvWriter, csvReader, o); err != nil {
return err
}
v.Grow(len(csvWriter.records))
v.SetCap(len(csvWriter.records)) // in case Grow was unnessary, trim down the capacity
v.SetLen(len(csvWriter.records))
reflect.Copy(v, reflect.ValueOf(csvWriter.records))
return nil
case t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8:
var buf bytes.Buffer
csvWriter := csv.NewWriter(&buf)
o.applyToWriter(csvWriter)
if err := bufferedCSV(csvWriter, csvReader, o); err != nil {
return err
}
v.SetBytes(buf.Bytes())
return nil
case t.Kind() == reflect.String:
var buf bytes.Buffer
csvWriter := csv.NewWriter(&buf)
o.applyToWriter(csvWriter)
if err := bufferedCSV(csvWriter, csvReader, o); err != nil {
return err
}
v.SetString(buf.String())
return nil
default:
return fmt.Errorf("%v (%T) is not supported by the CSVConsumer, %s",
data, data, "can be resolved by supporting CSVWriter/Writer/BinaryUnmarshaler interface",
)
}
}
csvWriter.Flush()
return nil
})
}
// CSVProducer creates a new CSV producer
func CSVProducer() Producer {
// CSVProducer creates a new CSV producer.
//
// The producer takes input data then writes as CSV to an output writer (essentially as a pipe).
//
// Supported input underlying types and interfaces, prioritized in this order:
// - *csv.Reader
// - CSVReader (reader options are ignored)
// - io.Reader
// - io.WriterTo
// - encoding.BinaryMarshaler
// - [][]string
// - []byte
// - string
//
// The producer prioritizes situations where buffering the input is not required.
func CSVProducer(opts ...CSVOpt) Producer {
o := csvOptsWithDefaults(opts)
return ProducerFunc(func(writer io.Writer, data interface{}) error {
if writer == nil {
return errors.New("CSVProducer requires a writer")
}
dataBytes, ok := data.([]byte)
if !ok {
return errors.New("data type must be byte array")
if data == nil {
return errors.New("nil data for CSVProducer")
}
csvReader := csv.NewReader(bytes.NewBuffer(dataBytes))
records, err := csvReader.ReadAll()
if err != nil {
return err
}
csvWriter := csv.NewWriter(writer)
for _, r := range records {
if err := csvWriter.Write(r); err != nil {
return err
o.applyToWriter(csvWriter)
closer := defaultCloser
if o.closeStream {
if cl, isWriterCloser := writer.(io.Closer); isWriterCloser {
closer = cl.Close
}
}
defer func() {
_ = closer()
}()
if rc, isDataCloser := data.(io.ReadCloser); isDataCloser {
defer rc.Close()
}
switch origin := data.(type) {
case *csv.Reader:
csvReader := origin
o.applyToReader(csvReader)
return pipeCSV(csvWriter, csvReader, o)
case CSVReader:
csvReader := origin
// no reader options available
return pipeCSV(csvWriter, csvReader, o)
case io.Reader:
csvReader := csv.NewReader(origin)
o.applyToReader(csvReader)
return pipeCSV(csvWriter, csvReader, o)
case io.WriterTo:
// async piping of the writes performed by WriteTo
r, w := io.Pipe()
csvReader := csv.NewReader(r)
o.applyToReader(csvReader)
pipe, _ := errgroup.WithContext(context.Background())
pipe.Go(func() error {
_, err := origin.WriteTo(w)
_ = w.Close()
return err
})
pipe.Go(func() error {
defer func() {
_ = r.Close()
}()
return pipeCSV(csvWriter, csvReader, o)
})
return pipe.Wait()
case encoding.BinaryMarshaler:
buf, err := origin.MarshalBinary()
if err != nil {
return err
}
rdr := bytes.NewBuffer(buf)
csvReader := csv.NewReader(rdr)
return bufferedCSV(csvWriter, csvReader, o)
default:
// support [][]string, []byte, string (or pointers to those)
v := reflect.Indirect(reflect.ValueOf(data))
t := v.Type()
switch {
case t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Slice && t.Elem().Elem().Kind() == reflect.String:
csvReader := &csvRecordsWriter{
records: make([][]string, v.Len()),
}
reflect.Copy(reflect.ValueOf(csvReader.records), v)
return pipeCSV(csvWriter, csvReader, o)
case t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8:
buf := bytes.NewBuffer(v.Bytes())
csvReader := csv.NewReader(buf)
o.applyToReader(csvReader)
return bufferedCSV(csvWriter, csvReader, o)
case t.Kind() == reflect.String:
buf := bytes.NewBufferString(v.String())
csvReader := csv.NewReader(buf)
o.applyToReader(csvReader)
return bufferedCSV(csvWriter, csvReader, o)
default:
return fmt.Errorf("%v (%T) is not supported by the CSVProducer, %s",
data, data, "can be resolved by supporting CSVReader/Reader/BinaryMarshaler interface",
)
}
}
csvWriter.Flush()
return nil
})
}
// pipeCSV copies CSV records from a CSV reader to a CSV writer
func pipeCSV(csvWriter CSVWriter, csvReader CSVReader, opts csvOpts) error {
for ; opts.skippedLines > 0; opts.skippedLines-- {
_, err := csvReader.Read()
if err != nil {
if errors.Is(err, io.EOF) {
return nil
}
return err
}
}
for {
record, err := csvReader.Read()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return err
}
if err := csvWriter.Write(record); err != nil {
return err
}
}
csvWriter.Flush()
return csvWriter.Error()
}
// bufferedCSV copies CSV records from a CSV reader to a CSV writer,
// by first reading all records then writing them at once.
func bufferedCSV(csvWriter *csv.Writer, csvReader *csv.Reader, opts csvOpts) error {
for ; opts.skippedLines > 0; opts.skippedLines-- {
_, err := csvReader.Read()
if err != nil {
if errors.Is(err, io.EOF) {
return nil
}
return err
}
}
records, err := csvReader.ReadAll()
if err != nil {
return err
}
return csvWriter.WriteAll(records)
}

121
vendor/github.com/go-openapi/runtime/csv_options.go generated vendored Normal file
View File

@ -0,0 +1,121 @@
package runtime
import (
"encoding/csv"
"io"
)
// CSVOpts alter the behavior of the CSV consumer or producer.
type CSVOpt func(*csvOpts)
type csvOpts struct {
csvReader csv.Reader
csvWriter csv.Writer
skippedLines int
closeStream bool
}
// WithCSVReaderOpts specifies the options to csv.Reader
// when reading CSV.
func WithCSVReaderOpts(reader csv.Reader) CSVOpt {
return func(o *csvOpts) {
o.csvReader = reader
}
}
// WithCSVWriterOpts specifies the options to csv.Writer
// when writing CSV.
func WithCSVWriterOpts(writer csv.Writer) CSVOpt {
return func(o *csvOpts) {
o.csvWriter = writer
}
}
// WithCSVSkipLines will skip header lines.
func WithCSVSkipLines(skipped int) CSVOpt {
return func(o *csvOpts) {
o.skippedLines = skipped
}
}
func WithCSVClosesStream() CSVOpt {
return func(o *csvOpts) {
o.closeStream = true
}
}
func (o csvOpts) applyToReader(in *csv.Reader) {
if o.csvReader.Comma != 0 {
in.Comma = o.csvReader.Comma
}
if o.csvReader.Comment != 0 {
in.Comment = o.csvReader.Comment
}
if o.csvReader.FieldsPerRecord != 0 {
in.FieldsPerRecord = o.csvReader.FieldsPerRecord
}
in.LazyQuotes = o.csvReader.LazyQuotes
in.TrimLeadingSpace = o.csvReader.TrimLeadingSpace
in.ReuseRecord = o.csvReader.ReuseRecord
}
func (o csvOpts) applyToWriter(in *csv.Writer) {
if o.csvWriter.Comma != 0 {
in.Comma = o.csvWriter.Comma
}
in.UseCRLF = o.csvWriter.UseCRLF
}
func csvOptsWithDefaults(opts []CSVOpt) csvOpts {
var o csvOpts
for _, apply := range opts {
apply(&o)
}
return o
}
type CSVWriter interface {
Write([]string) error
Flush()
Error() error
}
type CSVReader interface {
Read() ([]string, error)
}
var (
_ CSVWriter = &csvRecordsWriter{}
_ CSVReader = &csvRecordsWriter{}
)
// csvRecordsWriter is an internal container to move CSV records back and forth
type csvRecordsWriter struct {
i int
records [][]string
}
func (w *csvRecordsWriter) Write(record []string) error {
w.records = append(w.records, record)
return nil
}
func (w *csvRecordsWriter) Read() ([]string, error) {
if w.i >= len(w.records) {
return nil, io.EOF
}
defer func() {
w.i++
}()
return w.records[w.i], nil
}
func (w *csvRecordsWriter) Flush() {}
func (w *csvRecordsWriter) Error() error {
return nil
}

View File

@ -5,6 +5,8 @@ import (
"os"
)
var _ Logger = StandardLogger{}
type StandardLogger struct{}
func (StandardLogger) Printf(format string, args ...interface{}) {

View File

@ -18,6 +18,8 @@ import (
stdContext "context"
"fmt"
"net/http"
"net/url"
"path"
"strings"
"sync"
@ -35,12 +37,21 @@ import (
// Debug when true turns on verbose logging
var Debug = logger.DebugEnabled()
// Logger is the standard libray logger used for printing debug messages
var Logger logger.Logger = logger.StandardLogger{}
func debugLog(format string, args ...interface{}) {
if Debug {
Logger.Printf(format, args...)
func debugLogfFunc(lg logger.Logger) func(string, ...any) {
if logger.DebugEnabled() {
if lg == nil {
return Logger.Debugf
}
return lg.Debugf
}
// muted logger
return func(_ string, _ ...any) {}
}
// A Builder can create middlewares
@ -73,10 +84,11 @@ func (fn ResponderFunc) WriteResponse(rw http.ResponseWriter, pr runtime.Produce
// used throughout to store request context with the standard context attached
// to the http.Request
type Context struct {
spec *loads.Document
analyzer *analysis.Spec
api RoutableAPI
router Router
spec *loads.Document
analyzer *analysis.Spec
api RoutableAPI
router Router
debugLogf func(string, ...any) // a logging function to debug context and all components using it
}
type routableUntypedAPI struct {
@ -162,7 +174,7 @@ func (r *routableUntypedAPI) HandlerFor(method, path string) (http.Handler, bool
r.hlock.Unlock()
return handler, ok
}
func (r *routableUntypedAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) {
func (r *routableUntypedAPI) ServeErrorFor(_ string) func(http.ResponseWriter, *http.Request, error) {
return r.api.ServeError
}
func (r *routableUntypedAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {
@ -189,7 +201,9 @@ func (r *routableUntypedAPI) DefaultConsumes() string {
return r.defaultConsumes
}
// NewRoutableContext creates a new context for a routable API
// NewRoutableContext creates a new context for a routable API.
//
// If a nil Router is provided, the DefaultRouter (denco-based) will be used.
func NewRoutableContext(spec *loads.Document, routableAPI RoutableAPI, routes Router) *Context {
var an *analysis.Spec
if spec != nil {
@ -199,26 +213,40 @@ func NewRoutableContext(spec *loads.Document, routableAPI RoutableAPI, routes Ro
return NewRoutableContextWithAnalyzedSpec(spec, an, routableAPI, routes)
}
// NewRoutableContextWithAnalyzedSpec is like NewRoutableContext but takes in input the analysed spec too
// NewRoutableContextWithAnalyzedSpec is like NewRoutableContext but takes as input an already analysed spec.
//
// If a nil Router is provided, the DefaultRouter (denco-based) will be used.
func NewRoutableContextWithAnalyzedSpec(spec *loads.Document, an *analysis.Spec, routableAPI RoutableAPI, routes Router) *Context {
// Either there are no spec doc and analysis, or both of them.
if !((spec == nil && an == nil) || (spec != nil && an != nil)) {
panic(errors.New(http.StatusInternalServerError, "routable context requires either both spec doc and analysis, or none of them"))
}
ctx := &Context{spec: spec, api: routableAPI, analyzer: an, router: routes}
return ctx
return &Context{
spec: spec,
api: routableAPI,
analyzer: an,
router: routes,
debugLogf: debugLogfFunc(nil),
}
}
// NewContext creates a new context wrapper
// NewContext creates a new context wrapper.
//
// If a nil Router is provided, the DefaultRouter (denco-based) will be used.
func NewContext(spec *loads.Document, api *untyped.API, routes Router) *Context {
var an *analysis.Spec
if spec != nil {
an = analysis.New(spec.Spec())
}
ctx := &Context{spec: spec, analyzer: an}
ctx := &Context{
spec: spec,
analyzer: an,
router: routes,
debugLogf: debugLogfFunc(nil),
}
ctx.api = newRoutableUntypedAPI(spec, api, ctx)
ctx.router = routes
return ctx
}
@ -282,6 +310,13 @@ func (c *Context) BasePath() string {
return c.spec.BasePath()
}
// SetLogger allows for injecting a logger to catch debug entries.
//
// The logger is enabled in DEBUG mode only.
func (c *Context) SetLogger(lg logger.Logger) {
c.debugLogf = debugLogfFunc(lg)
}
// RequiredProduces returns the accepted content types for responses
func (c *Context) RequiredProduces() []string {
return c.analyzer.RequiredProduces()
@ -299,6 +334,7 @@ func (c *Context) BindValidRequest(request *http.Request, route *MatchedRoute, b
if err != nil {
res = append(res, err)
} else {
c.debugLogf("validating content type for %q against [%s]", ct, strings.Join(route.Consumes, ", "))
if err := validateContentType(route.Consumes, ct); err != nil {
res = append(res, err)
}
@ -397,16 +433,16 @@ func (c *Context) ResponseFormat(r *http.Request, offers []string) (string, *htt
var rCtx = r.Context()
if v, ok := rCtx.Value(ctxResponseFormat).(string); ok {
debugLog("[%s %s] found response format %q in context", r.Method, r.URL.Path, v)
c.debugLogf("[%s %s] found response format %q in context", r.Method, r.URL.Path, v)
return v, r
}
format := NegotiateContentType(r, offers, "")
if format != "" {
debugLog("[%s %s] set response format %q in context", r.Method, r.URL.Path, format)
c.debugLogf("[%s %s] set response format %q in context", r.Method, r.URL.Path, format)
r = r.WithContext(stdContext.WithValue(rCtx, ctxResponseFormat, format))
}
debugLog("[%s %s] negotiated response format %q", r.Method, r.URL.Path, format)
c.debugLogf("[%s %s] negotiated response format %q", r.Method, r.URL.Path, format)
return format, r
}
@ -469,7 +505,7 @@ func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute)
var rCtx = request.Context()
if v, ok := rCtx.Value(ctxBoundParams).(*validation); ok {
debugLog("got cached validation (valid: %t)", len(v.result) == 0)
c.debugLogf("got cached validation (valid: %t)", len(v.result) == 0)
if len(v.result) > 0 {
return v.bound, request, errors.CompositeValidationError(v.result...)
}
@ -481,7 +517,7 @@ func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute)
if len(result.result) > 0 {
return result.bound, request, errors.CompositeValidationError(result.result...)
}
debugLog("no validation errors found")
c.debugLogf("no validation errors found")
return result.bound, request, nil
}
@ -492,7 +528,7 @@ func (c *Context) NotFound(rw http.ResponseWriter, r *http.Request) {
// Respond renders the response after doing some content negotiation
func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []string, route *MatchedRoute, data interface{}) {
debugLog("responding to %s %s with produces: %v", r.Method, r.URL.Path, produces)
c.debugLogf("responding to %s %s with produces: %v", r.Method, r.URL.Path, produces)
offers := []string{}
for _, mt := range produces {
if mt != c.api.DefaultProduces() {
@ -501,7 +537,7 @@ func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []st
}
// the default producer is last so more specific producers take precedence
offers = append(offers, c.api.DefaultProduces())
debugLog("offers: %v", offers)
c.debugLogf("offers: %v", offers)
var format string
format, r = c.ResponseFormat(r, offers)
@ -516,7 +552,7 @@ func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []st
prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()}))
pr, ok := prods[c.api.DefaultProduces()]
if !ok {
panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
panic(errors.New(http.StatusInternalServerError, cantFindProducer(format)))
}
prod = pr
}
@ -542,14 +578,14 @@ func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []st
}
if route == nil || route.Operation == nil {
rw.WriteHeader(200)
if r.Method == "HEAD" {
rw.WriteHeader(http.StatusOK)
if r.Method == http.MethodHead {
return
}
producers := c.api.ProducersFor(normalizeOffers(offers))
prod, ok := producers[format]
if !ok {
panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
panic(errors.New(http.StatusInternalServerError, cantFindProducer(format)))
}
if err := prod.Produce(rw, data); err != nil {
panic(err) // let the recovery middleware deal with this
@ -559,7 +595,7 @@ func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []st
if _, code, ok := route.Operation.SuccessResponse(); ok {
rw.WriteHeader(code)
if code == 204 || r.Method == "HEAD" {
if code == http.StatusNoContent || r.Method == http.MethodHead {
return
}
@ -570,7 +606,7 @@ func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []st
prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()}))
pr, ok := prods[c.api.DefaultProduces()]
if !ok {
panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
panic(errors.New(http.StatusInternalServerError, cantFindProducer(format)))
}
prod = pr
}
@ -584,45 +620,92 @@ func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []st
c.api.ServeErrorFor(route.Operation.ID)(rw, r, errors.New(http.StatusInternalServerError, "can't produce response"))
}
func (c *Context) APIHandlerSwaggerUI(builder Builder) http.Handler {
// APIHandlerSwaggerUI returns a handler to serve the API.
//
// This handler includes a swagger spec, router and the contract defined in the swagger spec.
//
// A spec UI (SwaggerUI) is served at {API base path}/docs and the spec document at /swagger.json
// (these can be modified with uiOptions).
func (c *Context) APIHandlerSwaggerUI(builder Builder, opts ...UIOption) http.Handler {
b := builder
if b == nil {
b = PassthroughBuilder
}
var title string
sp := c.spec.Spec()
if sp != nil && sp.Info != nil && sp.Info.Title != "" {
title = sp.Info.Title
}
specPath, uiOpts, specOpts := c.uiOptionsForHandler(opts)
var swaggerUIOpts SwaggerUIOpts
fromCommonToAnyOptions(uiOpts, &swaggerUIOpts)
swaggerUIOpts := SwaggerUIOpts{
BasePath: c.BasePath(),
Title: title,
}
return Spec("", c.spec.Raw(), SwaggerUI(swaggerUIOpts, c.RoutesHandler(b)))
return Spec(specPath, c.spec.Raw(), SwaggerUI(swaggerUIOpts, c.RoutesHandler(b)), specOpts...)
}
// APIHandler returns a handler to serve the API, this includes a swagger spec, router and the contract defined in the swagger spec
func (c *Context) APIHandler(builder Builder) http.Handler {
// APIHandlerRapiDoc returns a handler to serve the API.
//
// This handler includes a swagger spec, router and the contract defined in the swagger spec.
//
// A spec UI (RapiDoc) is served at {API base path}/docs and the spec document at /swagger.json
// (these can be modified with uiOptions).
func (c *Context) APIHandlerRapiDoc(builder Builder, opts ...UIOption) http.Handler {
b := builder
if b == nil {
b = PassthroughBuilder
}
specPath, uiOpts, specOpts := c.uiOptionsForHandler(opts)
var rapidocUIOpts RapiDocOpts
fromCommonToAnyOptions(uiOpts, &rapidocUIOpts)
return Spec(specPath, c.spec.Raw(), RapiDoc(rapidocUIOpts, c.RoutesHandler(b)), specOpts...)
}
// APIHandler returns a handler to serve the API.
//
// This handler includes a swagger spec, router and the contract defined in the swagger spec.
//
// A spec UI (Redoc) is served at {API base path}/docs and the spec document at /swagger.json
// (these can be modified with uiOptions).
func (c *Context) APIHandler(builder Builder, opts ...UIOption) http.Handler {
b := builder
if b == nil {
b = PassthroughBuilder
}
specPath, uiOpts, specOpts := c.uiOptionsForHandler(opts)
var redocOpts RedocOpts
fromCommonToAnyOptions(uiOpts, &redocOpts)
return Spec(specPath, c.spec.Raw(), Redoc(redocOpts, c.RoutesHandler(b)), specOpts...)
}
func (c Context) uiOptionsForHandler(opts []UIOption) (string, uiOptions, []SpecOption) {
var title string
sp := c.spec.Spec()
if sp != nil && sp.Info != nil && sp.Info.Title != "" {
title = sp.Info.Title
}
redocOpts := RedocOpts{
BasePath: c.BasePath(),
Title: title,
// default options (may be overridden)
optsForContext := []UIOption{
WithUIBasePath(c.BasePath()),
WithUITitle(title),
}
optsForContext = append(optsForContext, opts...)
uiOpts := uiOptionsWithDefaults(optsForContext)
// If spec URL is provided, there is a non-default path to serve the spec.
// This makes sure that the UI middleware is aligned with the Spec middleware.
u, _ := url.Parse(uiOpts.SpecURL)
var specPath string
if u != nil {
specPath = u.Path
}
return Spec("", c.spec.Raw(), Redoc(redocOpts, c.RoutesHandler(b)))
pth, doc := path.Split(specPath)
if pth == "." {
pth = ""
}
return pth, uiOpts, []SpecOption{WithSpecDocument(doc)}
}
// RoutesHandler returns a handler to serve the API, just the routes and the contract defined in the swagger spec
@ -633,3 +716,7 @@ func (c *Context) RoutesHandler(builder Builder) http.Handler {
}
return NewRouter(c, b(NewOperationExecutor(c)))
}
func cantFindProducer(format string) string {
return "can't find a producer for " + format
}

View File

@ -2,6 +2,7 @@
package denco
import (
"errors"
"fmt"
"sort"
"strings"
@ -29,13 +30,13 @@ const (
// Router represents a URL router.
type Router struct {
param *doubleArray
// SizeHint expects the maximum number of path parameters in records to Build.
// SizeHint will be used to determine the capacity of the memory to allocate.
// By default, SizeHint will be determined from given records to Build.
SizeHint int
static map[string]interface{}
param *doubleArray
}
// New returns a new Router.
@ -51,7 +52,7 @@ func New() *Router {
// params is a slice of the Param that arranged in the order in which parameters appeared.
// e.g. when built routing path is "/path/to/:id/:name" and given path is "/path/to/1/alice". params order is [{"id": "1"}, {"name": "alice"}], not [{"name": "alice"}, {"id": "1"}].
func (rt *Router) Lookup(path string) (data interface{}, params Params, found bool) {
if data, found := rt.static[path]; found {
if data, found = rt.static[path]; found {
return data, nil, true
}
if len(rt.param.node) == 1 {
@ -71,7 +72,7 @@ func (rt *Router) Lookup(path string) (data interface{}, params Params, found bo
func (rt *Router) Build(records []Record) error {
statics, params := makeRecords(records)
if len(params) > MaxSize {
return fmt.Errorf("denco: too many records")
return errors.New("denco: too many records")
}
if rt.SizeHint < 0 {
rt.SizeHint = 0
@ -131,7 +132,8 @@ func newDoubleArray() *doubleArray {
// baseCheck contains BASE, CHECK and Extra flags.
// From the top, 22bits of BASE, 2bits of Extra flags and 8bits of CHECK.
//
// BASE (22bit) | Extra flags (2bit) | CHECK (8bit)
// BASE (22bit) | Extra flags (2bit) | CHECK (8bit)
//
// |----------------------|--|--------|
// 32 10 8 0
type baseCheck uint32
@ -196,24 +198,29 @@ func (da *doubleArray) lookup(path string, params []Param, idx int) (*node, []Pa
if next := nextIndex(da.bc[idx].Base(), TerminationCharacter); next < len(da.bc) && da.bc[next].Check() == TerminationCharacter {
return da.node[da.bc[next].Base()], params, true
}
BACKTRACKING:
for j := len(indices) - 1; j >= 0; j-- {
i, idx := int(indices[j]>>32), int(indices[j]&0xffffffff)
if da.bc[idx].IsSingleParam() {
idx := nextIndex(da.bc[idx].Base(), ParamCharacter)
if idx >= len(da.bc) {
nextIdx := nextIndex(da.bc[idx].Base(), ParamCharacter)
if nextIdx >= len(da.bc) {
break
}
next := NextSeparator(path, i)
params := append(params, Param{Value: path[i:next]})
if nd, params, found := da.lookup(path[next:], params, idx); found {
return nd, params, true
nextParams := params
nextParams = append(nextParams, Param{Value: path[i:next]})
if nd, nextNextParams, found := da.lookup(path[next:], nextParams, nextIdx); found {
return nd, nextNextParams, true
}
}
if da.bc[idx].IsWildcardParam() {
idx := nextIndex(da.bc[idx].Base(), WildcardCharacter)
params := append(params, Param{Value: path[i:]})
return da.node[da.bc[idx].Base()], params, true
nextIdx := nextIndex(da.bc[idx].Base(), WildcardCharacter)
nextParams := params
nextParams = append(nextParams, Param{Value: path[i:]})
return da.node[da.bc[nextIdx].Base()], nextParams, true
}
}
return nil, nil, false
@ -325,7 +332,7 @@ func (da *doubleArray) arrange(records []*record, idx, depth int, usedBase map[i
}
base = da.findBase(siblings, idx, usedBase)
if base > MaxSize {
return -1, nil, nil, fmt.Errorf("denco: too many elements of internal slice")
return -1, nil, nil, errors.New("denco: too many elements of internal slice")
}
da.setBase(idx, base)
return base, siblings, leaf, err
@ -386,7 +393,7 @@ func makeSiblings(records []*record, depth int) (sib []sibling, leaf *record, er
case pc == c:
continue
default:
return nil, nil, fmt.Errorf("denco: BUG: routing table hasn't been sorted")
return nil, nil, errors.New("denco: BUG: routing table hasn't been sorted")
}
if n > 0 {
sib[n-1].end = i
@ -431,7 +438,7 @@ func makeRecords(srcs []Record) (statics, params []*record) {
wildcardPrefix := string(SeparatorCharacter) + string(WildcardCharacter)
restconfPrefix := string(PathParamCharacter) + string(ParamCharacter)
for _, r := range srcs {
if strings.Contains(r.Key, paramPrefix) || strings.Contains(r.Key, wildcardPrefix) ||strings.Contains(r.Key, restconfPrefix){
if strings.Contains(r.Key, paramPrefix) || strings.Contains(r.Key, wildcardPrefix) || strings.Contains(r.Key, restconfPrefix) {
r.Key += termChar
params = append(params, &record{Record: r})
} else {

View File

@ -12,51 +12,52 @@
// See the License for the specific language governing permissions and
// limitations under the License.
/*Package middleware provides the library with helper functions for serving swagger APIs.
/*
Package middleware provides the library with helper functions for serving swagger APIs.
Pseudo middleware handler
import (
"net/http"
import (
"net/http"
"github.com/go-openapi/errors"
)
"github.com/go-openapi/errors"
)
func newCompleteMiddleware(ctx *Context) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// use context to lookup routes
if matched, ok := ctx.RouteInfo(r); ok {
func newCompleteMiddleware(ctx *Context) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// use context to lookup routes
if matched, ok := ctx.RouteInfo(r); ok {
if matched.NeedsAuth() {
if _, err := ctx.Authorize(r, matched); err != nil {
ctx.Respond(rw, r, matched.Produces, matched, err)
return
}
}
if matched.NeedsAuth() {
if _, err := ctx.Authorize(r, matched); err != nil {
ctx.Respond(rw, r, matched.Produces, matched, err)
return
}
}
bound, validation := ctx.BindAndValidate(r, matched)
if validation != nil {
ctx.Respond(rw, r, matched.Produces, matched, validation)
return
}
bound, validation := ctx.BindAndValidate(r, matched)
if validation != nil {
ctx.Respond(rw, r, matched.Produces, matched, validation)
return
}
result, err := matched.Handler.Handle(bound)
if err != nil {
ctx.Respond(rw, r, matched.Produces, matched, err)
return
}
result, err := matched.Handler.Handle(bound)
if err != nil {
ctx.Respond(rw, r, matched.Produces, matched, err)
return
}
ctx.Respond(rw, r, matched.Produces, matched, result)
return
}
ctx.Respond(rw, r, matched.Produces, matched, result)
return
}
// Not found, check if it exists in the other methods first
if others := ctx.AllowedMethods(r); len(others) > 0 {
ctx.Respond(rw, r, ctx.spec.RequiredProduces(), nil, errors.MethodNotAllowed(r.Method, others))
return
}
ctx.Respond(rw, r, ctx.spec.RequiredProduces(), nil, errors.NotFound("path %s was not found", r.URL.Path))
})
}
// Not found, check if it exists in the other methods first
if others := ctx.AllowedMethods(r); len(others) > 0 {
ctx.Respond(rw, r, ctx.spec.RequiredProduces(), nil, errors.MethodNotAllowed(r.Method, others))
return
}
ctx.Respond(rw, r, ctx.spec.RequiredProduces(), nil, errors.NotFound("path %s was not found", r.URL.Path))
})
}
*/
package middleware

View File

@ -1,9 +0,0 @@
// +build go1.8
package middleware
import "net/url"
func pathUnescape(path string) (string, error) {
return url.PathUnescape(path)
}

View File

@ -195,7 +195,8 @@ func ParseAccept2(header http.Header, key string) (specs []AcceptSpec) {
}
// ParseAccept parses Accept* headers.
func ParseAccept(header http.Header, key string) (specs []AcceptSpec) {
func ParseAccept(header http.Header, key string) []AcceptSpec {
var specs []AcceptSpec
loop:
for _, s := range header[key] {
for {
@ -218,6 +219,7 @@ loop:
}
}
}
specs = append(specs, spec)
s = skipSpace(s)
if !strings.HasPrefix(s, ",") {
@ -226,7 +228,8 @@ loop:
s = skipSpace(s[1:])
}
}
return
return specs
}
func skipSpace(s string) (rest string) {
@ -306,7 +309,7 @@ func expectTokenOrQuoted(s string) (value string, rest string) {
p := make([]byte, len(s)-1)
j := copy(p, s[:i])
escape := true
for i = i + 1; i < len(s); i++ {
for i++; i < len(s); i++ {
b := s[i]
switch {
case escape:

View File

@ -34,6 +34,11 @@ import (
const defaultMaxMemory = 32 << 20
const (
typeString = "string"
typeArray = "array"
)
var textUnmarshalType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
func newUntypedParamBinder(param spec.Parameter, spec *spec.Swagger, formats strfmt.Registry) *untypedParamBinder {
@ -66,7 +71,7 @@ func (p *untypedParamBinder) typeForSchema(tpe, format string, items *spec.Items
case "boolean":
return reflect.TypeOf(true)
case "string":
case typeString:
if tt, ok := p.formats.GetType(format); ok {
return tt
}
@ -94,7 +99,7 @@ func (p *untypedParamBinder) typeForSchema(tpe, format string, items *spec.Items
return reflect.TypeOf(float64(0))
}
case "array":
case typeArray:
if items == nil {
return nil
}
@ -119,7 +124,7 @@ func (p *untypedParamBinder) allowsMulti() bool {
func (p *untypedParamBinder) readValue(values runtime.Gettable, target reflect.Value) ([]string, bool, bool, error) {
name, in, cf, tpe := p.parameter.Name, p.parameter.In, p.parameter.CollectionFormat, p.parameter.Type
if tpe == "array" {
if tpe == typeArray {
if cf == "multi" {
if !p.allowsMulti() {
return nil, false, false, errors.InvalidCollectionFormat(name, in, cf)
@ -208,10 +213,11 @@ func (p *untypedParamBinder) Bind(request *http.Request, routeParams RouteParams
if ffErr != nil {
if p.parameter.Required {
return errors.NewParseError(p.Name, p.parameter.In, "", ffErr)
} else {
return nil
}
return nil
}
target.Set(reflect.ValueOf(runtime.File{Data: file, Header: header}))
return nil
}
@ -263,7 +269,7 @@ func (p *untypedParamBinder) Bind(request *http.Request, routeParams RouteParams
}
func (p *untypedParamBinder) bindValue(data []string, hasKey bool, target reflect.Value) error {
if p.parameter.Type == "array" {
if p.parameter.Type == typeArray {
return p.setSliceFieldValue(target, p.parameter.Default, data, hasKey)
}
var d string
@ -273,7 +279,7 @@ func (p *untypedParamBinder) bindValue(data []string, hasKey bool, target reflec
return p.setFieldValue(target, p.parameter.Default, d, hasKey)
}
func (p *untypedParamBinder) setFieldValue(target reflect.Value, defaultValue interface{}, data string, hasKey bool) error {
func (p *untypedParamBinder) setFieldValue(target reflect.Value, defaultValue interface{}, data string, hasKey bool) error { //nolint:gocyclo
tpe := p.parameter.Type
if p.parameter.Format != "" {
tpe = p.parameter.Format
@ -317,7 +323,7 @@ func (p *untypedParamBinder) setFieldValue(target reflect.Value, defaultValue in
return nil
}
switch target.Kind() {
switch target.Kind() { //nolint:exhaustive // we want to check only types that map from a swagger parameter
case reflect.Bool:
if data == "" {
if target.CanSet() {

View File

@ -1,9 +0,0 @@
// +build !go1.8
package middleware
import "net/url"
func pathUnescape(path string) (string, error) {
return url.QueryUnescape(path)
}

View File

@ -10,67 +10,57 @@ import (
// RapiDocOpts configures the RapiDoc middlewares
type RapiDocOpts struct {
// BasePath for the UI path, defaults to: /
// BasePath for the UI, defaults to: /
BasePath string
// Path combines with BasePath for the full UI path, defaults to: docs
// Path combines with BasePath to construct the path to the UI, defaults to: "docs".
Path string
// SpecURL the url to find the spec for
// SpecURL is the URL of the spec document.
//
// Defaults to: /swagger.json
SpecURL string
// RapiDocURL for the js that generates the rapidoc site, defaults to: https://cdn.jsdelivr.net/npm/rapidoc/bundles/rapidoc.standalone.js
RapiDocURL string
// Title for the documentation site, default to: API documentation
Title string
// Template specifies a custom template to serve the UI
Template string
// RapiDocURL points to the js asset that generates the rapidoc site.
//
// Defaults to https://unpkg.com/rapidoc/dist/rapidoc-min.js
RapiDocURL string
}
// EnsureDefaults in case some options are missing
func (r *RapiDocOpts) EnsureDefaults() {
if r.BasePath == "" {
r.BasePath = "/"
}
if r.Path == "" {
r.Path = "docs"
}
if r.SpecURL == "" {
r.SpecURL = "/swagger.json"
}
common := toCommonUIOptions(r)
common.EnsureDefaults()
fromCommonToAnyOptions(common, r)
// rapidoc-specifics
if r.RapiDocURL == "" {
r.RapiDocURL = rapidocLatest
}
if r.Title == "" {
r.Title = "API documentation"
if r.Template == "" {
r.Template = rapidocTemplate
}
}
// RapiDoc creates a middleware to serve a documentation site for a swagger spec.
// This allows for altering the spec before starting the http listener.
//
// This allows for altering the spec before starting the http listener.
func RapiDoc(opts RapiDocOpts, next http.Handler) http.Handler {
opts.EnsureDefaults()
pth := path.Join(opts.BasePath, opts.Path)
tmpl := template.Must(template.New("rapidoc").Parse(rapidocTemplate))
tmpl := template.Must(template.New("rapidoc").Parse(opts.Template))
assets := bytes.NewBuffer(nil)
if err := tmpl.Execute(assets, opts); err != nil {
panic(fmt.Errorf("cannot execute template: %w", err))
}
buf := bytes.NewBuffer(nil)
_ = tmpl.Execute(buf, opts)
b := buf.Bytes()
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.URL.Path == pth {
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write(b)
return
}
if next == nil {
rw.Header().Set("Content-Type", "text/plain")
rw.WriteHeader(http.StatusNotFound)
_, _ = rw.Write([]byte(fmt.Sprintf("%q not found", pth)))
return
}
next.ServeHTTP(rw, r)
})
return serveUI(pth, assets.Bytes(), next)
}
const (
@ -79,7 +69,7 @@ const (
<html>
<head>
<title>{{ .Title }}</title>
<meta charset="utf-8"> <!-- Important: rapi-doc uses utf8 charecters -->
<meta charset="utf-8"> <!-- Important: rapi-doc uses utf8 characters -->
<script type="module" src="{{ .RapiDocURL }}"></script>
</head>
<body>

View File

@ -10,67 +10,58 @@ import (
// RedocOpts configures the Redoc middlewares
type RedocOpts struct {
// BasePath for the UI path, defaults to: /
// BasePath for the UI, defaults to: /
BasePath string
// Path combines with BasePath for the full UI path, defaults to: docs
// Path combines with BasePath to construct the path to the UI, defaults to: "docs".
Path string
// SpecURL the url to find the spec for
// SpecURL is the URL of the spec document.
//
// Defaults to: /swagger.json
SpecURL string
// RedocURL for the js that generates the redoc site, defaults to: https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js
RedocURL string
// Title for the documentation site, default to: API documentation
Title string
// Template specifies a custom template to serve the UI
Template string
// RedocURL points to the js that generates the redoc site.
//
// Defaults to: https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js
RedocURL string
}
// EnsureDefaults in case some options are missing
func (r *RedocOpts) EnsureDefaults() {
if r.BasePath == "" {
r.BasePath = "/"
}
if r.Path == "" {
r.Path = "docs"
}
if r.SpecURL == "" {
r.SpecURL = "/swagger.json"
}
common := toCommonUIOptions(r)
common.EnsureDefaults()
fromCommonToAnyOptions(common, r)
// redoc-specifics
if r.RedocURL == "" {
r.RedocURL = redocLatest
}
if r.Title == "" {
r.Title = "API documentation"
if r.Template == "" {
r.Template = redocTemplate
}
}
// Redoc creates a middleware to serve a documentation site for a swagger spec.
// This allows for altering the spec before starting the http listener.
//
// This allows for altering the spec before starting the http listener.
func Redoc(opts RedocOpts, next http.Handler) http.Handler {
opts.EnsureDefaults()
pth := path.Join(opts.BasePath, opts.Path)
tmpl := template.Must(template.New("redoc").Parse(redocTemplate))
tmpl := template.Must(template.New("redoc").Parse(opts.Template))
assets := bytes.NewBuffer(nil)
if err := tmpl.Execute(assets, opts); err != nil {
panic(fmt.Errorf("cannot execute template: %w", err))
}
buf := bytes.NewBuffer(nil)
_ = tmpl.Execute(buf, opts)
b := buf.Bytes()
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.URL.Path == pth {
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write(b)
return
}
if next == nil {
rw.Header().Set("Content-Type", "text/plain")
rw.WriteHeader(http.StatusNotFound)
_, _ = rw.Write([]byte(fmt.Sprintf("%q not found", pth)))
return
}
next.ServeHTTP(rw, r)
})
return serveUI(pth, assets.Bytes(), next)
}
const (

View File

@ -19,10 +19,10 @@ import (
"reflect"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/logger"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/runtime"
)
// UntypedRequestBinder binds and validates the data from a http request
@ -31,6 +31,7 @@ type UntypedRequestBinder struct {
Parameters map[string]spec.Parameter
Formats strfmt.Registry
paramBinders map[string]*untypedParamBinder
debugLogf func(string, ...any) // a logging function to debug context and all components using it
}
// NewUntypedRequestBinder creates a new binder for reading a request.
@ -44,6 +45,7 @@ func NewUntypedRequestBinder(parameters map[string]spec.Parameter, spec *spec.Sw
paramBinders: binders,
Spec: spec,
Formats: formats,
debugLogf: debugLogfFunc(nil),
}
}
@ -52,10 +54,10 @@ func (o *UntypedRequestBinder) Bind(request *http.Request, routeParams RoutePara
val := reflect.Indirect(reflect.ValueOf(data))
isMap := val.Kind() == reflect.Map
var result []error
debugLog("binding %d parameters for %s %s", len(o.Parameters), request.Method, request.URL.EscapedPath())
o.debugLogf("binding %d parameters for %s %s", len(o.Parameters), request.Method, request.URL.EscapedPath())
for fieldName, param := range o.Parameters {
binder := o.paramBinders[fieldName]
debugLog("binding parameter %s for %s %s", fieldName, request.Method, request.URL.EscapedPath())
o.debugLogf("binding parameter %s for %s %s", fieldName, request.Method, request.URL.EscapedPath())
var target reflect.Value
if !isMap {
binder.Name = fieldName
@ -65,7 +67,7 @@ func (o *UntypedRequestBinder) Bind(request *http.Request, routeParams RoutePara
if isMap {
tpe := binder.Type()
if tpe == nil {
if param.Schema.Type.Contains("array") {
if param.Schema.Type.Contains(typeArray) {
tpe = reflect.TypeOf([]interface{}{})
} else {
tpe = reflect.TypeOf(map[string]interface{}{})
@ -102,3 +104,14 @@ func (o *UntypedRequestBinder) Bind(request *http.Request, routeParams RoutePara
return nil
}
// SetLogger allows for injecting a logger to catch debug entries.
//
// The logger is enabled in DEBUG mode only.
func (o *UntypedRequestBinder) SetLogger(lg logger.Logger) {
o.debugLogf = debugLogfFunc(lg)
}
func (o *UntypedRequestBinder) setDebugLogf(fn func(string, ...any)) {
o.debugLogf = fn
}

View File

@ -17,10 +17,12 @@ package middleware
import (
"fmt"
"net/http"
"net/url"
fpath "path"
"regexp"
"strings"
"github.com/go-openapi/runtime/logger"
"github.com/go-openapi/runtime/security"
"github.com/go-openapi/swag"
@ -67,10 +69,10 @@ func (r RouteParams) GetOK(name string) ([]string, bool, bool) {
return nil, false, false
}
// NewRouter creates a new context aware router middleware
// NewRouter creates a new context-aware router middleware
func NewRouter(ctx *Context, next http.Handler) http.Handler {
if ctx.router == nil {
ctx.router = DefaultRouter(ctx.spec, ctx.api)
ctx.router = DefaultRouter(ctx.spec, ctx.api, WithDefaultRouterLoggerFunc(ctx.debugLogf))
}
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
@ -103,41 +105,75 @@ type RoutableAPI interface {
DefaultConsumes() string
}
// Router represents a swagger aware router
// Router represents a swagger-aware router
type Router interface {
Lookup(method, path string) (*MatchedRoute, bool)
OtherMethods(method, path string) []string
}
type defaultRouteBuilder struct {
spec *loads.Document
analyzer *analysis.Spec
api RoutableAPI
records map[string][]denco.Record
spec *loads.Document
analyzer *analysis.Spec
api RoutableAPI
records map[string][]denco.Record
debugLogf func(string, ...any) // a logging function to debug context and all components using it
}
type defaultRouter struct {
spec *loads.Document
routers map[string]*denco.Router
spec *loads.Document
routers map[string]*denco.Router
debugLogf func(string, ...any) // a logging function to debug context and all components using it
}
func newDefaultRouteBuilder(spec *loads.Document, api RoutableAPI) *defaultRouteBuilder {
func newDefaultRouteBuilder(spec *loads.Document, api RoutableAPI, opts ...DefaultRouterOpt) *defaultRouteBuilder {
var o defaultRouterOpts
for _, apply := range opts {
apply(&o)
}
if o.debugLogf == nil {
o.debugLogf = debugLogfFunc(nil) // defaults to standard logger
}
return &defaultRouteBuilder{
spec: spec,
analyzer: analysis.New(spec.Spec()),
api: api,
records: make(map[string][]denco.Record),
spec: spec,
analyzer: analysis.New(spec.Spec()),
api: api,
records: make(map[string][]denco.Record),
debugLogf: o.debugLogf,
}
}
// DefaultRouter creates a default implemenation of the router
func DefaultRouter(spec *loads.Document, api RoutableAPI) Router {
builder := newDefaultRouteBuilder(spec, api)
// DefaultRouterOpt allows to inject optional behavior to the default router.
type DefaultRouterOpt func(*defaultRouterOpts)
type defaultRouterOpts struct {
debugLogf func(string, ...any)
}
// WithDefaultRouterLogger sets the debug logger for the default router.
//
// This is enabled only in DEBUG mode.
func WithDefaultRouterLogger(lg logger.Logger) DefaultRouterOpt {
return func(o *defaultRouterOpts) {
o.debugLogf = debugLogfFunc(lg)
}
}
// WithDefaultRouterLoggerFunc sets a logging debug method for the default router.
func WithDefaultRouterLoggerFunc(fn func(string, ...any)) DefaultRouterOpt {
return func(o *defaultRouterOpts) {
o.debugLogf = fn
}
}
// DefaultRouter creates a default implementation of the router
func DefaultRouter(spec *loads.Document, api RoutableAPI, opts ...DefaultRouterOpt) Router {
builder := newDefaultRouteBuilder(spec, api, opts...)
if spec != nil {
for method, paths := range builder.analyzer.Operations() {
for path, operation := range paths {
fp := fpath.Join(spec.BasePath(), path)
debugLog("adding route %s %s %q", method, fp, operation.ID)
builder.debugLogf("adding route %s %s %q", method, fp, operation.ID)
builder.AddRoute(method, fp, operation)
}
}
@ -319,24 +355,24 @@ func (m *MatchedRoute) NeedsAuth() bool {
func (d *defaultRouter) Lookup(method, path string) (*MatchedRoute, bool) {
mth := strings.ToUpper(method)
debugLog("looking up route for %s %s", method, path)
d.debugLogf("looking up route for %s %s", method, path)
if Debug {
if len(d.routers) == 0 {
debugLog("there are no known routers")
d.debugLogf("there are no known routers")
}
for meth := range d.routers {
debugLog("got a router for %s", meth)
d.debugLogf("got a router for %s", meth)
}
}
if router, ok := d.routers[mth]; ok {
if m, rp, ok := router.Lookup(fpath.Clean(path)); ok && m != nil {
if entry, ok := m.(*routeEntry); ok {
debugLog("found a route for %s %s with %d parameters", method, path, len(entry.Parameters))
d.debugLogf("found a route for %s %s with %d parameters", method, path, len(entry.Parameters))
var params RouteParams
for _, p := range rp {
v, err := pathUnescape(p.Value)
v, err := url.PathUnescape(p.Value)
if err != nil {
debugLog("failed to escape %q: %v", p.Value, err)
d.debugLogf("failed to escape %q: %v", p.Value, err)
v = p.Value
}
// a workaround to handle fragment/composing parameters until they are supported in denco router
@ -356,10 +392,10 @@ func (d *defaultRouter) Lookup(method, path string) (*MatchedRoute, bool) {
return &MatchedRoute{routeEntry: *entry, Params: params}, true
}
} else {
debugLog("couldn't find a route by path for %s %s", method, path)
d.debugLogf("couldn't find a route by path for %s %s", method, path)
}
} else {
debugLog("couldn't find a route by method for %s %s", method, path)
d.debugLogf("couldn't find a route by method for %s %s", method, path)
}
return nil, false
}
@ -378,6 +414,10 @@ func (d *defaultRouter) OtherMethods(method, path string) []string {
return methods
}
func (d *defaultRouter) SetLogger(lg logger.Logger) {
d.debugLogf = debugLogfFunc(lg)
}
// convert swagger parameters per path segment into a denco parameter as multiple parameters per segment are not supported in denco
var pathConverter = regexp.MustCompile(`{(.+?)}([^/]*)`)
@ -413,7 +453,7 @@ func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Oper
bp = bp[:len(bp)-1]
}
debugLog("operation: %#v", *operation)
d.debugLogf("operation: %#v", *operation)
if handler, ok := d.api.HandlerFor(method, strings.TrimPrefix(path, bp)); ok {
consumes := d.analyzer.ConsumesFor(operation)
produces := d.analyzer.ProducesFor(operation)
@ -428,6 +468,8 @@ func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Oper
produces = append(produces, defProduces)
}
requestBinder := NewUntypedRequestBinder(parameters, d.spec.Spec(), d.api.Formats())
requestBinder.setDebugLogf(d.debugLogf)
record := denco.NewRecord(pathConverter.ReplaceAllString(path, ":$1"), &routeEntry{
BasePath: bp,
PathPattern: path,
@ -439,7 +481,7 @@ func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Oper
Producers: d.api.ProducersFor(normalizeOffers(produces)),
Parameters: parameters,
Formats: d.api.Formats(),
Binder: NewUntypedRequestBinder(parameters, d.spec.Spec(), d.api.Formats()),
Binder: requestBinder,
Authenticators: d.buildAuthenticators(operation),
Authorizer: d.api.Authorizer(),
})
@ -449,11 +491,11 @@ func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Oper
func (d *defaultRouteBuilder) buildAuthenticators(operation *spec.Operation) RouteAuthenticators {
requirements := d.analyzer.SecurityRequirementsFor(operation)
var auths []RouteAuthenticator
auths := make([]RouteAuthenticator, 0, len(requirements))
for _, reqs := range requirements {
var schemes []string
schemes := make([]string, 0, len(reqs))
scopes := make(map[string][]string, len(reqs))
var scopeSlices [][]string
scopeSlices := make([][]string, 0, len(reqs))
for _, req := range reqs {
schemes = append(schemes, req.Name)
scopes[req.Name] = req.Scopes
@ -482,7 +524,8 @@ func (d *defaultRouteBuilder) Build() *defaultRouter {
routers[method] = router
}
return &defaultRouter{
spec: d.spec,
routers: routers,
spec: d.spec,
routers: routers,
debugLogf: d.debugLogf,
}
}

View File

@ -19,30 +19,84 @@ import (
"path"
)
// Spec creates a middleware to serve a swagger spec.
// This allows for altering the spec before starting the http listener.
// This can be useful if you want to serve the swagger spec from another path than /swagger.json
const (
contentTypeHeader = "Content-Type"
applicationJSON = "application/json"
)
// SpecOption can be applied to the Spec serving middleware
type SpecOption func(*specOptions)
var defaultSpecOptions = specOptions{
Path: "",
Document: "swagger.json",
}
type specOptions struct {
Path string
Document string
}
func specOptionsWithDefaults(opts []SpecOption) specOptions {
o := defaultSpecOptions
for _, apply := range opts {
apply(&o)
}
return o
}
// Spec creates a middleware to serve a swagger spec as a JSON document.
//
func Spec(basePath string, b []byte, next http.Handler) http.Handler {
// This allows for altering the spec before starting the http listener.
//
// The basePath argument indicates the path of the spec document (defaults to "/").
// Additional SpecOption can be used to change the name of the document (defaults to "swagger.json").
func Spec(basePath string, b []byte, next http.Handler, opts ...SpecOption) http.Handler {
if basePath == "" {
basePath = "/"
}
pth := path.Join(basePath, "swagger.json")
o := specOptionsWithDefaults(opts)
pth := path.Join(basePath, o.Path, o.Document)
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.URL.Path == pth {
rw.Header().Set("Content-Type", "application/json")
if path.Clean(r.URL.Path) == pth {
rw.Header().Set(contentTypeHeader, applicationJSON)
rw.WriteHeader(http.StatusOK)
//#nosec
_, _ = rw.Write(b)
return
}
if next == nil {
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusNotFound)
if next != nil {
next.ServeHTTP(rw, r)
return
}
next.ServeHTTP(rw, r)
rw.Header().Set(contentTypeHeader, applicationJSON)
rw.WriteHeader(http.StatusNotFound)
})
}
// WithSpecPath sets the path to be joined to the base path of the Spec middleware.
//
// This is empty by default.
func WithSpecPath(pth string) SpecOption {
return func(o *specOptions) {
o.Path = pth
}
}
// WithSpecDocument sets the name of the JSON document served as a spec.
//
// By default, this is "swagger.json"
func WithSpecDocument(doc string) SpecOption {
return func(o *specOptions) {
if doc == "" {
return
}
o.Document = doc
}
}

View File

@ -8,40 +8,65 @@ import (
"path"
)
// SwaggerUIOpts configures the Swaggerui middlewares
// SwaggerUIOpts configures the SwaggerUI middleware
type SwaggerUIOpts struct {
// BasePath for the UI path, defaults to: /
// BasePath for the API, defaults to: /
BasePath string
// Path combines with BasePath for the full UI path, defaults to: docs
// Path combines with BasePath to construct the path to the UI, defaults to: "docs".
Path string
// SpecURL the url to find the spec for
// SpecURL is the URL of the spec document.
//
// Defaults to: /swagger.json
SpecURL string
// Title for the documentation site, default to: API documentation
Title string
// Template specifies a custom template to serve the UI
Template string
// OAuthCallbackURL the url called after OAuth2 login
OAuthCallbackURL string
// The three components needed to embed swagger-ui
SwaggerURL string
// SwaggerURL points to the js that generates the SwaggerUI site.
//
// Defaults to: https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js
SwaggerURL string
SwaggerPresetURL string
SwaggerStylesURL string
Favicon32 string
Favicon16 string
// Title for the documentation site, default to: API documentation
Title string
}
// EnsureDefaults in case some options are missing
func (r *SwaggerUIOpts) EnsureDefaults() {
if r.BasePath == "" {
r.BasePath = "/"
r.ensureDefaults()
if r.Template == "" {
r.Template = swaggeruiTemplate
}
if r.Path == "" {
r.Path = "docs"
}
if r.SpecURL == "" {
r.SpecURL = "/swagger.json"
}
func (r *SwaggerUIOpts) EnsureDefaultsOauth2() {
r.ensureDefaults()
if r.Template == "" {
r.Template = swaggerOAuthTemplate
}
}
func (r *SwaggerUIOpts) ensureDefaults() {
common := toCommonUIOptions(r)
common.EnsureDefaults()
fromCommonToAnyOptions(common, r)
// swaggerui-specifics
if r.OAuthCallbackURL == "" {
r.OAuthCallbackURL = path.Join(r.BasePath, r.Path, "oauth2-callback")
}
@ -60,40 +85,22 @@ func (r *SwaggerUIOpts) EnsureDefaults() {
if r.Favicon32 == "" {
r.Favicon32 = swaggerFavicon32Latest
}
if r.Title == "" {
r.Title = "API documentation"
}
}
// SwaggerUI creates a middleware to serve a documentation site for a swagger spec.
//
// This allows for altering the spec before starting the http listener.
func SwaggerUI(opts SwaggerUIOpts, next http.Handler) http.Handler {
opts.EnsureDefaults()
pth := path.Join(opts.BasePath, opts.Path)
tmpl := template.Must(template.New("swaggerui").Parse(swaggeruiTemplate))
tmpl := template.Must(template.New("swaggerui").Parse(opts.Template))
assets := bytes.NewBuffer(nil)
if err := tmpl.Execute(assets, opts); err != nil {
panic(fmt.Errorf("cannot execute template: %w", err))
}
buf := bytes.NewBuffer(nil)
_ = tmpl.Execute(buf, &opts)
b := buf.Bytes()
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if path.Join(r.URL.Path) == pth {
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write(b)
return
}
if next == nil {
rw.Header().Set("Content-Type", "text/plain")
rw.WriteHeader(http.StatusNotFound)
_, _ = rw.Write([]byte(fmt.Sprintf("%q not found", pth)))
return
}
next.ServeHTTP(rw, r)
})
return serveUI(pth, assets.Bytes(), next)
}
const (

View File

@ -4,37 +4,20 @@ import (
"bytes"
"fmt"
"net/http"
"path"
"text/template"
)
func SwaggerUIOAuth2Callback(opts SwaggerUIOpts, next http.Handler) http.Handler {
opts.EnsureDefaults()
opts.EnsureDefaultsOauth2()
pth := opts.OAuthCallbackURL
tmpl := template.Must(template.New("swaggeroauth").Parse(swaggerOAuthTemplate))
tmpl := template.Must(template.New("swaggeroauth").Parse(opts.Template))
assets := bytes.NewBuffer(nil)
if err := tmpl.Execute(assets, opts); err != nil {
panic(fmt.Errorf("cannot execute template: %w", err))
}
buf := bytes.NewBuffer(nil)
_ = tmpl.Execute(buf, &opts)
b := buf.Bytes()
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if path.Join(r.URL.Path) == pth {
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write(b)
return
}
if next == nil {
rw.Header().Set("Content-Type", "text/plain")
rw.WriteHeader(http.StatusNotFound)
_, _ = rw.Write([]byte(fmt.Sprintf("%q not found", pth)))
return
}
next.ServeHTTP(rw, r)
})
return serveUI(pth, assets.Bytes(), next)
}
const (

View File

@ -0,0 +1,173 @@
package middleware
import (
"bytes"
"encoding/gob"
"fmt"
"net/http"
"path"
"strings"
)
const (
// constants that are common to all UI-serving middlewares
defaultDocsPath = "docs"
defaultDocsURL = "/swagger.json"
defaultDocsTitle = "API Documentation"
)
// uiOptions defines common options for UI serving middlewares.
type uiOptions struct {
// BasePath for the UI, defaults to: /
BasePath string
// Path combines with BasePath to construct the path to the UI, defaults to: "docs".
Path string
// SpecURL is the URL of the spec document.
//
// Defaults to: /swagger.json
SpecURL string
// Title for the documentation site, default to: API documentation
Title string
// Template specifies a custom template to serve the UI
Template string
}
// toCommonUIOptions converts any UI option type to retain the common options.
//
// This uses gob encoding/decoding to convert common fields from one struct to another.
func toCommonUIOptions(opts interface{}) uiOptions {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
dec := gob.NewDecoder(&buf)
var o uiOptions
err := enc.Encode(opts)
if err != nil {
panic(err)
}
err = dec.Decode(&o)
if err != nil {
panic(err)
}
return o
}
func fromCommonToAnyOptions[T any](source uiOptions, target *T) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
dec := gob.NewDecoder(&buf)
err := enc.Encode(source)
if err != nil {
panic(err)
}
err = dec.Decode(target)
if err != nil {
panic(err)
}
}
// UIOption can be applied to UI serving middleware, such as Context.APIHandler or
// Context.APIHandlerSwaggerUI to alter the defaut behavior.
type UIOption func(*uiOptions)
func uiOptionsWithDefaults(opts []UIOption) uiOptions {
var o uiOptions
for _, apply := range opts {
apply(&o)
}
return o
}
// WithUIBasePath sets the base path from where to serve the UI assets.
//
// By default, Context middleware sets this value to the API base path.
func WithUIBasePath(base string) UIOption {
return func(o *uiOptions) {
if !strings.HasPrefix(base, "/") {
base = "/" + base
}
o.BasePath = base
}
}
// WithUIPath sets the path from where to serve the UI assets (i.e. /{basepath}/{path}.
func WithUIPath(pth string) UIOption {
return func(o *uiOptions) {
o.Path = pth
}
}
// WithUISpecURL sets the path from where to serve swagger spec document.
//
// This may be specified as a full URL or a path.
//
// By default, this is "/swagger.json"
func WithUISpecURL(specURL string) UIOption {
return func(o *uiOptions) {
o.SpecURL = specURL
}
}
// WithUITitle sets the title of the UI.
//
// By default, Context middleware sets this value to the title found in the API spec.
func WithUITitle(title string) UIOption {
return func(o *uiOptions) {
o.Title = title
}
}
// WithTemplate allows to set a custom template for the UI.
//
// UI middleware will panic if the template does not parse or execute properly.
func WithTemplate(tpl string) UIOption {
return func(o *uiOptions) {
o.Template = tpl
}
}
// EnsureDefaults in case some options are missing
func (r *uiOptions) EnsureDefaults() {
if r.BasePath == "" {
r.BasePath = "/"
}
if r.Path == "" {
r.Path = defaultDocsPath
}
if r.SpecURL == "" {
r.SpecURL = defaultDocsURL
}
if r.Title == "" {
r.Title = defaultDocsTitle
}
}
// serveUI creates a middleware that serves a templated asset as text/html.
func serveUI(pth string, assets []byte, next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if path.Clean(r.URL.Path) == pth {
rw.Header().Set(contentTypeHeader, "text/html; charset=utf-8")
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write(assets)
return
}
if next != nil {
next.ServeHTTP(rw, r)
return
}
rw.Header().Set(contentTypeHeader, "text/plain")
rw.WriteHeader(http.StatusNotFound)
_, _ = rw.Write([]byte(fmt.Sprintf("%q not found", pth)))
})
}

View File

@ -197,30 +197,31 @@ func (d *API) Validate() error {
// validateWith validates the registrations in this API against the provided spec analyzer
func (d *API) validate() error {
var consumes []string
consumes := make([]string, 0, len(d.consumers))
for k := range d.consumers {
consumes = append(consumes, k)
}
var produces []string
produces := make([]string, 0, len(d.producers))
for k := range d.producers {
produces = append(produces, k)
}
var authenticators []string
authenticators := make([]string, 0, len(d.authenticators))
for k := range d.authenticators {
authenticators = append(authenticators, k)
}
var operations []string
operations := make([]string, 0, len(d.operations))
for m, v := range d.operations {
for p := range v {
operations = append(operations, fmt.Sprintf("%s %s", strings.ToUpper(m), p))
}
}
var definedAuths []string
for k := range d.spec.Spec().SecurityDefinitions {
secDefinitions := d.spec.Spec().SecurityDefinitions
definedAuths := make([]string, 0, len(secDefinitions))
for k := range secDefinitions {
definedAuths = append(definedAuths, k)
}
@ -267,7 +268,7 @@ func (d *API) verify(name string, registrations []string, expectations []string)
delete(expected, k)
}
var unregistered []string
unregistered := make([]string, 0, len(expected))
for k := range expected {
unregistered = append(unregistered, k)
}

View File

@ -35,7 +35,6 @@ type validation struct {
// ContentType validates the content type of a request
func validateContentType(allowed []string, actual string) error {
debugLog("validating content type for %q against [%s]", actual, strings.Join(allowed, ", "))
if len(allowed) == 0 {
return nil
}
@ -57,13 +56,13 @@ func validateContentType(allowed []string, actual string) error {
}
func validateRequest(ctx *Context, request *http.Request, route *MatchedRoute) *validation {
debugLog("validating request %s %s", request.Method, request.URL.EscapedPath())
validate := &validation{
context: ctx,
request: request,
route: route,
bound: make(map[string]interface{}),
}
validate.debugLogf("validating request %s %s", request.Method, request.URL.EscapedPath())
validate.contentType()
if len(validate.result) == 0 {
@ -76,8 +75,12 @@ func validateRequest(ctx *Context, request *http.Request, route *MatchedRoute) *
return validate
}
func (v *validation) debugLogf(format string, args ...any) {
v.context.debugLogf(format, args...)
}
func (v *validation) parameters() {
debugLog("validating request parameters for %s %s", v.request.Method, v.request.URL.EscapedPath())
v.debugLogf("validating request parameters for %s %s", v.request.Method, v.request.URL.EscapedPath())
if result := v.route.Binder.Bind(v.request, v.route.Params, v.route.Consumer, v.bound); result != nil {
if result.Error() == "validation failure list" {
for _, e := range result.(*errors.Validation).Value.([]interface{}) {
@ -91,7 +94,7 @@ func (v *validation) parameters() {
func (v *validation) contentType() {
if len(v.result) == 0 && runtime.HasBody(v.request) {
debugLog("validating body content type for %s %s", v.request.Method, v.request.URL.EscapedPath())
v.debugLogf("validating body content type for %s %s", v.request.Method, v.request.URL.EscapedPath())
ct, _, req, err := v.context.ContentType(v.request)
if err != nil {
v.result = append(v.result, err)
@ -100,6 +103,7 @@ func (v *validation) contentType() {
}
if len(v.result) == 0 {
v.debugLogf("validating content type for %q against [%s]", ct, strings.Join(v.route.Consumes, ", "))
if err := validateContentType(v.route.Consumes, ct); err != nil {
v.result = append(v.result, err)
}

View File

@ -16,6 +16,8 @@ package runtime
import (
"bufio"
"context"
"errors"
"io"
"net/http"
"strings"
@ -96,10 +98,16 @@ func (p *peekingReader) Read(d []byte) (int, error) {
if p == nil {
return 0, io.EOF
}
if p.underlying == nil {
return 0, io.ErrUnexpectedEOF
}
return p.underlying.Read(d)
}
func (p *peekingReader) Close() error {
if p.underlying == nil {
return errors.New("reader already closed")
}
p.underlying = nil
if p.orig != nil {
return p.orig.Close()
@ -107,9 +115,11 @@ func (p *peekingReader) Close() error {
return nil
}
// JSONRequest creates a new http request with json headers set
// JSONRequest creates a new http request with json headers set.
//
// It uses context.Background.
func JSONRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequest(method, urlStr, body)
req, err := http.NewRequestWithContext(context.Background(), method, urlStr, body)
if err != nil {
return nil, err
}

View File

@ -25,12 +25,13 @@ import (
)
const (
query = "query"
header = "header"
query = "query"
header = "header"
accessTokenParam = "access_token"
)
// HttpAuthenticator is a function that authenticates a HTTP request
func HttpAuthenticator(handler func(*http.Request) (bool, interface{}, error)) runtime.Authenticator {
func HttpAuthenticator(handler func(*http.Request) (bool, interface{}, error)) runtime.Authenticator { //nolint:revive,stylecheck
return runtime.AuthenticatorFunc(func(params interface{}) (bool, interface{}, error) {
if request, ok := params.(*http.Request); ok {
return handler(request)
@ -158,7 +159,7 @@ func APIKeyAuth(name, in string, authenticate TokenAuthentication) runtime.Authe
inl := strings.ToLower(in)
if inl != query && inl != header {
// panic because this is most likely a typo
panic(errors.New(500, "api key auth: in value needs to be either \"query\" or \"header\"."))
panic(errors.New(500, "api key auth: in value needs to be either \"query\" or \"header\""))
}
var getToken func(*http.Request) string
@ -186,7 +187,7 @@ func APIKeyAuthCtx(name, in string, authenticate TokenAuthenticationCtx) runtime
inl := strings.ToLower(in)
if inl != query && inl != header {
// panic because this is most likely a typo
panic(errors.New(500, "api key auth: in value needs to be either \"query\" or \"header\"."))
panic(errors.New(500, "api key auth: in value needs to be either \"query\" or \"header\""))
}
var getToken func(*http.Request) string
@ -226,12 +227,12 @@ func BearerAuth(name string, authenticate ScopedTokenAuthentication) runtime.Aut
}
if token == "" {
qs := r.Request.URL.Query()
token = qs.Get("access_token")
token = qs.Get(accessTokenParam)
}
//#nosec
ct, _, _ := runtime.ContentType(r.Request.Header)
if token == "" && (ct == "application/x-www-form-urlencoded" || ct == "multipart/form-data") {
token = r.Request.FormValue("access_token")
token = r.Request.FormValue(accessTokenParam)
}
if token == "" {
@ -256,12 +257,12 @@ func BearerAuthCtx(name string, authenticate ScopedTokenAuthenticationCtx) runti
}
if token == "" {
qs := r.Request.URL.Query()
token = qs.Get("access_token")
token = qs.Get(accessTokenParam)
}
//#nosec
ct, _, _ := runtime.ContentType(r.Request.Header)
if token == "" && (ct == "application/x-www-form-urlencoded" || ct == "multipart/form-data") {
token = r.Request.FormValue("access_token")
token = r.Request.FormValue(accessTokenParam)
}
if token == "" {

View File

@ -1,2 +1 @@
secrets.yml
coverage.out
*.out

View File

@ -11,7 +11,7 @@ linters-settings:
threshold: 200
goconst:
min-len: 2
min-occurrences: 2
min-occurrences: 3
linters:
enable-all: true
@ -40,3 +40,22 @@ linters:
- tparallel
- thelper
- ifshort
- exhaustruct
- varnamelen
- gci
- depguard
- errchkjson
- inamedparam
- nonamedreturns
- musttag
- ireturn
- forcetypeassert
- cyclop
# deprecated linters
- deadcode
- interfacer
- scopelint
- varcheck
- structcheck
- golint
- nosnakecase

View File

@ -1,8 +1,5 @@
# OAI object model
# OpenAPI v2 object model [![Build Status](https://github.com/go-openapi/spec/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/spec/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/spec/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/spec)
[![Build Status](https://travis-ci.org/go-openapi/spec.svg?branch=master)](https://travis-ci.org/go-openapi/spec)
<!-- [![Build status](https://ci.appveyor.com/api/projects/status/x377t5o9ennm847o/branch/master?svg=true)](https://ci.appveyor.com/project/casualjim/go-openapi/spec/branch/master) -->
[![codecov](https://codecov.io/gh/go-openapi/spec/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/spec)
[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/spec/master/LICENSE)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/spec.svg)](https://pkg.go.dev/github.com/go-openapi/spec)
@ -32,3 +29,26 @@ The object model for OpenAPI specification documents.
> 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

View File

@ -1,32 +0,0 @@
version: "0.1.{build}"
clone_folder: C:\go-openapi\spec
shallow_clone: true # for startup speed
pull_requests:
do_not_increment_build_number: true
#skip_tags: true
#skip_branch_with_pr: true
# appveyor.yml
build: off
environment:
GOPATH: c:\gopath
stack: go 1.15
test_script:
- go test -v -timeout 20m ./...
deploy: off
notifications:
- provider: Slack
incoming_webhook: https://hooks.slack.com/services/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
auth_token:
secure: Sf7kZf7ZGbnwWUMpffHwMu5A0cHkLK2MYY32LNTPj4+/3qC3Ghl7+9v4TSLOqOlCwdRNjOGblAq7s+GDJed6/xgRQl1JtCi1klzZNrYX4q01pgTPvvGcwbBkIYgeMaPeIRcK9OZnud7sRXdttozgTOpytps2U6Js32ip7uj5mHSg2ub0FwoSJwlS6dbezZ8+eDhoha0F/guY99BEwx8Bd+zROrT2TFGsSGOFGN6wFc7moCqTHO/YkWib13a2QNXqOxCCVBy/lt76Wp+JkeFppjHlzs/2lP3EAk13RIUAaesdEUHvIHrzCyNJEd3/+KO2DzsWOYfpktd+KBCvgaYOsoo7ubdT3IROeAegZdCgo/6xgCEsmFc9ZcqCfN5yNx2A+BZ2Vwmpws+bQ1E1+B5HDzzaiLcYfG4X2O210QVGVDLWsv1jqD+uPYeHY2WRfh5ZsIUFvaqgUEnwHwrK44/8REAhQavt1QAj5uJpsRd7CkRVPWRNK+yIky+wgbVUFEchRNmS55E7QWf+W4+4QZkQi7vUTMc9nbTUu2Es9NfvfudOpM2wZbn98fjpb/qq/nRv6Bk+ca+7XD5/IgNLMbWp2ouDdzbiHLCOfDUiHiDJhLfFZx9Bwo7ZwfzeOlbrQX66bx7xRKYmOe4DLrXhNcpbsMa8qbfxlZRCmYbubB/Y8h4=
channel: bots
on_build_success: false
on_build_failure: true
on_build_status_changed: true

File diff suppressed because one or more lines are too long

17
vendor/github.com/go-openapi/spec/embed.go generated vendored Normal file
View File

@ -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"))
}

View File

@ -27,7 +27,6 @@ import (
// 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
@ -58,7 +57,7 @@ func ExpandSpec(spec *Swagger, options *ExpandOptions) error {
if !options.SkipSchemas {
for key, definition := range spec.Definitions {
parentRefs := make([]string, 0, 10)
parentRefs = append(parentRefs, fmt.Sprintf("#/definitions/%s", key))
parentRefs = append(parentRefs, "#/definitions/"+key)
def, err := expandSchema(definition, parentRefs, resolver, specBasePath)
if resolver.shouldStopOnError(err) {
@ -103,15 +102,21 @@ const rootBase = ".root"
// baseForRoot loads in the cache the root document and produces a fake ".root" base path entry
// for further $ref resolution
//
// Setting the cache is optional and this parameter may safely be left to nil.
func baseForRoot(root interface{}, cache ResolutionCache) string {
if root == nil {
return ""
}
// 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
@ -208,7 +213,19 @@ func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, ba
}
if target.Ref.String() != "" {
return expandSchemaRef(target, parentRefs, resolver, basePath)
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 {
@ -520,21 +537,25 @@ func getRefAndSchema(input interface{}) (*Ref, *Schema, error) {
}
func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePath string) error {
ref, _, err := getRefAndSchema(input)
ref, sch, err := getRefAndSchema(input)
if err != nil {
return err
}
if ref == nil {
if ref == nil && sch == nil { // nothing to do
return nil
}
parentRefs := make([]string, 0, 10)
if err = resolver.deref(input, parentRefs, basePath); resolver.shouldStopOnError(err) {
return err
if ref != nil {
// dereference this $ref
if err = resolver.deref(input, parentRefs, basePath); resolver.shouldStopOnError(err) {
return err
}
ref, sch, _ = getRefAndSchema(input)
}
ref, sch, _ := getRefAndSchema(input)
if ref.String() != "" {
transitiveResolver := resolver.transitiveResolver(basePath, *ref)
basePath = resolver.updateBasePath(transitiveResolver, basePath)
@ -546,6 +567,7 @@ func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePa
if ref != nil {
*ref = Ref{}
}
return nil
}
@ -555,38 +577,29 @@ func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePa
return ern
}
switch {
case resolver.isCircular(&rebasedRef, basePath, parentRefs...):
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
}
case !resolver.options.SkipSchemas:
// schema expanded to a $ref in another root
sch.Ref = rebasedRef
debugLog("rebased to: %s", sch.Ref.String())
default:
// skip schema expansion but rebase $ref to schema
sch.Ref = denormalizeRef(&rebasedRef, resolver.context.basePath, resolver.context.rootID)
}
}
// $ref expansion or rebasing is performed by expandSchema below
if ref != nil {
*ref = Ref{}
}
// expand schema
if !resolver.options.SkipSchemas {
s, err := expandSchema(*sch, parentRefs, resolver, basePath)
if resolver.shouldStopOnError(err) {
return err
}
if s == nil {
// guard for when continuing on error
return nil
}
// 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
}

View File

@ -40,5 +40,5 @@ func repairURI(in string) (*url.URL, string) {
return u, ""
}
func fixWindowsURI(u *url.URL, in string) {
func fixWindowsURI(_ *url.URL, _ string) {
}

View File

@ -217,9 +217,12 @@ func (o *Operation) AddParam(param *Parameter) *Operation {
for i, p := range o.Parameters {
if p.Name == param.Name && p.In == param.In {
params := append(o.Parameters[:i], *param)
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
}
}

View File

@ -84,27 +84,27 @@ type ParamProps struct {
// 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.
// - 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 {

View File

@ -168,14 +168,7 @@ func (r *schemaLoader) load(refURL *url.URL) (interface{}, url.URL, bool, error)
normalized := normalizeBase(pth)
debugLog("loading doc from: %s", normalized)
unescaped, err := url.PathUnescape(normalized)
if err != nil {
return nil, url.URL{}, false, err
}
u := url.URL{Path: unescaped}
data, fromCache := r.cache.Get(u.RequestURI())
data, fromCache := r.cache.Get(normalized)
if fromCache {
return data, toFetch, fromCache, nil
}

View File

@ -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": {}
}

1607
vendor/github.com/go-openapi/spec/schemas/v2/schema.json generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,7 @@ import (
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 schema
// JSONSchemaURL the url for the json schema
JSONSchemaURL = "http://json-schema.org/draft-04/schema#"
)
@ -41,7 +41,7 @@ func MustLoadJSONSchemaDraft04() *Schema {
// JSONSchemaDraft04 loads the json schema document for json shema draft04
func JSONSchemaDraft04() (*Schema, error) {
b, err := Asset("jsonschema-draft-04.json")
b, err := jsonschemaDraft04JSONBytes()
if err != nil {
return nil, err
}
@ -65,7 +65,7 @@ func MustLoadSwagger20Schema() *Schema {
// Swagger20Schema loads the swagger 2.0 schema from the embedded assets
func Swagger20Schema() (*Schema, error) {
b, err := Asset("v2/schema.json")
b, err := v2SchemaJSONBytes()
if err != nil {
return nil, err
}

View File

@ -253,7 +253,7 @@ func (s SchemaOrBool) MarshalJSON() ([]byte, error) {
// UnmarshalJSON converts this bool or schema object from a JSON structure
func (s *SchemaOrBool) UnmarshalJSON(data []byte) error {
var nw SchemaOrBool
if len(data) >= 4 {
if len(data) > 0 {
if data[0] == '{' {
var sch Schema
if err := json.Unmarshal(data, &sch); err != nil {
@ -261,7 +261,7 @@ func (s *SchemaOrBool) UnmarshalJSON(data []byte) error {
}
nw.Schema = &sch
}
nw.Allows = !(data[0] == 'f' && data[1] == 'a' && data[2] == 'l' && data[3] == 's' && data[4] == 'e')
nw.Allows = !bytes.Equal(data, []byte("false"))
}
*s = nw
return nil

View File

@ -1,8 +0,0 @@
//go:build !go1.19
// +build !go1.19
package spec
import "net/url"
var parseURL = url.Parse

View File

@ -1,6 +1,3 @@
//go:build go1.19
// +build go1.19
package spec
import "net/url"

View File

@ -4,56 +4,58 @@ linters-settings:
golint:
min-confidence: 0
gocyclo:
min-complexity: 31
min-complexity: 45
maligned:
suggest-new: true
dupl:
threshold: 100
threshold: 200
goconst:
min-len: 2
min-occurrences: 4
min-occurrences: 3
linters:
enable:
- revive
- goimports
- gosec
enable-all: true
disable:
- maligned
- unparam
- unconvert
- predeclared
- prealloc
- misspell
# disable:
# - maligned
# - lll
# - gochecknoinits
# - gochecknoglobals
# - godox
# - gocognit
# - whitespace
# - wsl
# - funlen
# - wrapcheck
# - testpackage
# - nlreturn
# - gofumpt
# - goerr113
# - gci
# - gomnd
# - godot
# - exhaustivestruct
# - paralleltest
# - varnamelen
# - ireturn
# - exhaustruct
# #- thelper
issues:
exclude-rules:
- path: bson.go
text: "should be .*ObjectID"
linters:
- golint
- stylecheck
- lll
- gochecknoinits
- gochecknoglobals
- funlen
- godox
- gocognit
- whitespace
- wsl
- wrapcheck
- testpackage
- nlreturn
- gomnd
- exhaustivestruct
- goerr113
- errorlint
- nestif
- godot
- gofumpt
- paralleltest
- tparallel
- thelper
- ifshort
- exhaustruct
- varnamelen
- gci
- depguard
- errchkjson
- inamedparam
- nonamedreturns
- musttag
- ireturn
- forcetypeassert
- cyclop
# deprecated linters
- deadcode
- interfacer
- scopelint
- varcheck
- structcheck
- golint
- nosnakecase

View File

@ -1,8 +1,7 @@
# Strfmt [![Build Status](https://travis-ci.org/go-openapi/strfmt.svg?branch=master)](https://travis-ci.org/go-openapi/strfmt) [![codecov](https://codecov.io/gh/go-openapi/strfmt/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/strfmt) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
# Strfmt [![Build Status](https://github.com/go-openapi/strfmt/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/strfmt/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/strfmt/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/strfmt)
[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/strfmt/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/go-openapi/strfmt?status.svg)](http://godoc.org/github.com/go-openapi/strfmt)
[![GolangCI](https://golangci.com/badges/github.com/go-openapi/strfmt.svg)](https://golangci.com)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/strfmt)](https://goreportcard.com/report/github.com/go-openapi/strfmt)
This package exposes a registry of data types to support string formats in the go-openapi toolkit.

View File

@ -39,10 +39,10 @@ func IsBSONObjectID(str string) bool {
// ObjectId represents a BSON object ID (alias to go.mongodb.org/mongo-driver/bson/primitive.ObjectID)
//
// swagger:strfmt bsonobjectid
type ObjectId bsonprim.ObjectID //nolint:revive
type ObjectId bsonprim.ObjectID //nolint:revive,stylecheck
// NewObjectId creates a ObjectId from a Hex String
func NewObjectId(hex string) ObjectId { //nolint:revive
func NewObjectId(hex string) ObjectId { //nolint:revive,stylecheck
oid, err := bsonprim.ObjectIDFromHex(hex)
if err != nil {
panic(err)
@ -135,7 +135,7 @@ func (id *ObjectId) UnmarshalBSON(data []byte) error {
// BSON document if the error is nil.
func (id ObjectId) MarshalBSONValue() (bsontype.Type, []byte, error) {
oid := bsonprim.ObjectID(id)
return bsontype.ObjectID, oid[:], nil
return bson.TypeObjectID, oid[:], nil
}
// UnmarshalBSONValue is an interface implemented by types that can unmarshal a

View File

@ -25,6 +25,7 @@ import (
"strings"
"github.com/asaskevich/govalidator"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
)
@ -57,24 +58,35 @@ const (
// - long top-level domain names (e.g. example.london) are permitted
// - symbol unicode points are permitted (e.g. emoji) (not for top-level domain)
HostnamePattern = `^([a-zA-Z0-9\p{S}\p{L}]((-?[a-zA-Z0-9\p{S}\p{L}]{0,62})?)|([a-zA-Z0-9\p{S}\p{L}](([a-zA-Z0-9-\p{S}\p{L}]{0,61}[a-zA-Z0-9\p{S}\p{L}])?)(\.)){1,}([a-zA-Z\p{L}]){2,63})$`
// UUIDPattern Regex for UUID that allows uppercase
UUIDPattern = `(?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$`
// UUID3Pattern Regex for UUID3 that allows uppercase
UUID3Pattern = `(?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$`
// UUID4Pattern Regex for UUID4 that allows uppercase
UUID4Pattern = `(?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$`
// UUID5Pattern Regex for UUID5 that allows uppercase
UUID5Pattern = `(?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$`
// json null type
jsonNull = "null"
)
const (
// UUIDPattern Regex for UUID that allows uppercase
//
// Deprecated: strfmt no longer uses regular expressions to validate UUIDs.
UUIDPattern = `(?i)(^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$)|(^[0-9a-f]{32}$)`
// UUID3Pattern Regex for UUID3 that allows uppercase
//
// Deprecated: strfmt no longer uses regular expressions to validate UUIDs.
UUID3Pattern = `(?i)(^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$)|(^[0-9a-f]{12}3[0-9a-f]{3}?[0-9a-f]{16}$)`
// UUID4Pattern Regex for UUID4 that allows uppercase
//
// Deprecated: strfmt no longer uses regular expressions to validate UUIDs.
UUID4Pattern = `(?i)(^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$)|(^[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}$)`
// UUID5Pattern Regex for UUID5 that allows uppercase
//
// Deprecated: strfmt no longer uses regular expressions to validate UUIDs.
UUID5Pattern = `(?i)(^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$)|(^[0-9a-f]{12}5[0-9a-f]{3}[89ab][0-9a-f]{15}$)`
)
var (
rxHostname = regexp.MustCompile(HostnamePattern)
rxUUID = regexp.MustCompile(UUIDPattern)
rxUUID3 = regexp.MustCompile(UUID3Pattern)
rxUUID4 = regexp.MustCompile(UUID4Pattern)
rxUUID5 = regexp.MustCompile(UUID5Pattern)
)
// IsHostname returns true when the string is a valid hostname
@ -99,24 +111,28 @@ func IsHostname(str string) bool {
return valid
}
// IsUUID returns true is the string matches a UUID, upper case is allowed
// IsUUID returns true is the string matches a UUID (in any version, including v6 and v7), upper case is allowed
func IsUUID(str string) bool {
return rxUUID.MatchString(str)
_, err := uuid.Parse(str)
return err == nil
}
// IsUUID3 returns true is the string matches a UUID, upper case is allowed
// IsUUID3 returns true is the string matches a UUID v3, upper case is allowed
func IsUUID3(str string) bool {
return rxUUID3.MatchString(str)
id, err := uuid.Parse(str)
return err == nil && id.Version() == uuid.Version(3)
}
// IsUUID4 returns true is the string matches a UUID, upper case is allowed
// IsUUID4 returns true is the string matches a UUID v4, upper case is allowed
func IsUUID4(str string) bool {
return rxUUID4.MatchString(str)
id, err := uuid.Parse(str)
return err == nil && id.Version() == uuid.Version(4)
}
// IsUUID5 returns true is the string matches a UUID, upper case is allowed
// IsUUID5 returns true is the string matches a UUID v5, upper case is allowed
func IsUUID5(str string) bool {
return rxUUID5.MatchString(str)
id, err := uuid.Parse(str)
return err == nil && id.Version() == uuid.Version(5)
}
// IsEmail validates an email address.

View File

@ -16,6 +16,7 @@ package strfmt
import (
"encoding"
stderrors "errors"
"fmt"
"reflect"
"strings"
@ -94,7 +95,7 @@ func NewSeededFormats(seeds []knownFormat, normalizer NameNormalizer) Registry {
}
// MapStructureHookFunc is a decode hook function for mapstructure
func (f *defaultFormats) MapStructureHookFunc() mapstructure.DecodeHookFunc { //nolint:gocyclo,cyclop
func (f *defaultFormats) MapStructureHookFunc() mapstructure.DecodeHookFunc {
return func(from reflect.Type, to reflect.Type, obj interface{}) (interface{}, error) {
if from.Kind() != reflect.String {
return obj, nil
@ -117,7 +118,7 @@ func (f *defaultFormats) MapStructureHookFunc() mapstructure.DecodeHookFunc { //
case "datetime":
input := data
if len(input) == 0 {
return nil, fmt.Errorf("empty string is an invalid datetime format")
return nil, stderrors.New("empty string is an invalid datetime format")
}
return ParseDateTime(input)
case "duration":

View File

@ -76,6 +76,8 @@ const (
ISO8601TimeWithReducedPrecisionLocaltime = "2006-01-02T15:04"
// ISO8601TimeUniversalSortableDateTimePattern represents a ISO8601 universal sortable date time pattern.
ISO8601TimeUniversalSortableDateTimePattern = "2006-01-02 15:04:05"
// short form of ISO8601TimeUniversalSortableDateTimePattern
ISO8601TimeUniversalSortableDateTimePatternShortForm = "2006-01-02"
// DateTimePattern pattern to match for the date-time format from http://tools.ietf.org/html/rfc3339#section-5.6
DateTimePattern = `^([0-9]{2}):([0-9]{2}):([0-9]{2})(.[0-9]+)?(z|([+-][0-9]{2}:[0-9]{2}))$`
)
@ -84,7 +86,7 @@ var (
rxDateTime = regexp.MustCompile(DateTimePattern)
// DateTimeFormats is the collection of formats used by ParseDateTime()
DateTimeFormats = []string{RFC3339Micro, RFC3339MicroNoColon, RFC3339Millis, RFC3339MillisNoColon, time.RFC3339, time.RFC3339Nano, ISO8601LocalTime, ISO8601TimeWithReducedPrecision, ISO8601TimeWithReducedPrecisionLocaltime, ISO8601TimeUniversalSortableDateTimePattern}
DateTimeFormats = []string{RFC3339Micro, RFC3339MicroNoColon, RFC3339Millis, RFC3339MillisNoColon, time.RFC3339, time.RFC3339Nano, ISO8601LocalTime, ISO8601TimeWithReducedPrecision, ISO8601TimeWithReducedPrecisionLocaltime, ISO8601TimeUniversalSortableDateTimePattern, ISO8601TimeUniversalSortableDateTimePatternShortForm}
// MarshalFormat sets the time resolution format used for marshaling time (set to milliseconds)
MarshalFormat = RFC3339Millis
@ -245,7 +247,7 @@ func (t DateTime) MarshalBSONValue() (bsontype.Type, []byte, error) {
buf := make([]byte, 8)
binary.LittleEndian.PutUint64(buf, uint64(i64))
return bsontype.DateTime, buf, nil
return bson.TypeDateTime, buf, nil
}
// UnmarshalBSONValue is an interface implemented by types that can unmarshal a
@ -253,7 +255,7 @@ func (t DateTime) MarshalBSONValue() (bsontype.Type, []byte, error) {
// assumed to be valid. UnmarshalBSONValue must copy the BSON value bytes if it
// wishes to retain the data after returning.
func (t *DateTime) UnmarshalBSONValue(tpe bsontype.Type, data []byte) error {
if tpe == bsontype.Null {
if tpe == bson.TypeNull {
*t = DateTime{}
return nil
}

View File

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

View File

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

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

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

@ -1,12 +1,14 @@
linters-settings:
govet:
check-shadowing: true
golint:
min-confidence: 0
gocyclo:
min-complexity: 50
min-complexity: 45
maligned:
suggest-new: true
dupl:
threshold: 100
threshold: 200
goconst:
min-len: 2
min-occurrences: 3
@ -15,36 +17,45 @@ linters:
enable-all: true
disable:
- maligned
- unparam
- lll
- gochecknoinits
- gochecknoglobals
- funlen
- godox
- gocognit
- whitespace
- wsl
- funlen
- gochecknoglobals
- gochecknoinits
- scopelint
- wrapcheck
- exhaustivestruct
- exhaustive
- nlreturn
- testpackage
- gci
- gofumpt
- goerr113
- nlreturn
- gomnd
- tparallel
- exhaustivestruct
- goerr113
- errorlint
- nestif
- godot
- tparallel
- gofumpt
- paralleltest
- cyclop # because we have gocyclo already
# TODO: review the linters below. We disabled them to make the CI pass first.
- ireturn
- varnamelen
- forcetypeassert
- tparallel
- thelper
# Disable deprecated linters.
# They will be removed from golangci-lint in future.
- ifshort
- exhaustruct
- varnamelen
- gci
- depguard
- errchkjson
- inamedparam
- nonamedreturns
- musttag
- ireturn
- forcetypeassert
- cyclop
# deprecated linters
- deadcode
- interfacer
- golint
- scopelint
- varcheck
- structcheck
- golint
- nosnakecase

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

@ -0,0 +1,31 @@
# Benchmark
Validating the Kubernetes Swagger API
## v0.22.6: 60,000,000 allocs
```
goos: linux
goarch: amd64
pkg: github.com/go-openapi/validate
cpu: AMD Ryzen 7 5800X 8-Core Processor
Benchmark_KubernetesSpec/validating_kubernetes_API-16 1 8549863982 ns/op 7067424936 B/op 59583275 allocs/op
```
## After refact PR: minor but noticable improvements: 25,000,000 allocs
```
go test -bench Spec
goos: linux
goarch: amd64
pkg: github.com/go-openapi/validate
cpu: AMD Ryzen 7 5800X 8-Core Processor
Benchmark_KubernetesSpec/validating_kubernetes_API-16 1 4064535557 ns/op 3379715592 B/op 25320330 allocs/op
```
## After reduce GC pressure PR: 17,000,000 allocs
```
goos: linux
goarch: amd64
pkg: github.com/go-openapi/validate
cpu: AMD Ryzen 7 5800X 8-Core Processor
Benchmark_KubernetesSpec/validating_kubernetes_API-16 1 3758414145 ns/op 2593881496 B/op 17111373 allocs/op
```

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