mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 10:52:28 -05:00 
			
		
		
		
	[bugfix] Fix Swagger spec and add test script (#2698)
* Add Swagger spec test script * Fix Swagger spec errors not related to statuses with polls * Add API tests that post a status with a poll * Fix creating a status with a poll from form params * Fix Swagger spec errors related to statuses with polls (this is the last error) * Fix Swagger spec warnings not related to unused definitions * Suppress a duplicate list update params definition that was somehow causing wrong param names * Add Swagger test to CI - updates Drone config - vendorizes go-swagger - fixes a file extension issue that caused the test script to generate JSON instead of YAML with the vendorized version * Put `Sample: ` on its own line everywhere * Remove unused id param from emojiCategoriesGet * Add 5 more pairs of profile fields to account update API Swagger * Remove Swagger prefix from dummy fields It makes the generated code look weird * Manually annotate params for statusCreate operation * Fix all remaining Swagger spec warnings - Change some models into operation parameters - Ignore models that already correspond to manually documented operation parameters but can't be trivially changed (those with file fields) * Documented that creating a status with scheduled_at isn't implemented yet * sign drone.yml * Fix filter API Swagger errors * fixup! Fix filter API Swagger errors --------- Co-authored-by: tobi <tobi.smethurst@protonmail.com>
This commit is contained in:
		
					parent
					
						
							
								68c8fe67cc
							
						
					
				
			
			
				commit
				
					
						fc3741365c
					
				
			
		
					 672 changed files with 135624 additions and 713 deletions
				
			
		
							
								
								
									
										488
									
								
								vendor/github.com/go-openapi/runtime/middleware/router.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										488
									
								
								vendor/github.com/go-openapi/runtime/middleware/router.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,488 @@ | |||
| // 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 middleware | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	fpath "path" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/go-openapi/runtime/security" | ||||
| 	"github.com/go-openapi/swag" | ||||
| 
 | ||||
| 	"github.com/go-openapi/analysis" | ||||
| 	"github.com/go-openapi/errors" | ||||
| 	"github.com/go-openapi/loads" | ||||
| 	"github.com/go-openapi/spec" | ||||
| 	"github.com/go-openapi/strfmt" | ||||
| 
 | ||||
| 	"github.com/go-openapi/runtime" | ||||
| 	"github.com/go-openapi/runtime/middleware/denco" | ||||
| ) | ||||
| 
 | ||||
| // RouteParam is a object to capture route params in a framework agnostic way. | ||||
| // implementations of the muxer should use these route params to communicate with the | ||||
| // swagger framework | ||||
| type RouteParam struct { | ||||
| 	Name  string | ||||
| 	Value string | ||||
| } | ||||
| 
 | ||||
| // RouteParams the collection of route params | ||||
| type RouteParams []RouteParam | ||||
| 
 | ||||
| // Get gets the value for the route param for the specified key | ||||
| func (r RouteParams) Get(name string) string { | ||||
| 	vv, _, _ := r.GetOK(name) | ||||
| 	if len(vv) > 0 { | ||||
| 		return vv[len(vv)-1] | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
| 
 | ||||
| // GetOK gets the value but also returns booleans to indicate if a key or value | ||||
| // is present. This aids in validation and satisfies an interface in use there | ||||
| // | ||||
| // The returned values are: data, has key, has value | ||||
| func (r RouteParams) GetOK(name string) ([]string, bool, bool) { | ||||
| 	for _, p := range r { | ||||
| 		if p.Name == name { | ||||
| 			return []string{p.Value}, true, p.Value != "" | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, false, false | ||||
| } | ||||
| 
 | ||||
| // 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) | ||||
| 	} | ||||
| 
 | ||||
| 	return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||
| 		if _, rCtx, ok := ctx.RouteInfo(r); ok { | ||||
| 			next.ServeHTTP(rw, rCtx) | ||||
| 			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.analyzer.RequiredProduces(), nil, errors.MethodNotAllowed(r.Method, others)) | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		ctx.Respond(rw, r, ctx.analyzer.RequiredProduces(), nil, errors.NotFound("path %s was not found", r.URL.EscapedPath())) | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // RoutableAPI represents an interface for things that can serve | ||||
| // as a provider of implementations for the swagger router | ||||
| type RoutableAPI interface { | ||||
| 	HandlerFor(string, string) (http.Handler, bool) | ||||
| 	ServeErrorFor(string) func(http.ResponseWriter, *http.Request, error) | ||||
| 	ConsumersFor([]string) map[string]runtime.Consumer | ||||
| 	ProducersFor([]string) map[string]runtime.Producer | ||||
| 	AuthenticatorsFor(map[string]spec.SecurityScheme) map[string]runtime.Authenticator | ||||
| 	Authorizer() runtime.Authorizer | ||||
| 	Formats() strfmt.Registry | ||||
| 	DefaultProduces() string | ||||
| 	DefaultConsumes() string | ||||
| } | ||||
| 
 | ||||
| // 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 | ||||
| } | ||||
| 
 | ||||
| type defaultRouter struct { | ||||
| 	spec    *loads.Document | ||||
| 	routers map[string]*denco.Router | ||||
| } | ||||
| 
 | ||||
| func newDefaultRouteBuilder(spec *loads.Document, api RoutableAPI) *defaultRouteBuilder { | ||||
| 	return &defaultRouteBuilder{ | ||||
| 		spec:     spec, | ||||
| 		analyzer: analysis.New(spec.Spec()), | ||||
| 		api:      api, | ||||
| 		records:  make(map[string][]denco.Record), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // DefaultRouter creates a default implemenation of the router | ||||
| func DefaultRouter(spec *loads.Document, api RoutableAPI) Router { | ||||
| 	builder := newDefaultRouteBuilder(spec, api) | ||||
| 	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.AddRoute(method, fp, operation) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return builder.Build() | ||||
| } | ||||
| 
 | ||||
| // RouteAuthenticator is an authenticator that can compose several authenticators together. | ||||
| // It also knows when it contains an authenticator that allows for anonymous pass through. | ||||
| // Contains a group of 1 or more authenticators that have a logical AND relationship | ||||
| type RouteAuthenticator struct { | ||||
| 	Authenticator  map[string]runtime.Authenticator | ||||
| 	Schemes        []string | ||||
| 	Scopes         map[string][]string | ||||
| 	allScopes      []string | ||||
| 	commonScopes   []string | ||||
| 	allowAnonymous bool | ||||
| } | ||||
| 
 | ||||
| func (ra *RouteAuthenticator) AllowsAnonymous() bool { | ||||
| 	return ra.allowAnonymous | ||||
| } | ||||
| 
 | ||||
| // AllScopes returns a list of unique scopes that is the combination | ||||
| // of all the scopes in the requirements | ||||
| func (ra *RouteAuthenticator) AllScopes() []string { | ||||
| 	return ra.allScopes | ||||
| } | ||||
| 
 | ||||
| // CommonScopes returns a list of unique scopes that are common in all the | ||||
| // scopes in the requirements | ||||
| func (ra *RouteAuthenticator) CommonScopes() []string { | ||||
| 	return ra.commonScopes | ||||
| } | ||||
| 
 | ||||
| // Authenticate Authenticator interface implementation | ||||
| func (ra *RouteAuthenticator) Authenticate(req *http.Request, route *MatchedRoute) (bool, interface{}, error) { | ||||
| 	if ra.allowAnonymous { | ||||
| 		route.Authenticator = ra | ||||
| 		return true, nil, nil | ||||
| 	} | ||||
| 	// iterate in proper order | ||||
| 	var lastResult interface{} | ||||
| 	for _, scheme := range ra.Schemes { | ||||
| 		if authenticator, ok := ra.Authenticator[scheme]; ok { | ||||
| 			applies, princ, err := authenticator.Authenticate(&security.ScopedAuthRequest{ | ||||
| 				Request:        req, | ||||
| 				RequiredScopes: ra.Scopes[scheme], | ||||
| 			}) | ||||
| 			if !applies { | ||||
| 				return false, nil, nil | ||||
| 			} | ||||
| 			if err != nil { | ||||
| 				route.Authenticator = ra | ||||
| 				return true, nil, err | ||||
| 			} | ||||
| 			lastResult = princ | ||||
| 		} | ||||
| 	} | ||||
| 	route.Authenticator = ra | ||||
| 	return true, lastResult, nil | ||||
| } | ||||
| 
 | ||||
| func stringSliceUnion(slices ...[]string) []string { | ||||
| 	unique := make(map[string]struct{}) | ||||
| 	var result []string | ||||
| 	for _, slice := range slices { | ||||
| 		for _, entry := range slice { | ||||
| 			if _, ok := unique[entry]; ok { | ||||
| 				continue | ||||
| 			} | ||||
| 			unique[entry] = struct{}{} | ||||
| 			result = append(result, entry) | ||||
| 		} | ||||
| 	} | ||||
| 	return result | ||||
| } | ||||
| 
 | ||||
| func stringSliceIntersection(slices ...[]string) []string { | ||||
| 	unique := make(map[string]int) | ||||
| 	var intersection []string | ||||
| 
 | ||||
| 	total := len(slices) | ||||
| 	var emptyCnt int | ||||
| 	for _, slice := range slices { | ||||
| 		if len(slice) == 0 { | ||||
| 			emptyCnt++ | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		for _, entry := range slice { | ||||
| 			unique[entry]++ | ||||
| 			if unique[entry] == total-emptyCnt { // this entry appeared in all the non-empty slices | ||||
| 				intersection = append(intersection, entry) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return intersection | ||||
| } | ||||
| 
 | ||||
| // RouteAuthenticators represents a group of authenticators that represent a logical OR | ||||
| type RouteAuthenticators []RouteAuthenticator | ||||
| 
 | ||||
| // AllowsAnonymous returns true when there is an authenticator that means optional auth | ||||
| func (ras RouteAuthenticators) AllowsAnonymous() bool { | ||||
| 	for _, ra := range ras { | ||||
| 		if ra.AllowsAnonymous() { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| // Authenticate method implemention so this collection can be used as authenticator | ||||
| func (ras RouteAuthenticators) Authenticate(req *http.Request, route *MatchedRoute) (bool, interface{}, error) { | ||||
| 	var lastError error | ||||
| 	var allowsAnon bool | ||||
| 	var anonAuth RouteAuthenticator | ||||
| 
 | ||||
| 	for _, ra := range ras { | ||||
| 		if ra.AllowsAnonymous() { | ||||
| 			anonAuth = ra | ||||
| 			allowsAnon = true | ||||
| 			continue | ||||
| 		} | ||||
| 		applies, usr, err := ra.Authenticate(req, route) | ||||
| 		if !applies || err != nil || usr == nil { | ||||
| 			if err != nil { | ||||
| 				lastError = err | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
| 		return applies, usr, nil | ||||
| 	} | ||||
| 
 | ||||
| 	if allowsAnon && lastError == nil { | ||||
| 		route.Authenticator = &anonAuth | ||||
| 		return true, nil, lastError | ||||
| 	} | ||||
| 	return lastError != nil, nil, lastError | ||||
| } | ||||
| 
 | ||||
| type routeEntry struct { | ||||
| 	PathPattern    string | ||||
| 	BasePath       string | ||||
| 	Operation      *spec.Operation | ||||
| 	Consumes       []string | ||||
| 	Consumers      map[string]runtime.Consumer | ||||
| 	Produces       []string | ||||
| 	Producers      map[string]runtime.Producer | ||||
| 	Parameters     map[string]spec.Parameter | ||||
| 	Handler        http.Handler | ||||
| 	Formats        strfmt.Registry | ||||
| 	Binder         *UntypedRequestBinder | ||||
| 	Authenticators RouteAuthenticators | ||||
| 	Authorizer     runtime.Authorizer | ||||
| } | ||||
| 
 | ||||
| // MatchedRoute represents the route that was matched in this request | ||||
| type MatchedRoute struct { | ||||
| 	routeEntry | ||||
| 	Params        RouteParams | ||||
| 	Consumer      runtime.Consumer | ||||
| 	Producer      runtime.Producer | ||||
| 	Authenticator *RouteAuthenticator | ||||
| } | ||||
| 
 | ||||
| // HasAuth returns true when the route has a security requirement defined | ||||
| func (m *MatchedRoute) HasAuth() bool { | ||||
| 	return len(m.Authenticators) > 0 | ||||
| } | ||||
| 
 | ||||
| // NeedsAuth returns true when the request still | ||||
| // needs to perform authentication | ||||
| func (m *MatchedRoute) NeedsAuth() bool { | ||||
| 	return m.HasAuth() && m.Authenticator == nil | ||||
| } | ||||
| 
 | ||||
| func (d *defaultRouter) Lookup(method, path string) (*MatchedRoute, bool) { | ||||
| 	mth := strings.ToUpper(method) | ||||
| 	debugLog("looking up route for %s %s", method, path) | ||||
| 	if Debug { | ||||
| 		if len(d.routers) == 0 { | ||||
| 			debugLog("there are no known routers") | ||||
| 		} | ||||
| 		for meth := range d.routers { | ||||
| 			debugLog("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)) | ||||
| 				var params RouteParams | ||||
| 				for _, p := range rp { | ||||
| 					v, err := pathUnescape(p.Value) | ||||
| 					if err != nil { | ||||
| 						debugLog("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 | ||||
| 					// check if this parameter is a fragment within a path segment | ||||
| 					if xpos := strings.Index(entry.PathPattern, fmt.Sprintf("{%s}", p.Name)) + len(p.Name) + 2; xpos < len(entry.PathPattern) && entry.PathPattern[xpos] != '/' { | ||||
| 						// extract fragment parameters | ||||
| 						ep := strings.Split(entry.PathPattern[xpos:], "/")[0] | ||||
| 						pnames, pvalues := decodeCompositParams(p.Name, v, ep, nil, nil) | ||||
| 						for i, pname := range pnames { | ||||
| 							params = append(params, RouteParam{Name: pname, Value: pvalues[i]}) | ||||
| 						} | ||||
| 					} else { | ||||
| 						// use the parameter directly | ||||
| 						params = append(params, RouteParam{Name: p.Name, Value: v}) | ||||
| 					} | ||||
| 				} | ||||
| 				return &MatchedRoute{routeEntry: *entry, Params: params}, true | ||||
| 			} | ||||
| 		} else { | ||||
| 			debugLog("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) | ||||
| 	} | ||||
| 	return nil, false | ||||
| } | ||||
| 
 | ||||
| func (d *defaultRouter) OtherMethods(method, path string) []string { | ||||
| 	mn := strings.ToUpper(method) | ||||
| 	var methods []string | ||||
| 	for k, v := range d.routers { | ||||
| 		if k != mn { | ||||
| 			if _, _, ok := v.Lookup(fpath.Clean(path)); ok { | ||||
| 				methods = append(methods, k) | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return methods | ||||
| } | ||||
| 
 | ||||
| // convert swagger parameters per path segment into a denco parameter as multiple parameters per segment are not supported in denco | ||||
| var pathConverter = regexp.MustCompile(`{(.+?)}([^/]*)`) | ||||
| 
 | ||||
| func decodeCompositParams(name string, value string, pattern string, names []string, values []string) ([]string, []string) { | ||||
| 	pleft := strings.Index(pattern, "{") | ||||
| 	names = append(names, name) | ||||
| 	if pleft < 0 { | ||||
| 		if strings.HasSuffix(value, pattern) { | ||||
| 			values = append(values, value[:len(value)-len(pattern)]) | ||||
| 		} else { | ||||
| 			values = append(values, "") | ||||
| 		} | ||||
| 	} else { | ||||
| 		toskip := pattern[:pleft] | ||||
| 		pright := strings.Index(pattern, "}") | ||||
| 		vright := strings.Index(value, toskip) | ||||
| 		if vright >= 0 { | ||||
| 			values = append(values, value[:vright]) | ||||
| 		} else { | ||||
| 			values = append(values, "") | ||||
| 			value = "" | ||||
| 		} | ||||
| 		return decodeCompositParams(pattern[pleft+1:pright], value[vright+len(toskip):], pattern[pright+1:], names, values) | ||||
| 	} | ||||
| 	return names, values | ||||
| } | ||||
| 
 | ||||
| func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Operation) { | ||||
| 	mn := strings.ToUpper(method) | ||||
| 
 | ||||
| 	bp := fpath.Clean(d.spec.BasePath()) | ||||
| 	if len(bp) > 0 && bp[len(bp)-1] == '/' { | ||||
| 		bp = bp[:len(bp)-1] | ||||
| 	} | ||||
| 
 | ||||
| 	debugLog("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) | ||||
| 		parameters := d.analyzer.ParamsFor(method, strings.TrimPrefix(path, bp)) | ||||
| 
 | ||||
| 		// add API defaults if not part of the spec | ||||
| 		if defConsumes := d.api.DefaultConsumes(); defConsumes != "" && !swag.ContainsStringsCI(consumes, defConsumes) { | ||||
| 			consumes = append(consumes, defConsumes) | ||||
| 		} | ||||
| 
 | ||||
| 		if defProduces := d.api.DefaultProduces(); defProduces != "" && !swag.ContainsStringsCI(produces, defProduces) { | ||||
| 			produces = append(produces, defProduces) | ||||
| 		} | ||||
| 
 | ||||
| 		record := denco.NewRecord(pathConverter.ReplaceAllString(path, ":$1"), &routeEntry{ | ||||
| 			BasePath:       bp, | ||||
| 			PathPattern:    path, | ||||
| 			Operation:      operation, | ||||
| 			Handler:        handler, | ||||
| 			Consumes:       consumes, | ||||
| 			Produces:       produces, | ||||
| 			Consumers:      d.api.ConsumersFor(normalizeOffers(consumes)), | ||||
| 			Producers:      d.api.ProducersFor(normalizeOffers(produces)), | ||||
| 			Parameters:     parameters, | ||||
| 			Formats:        d.api.Formats(), | ||||
| 			Binder:         NewUntypedRequestBinder(parameters, d.spec.Spec(), d.api.Formats()), | ||||
| 			Authenticators: d.buildAuthenticators(operation), | ||||
| 			Authorizer:     d.api.Authorizer(), | ||||
| 		}) | ||||
| 		d.records[mn] = append(d.records[mn], record) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (d *defaultRouteBuilder) buildAuthenticators(operation *spec.Operation) RouteAuthenticators { | ||||
| 	requirements := d.analyzer.SecurityRequirementsFor(operation) | ||||
| 	var auths []RouteAuthenticator | ||||
| 	for _, reqs := range requirements { | ||||
| 		var schemes []string | ||||
| 		scopes := make(map[string][]string, len(reqs)) | ||||
| 		var scopeSlices [][]string | ||||
| 		for _, req := range reqs { | ||||
| 			schemes = append(schemes, req.Name) | ||||
| 			scopes[req.Name] = req.Scopes | ||||
| 			scopeSlices = append(scopeSlices, req.Scopes) | ||||
| 		} | ||||
| 
 | ||||
| 		definitions := d.analyzer.SecurityDefinitionsForRequirements(reqs) | ||||
| 		authenticators := d.api.AuthenticatorsFor(definitions) | ||||
| 		auths = append(auths, RouteAuthenticator{ | ||||
| 			Authenticator:  authenticators, | ||||
| 			Schemes:        schemes, | ||||
| 			Scopes:         scopes, | ||||
| 			allScopes:      stringSliceUnion(scopeSlices...), | ||||
| 			commonScopes:   stringSliceIntersection(scopeSlices...), | ||||
| 			allowAnonymous: len(reqs) == 1 && reqs[0].Name == "", | ||||
| 		}) | ||||
| 	} | ||||
| 	return auths | ||||
| } | ||||
| 
 | ||||
| func (d *defaultRouteBuilder) Build() *defaultRouter { | ||||
| 	routers := make(map[string]*denco.Router) | ||||
| 	for method, records := range d.records { | ||||
| 		router := denco.New() | ||||
| 		_ = router.Build(records) | ||||
| 		routers[method] = router | ||||
| 	} | ||||
| 	return &defaultRouter{ | ||||
| 		spec:    d.spec, | ||||
| 		routers: routers, | ||||
| 	} | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue