diff --git a/.openapi.yml b/.openapi.yml new file mode 100644 index 00000000..b3ef4872 --- /dev/null +++ b/.openapi.yml @@ -0,0 +1,5 @@ +--- +# https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +output-options: + nullable-type: true + name-normalizer: "ToCamelCase" diff --git a/api/api/export.go b/api/api/export.go deleted file mode 100644 index b4a8f0ae..00000000 --- a/api/api/export.go +++ /dev/null @@ -1,26 +0,0 @@ -package controllers - -import ( - "context" - serviceinterfaces "queryorchestration/api/serviceInterfaces" - "queryorchestration/internal/export" - - "github.com/go-playground/validator/v10" -) - -type ExportController struct { - serviceinterfaces.UnimplementedExportServiceServer - export export.Service - validator *validator.Validate -} - -func NewExportController(exportSvc export.Service, validator *validator.Validate) *ExportController { - return &ExportController{ - export: exportSvc, - validator: validator, - } -} - -func (s *ExportController) Trigger(ctx context.Context, req *serviceinterfaces.ExportTrigger) (*serviceinterfaces.IdMessage, error) { - return &serviceinterfaces.IdMessage{}, nil -} diff --git a/api/api/jobcollector.go b/api/api/jobcollector.go deleted file mode 100644 index de6210ad..00000000 --- a/api/api/jobcollector.go +++ /dev/null @@ -1,36 +0,0 @@ -package controllers - -import ( - "context" - serviceinterfaces "queryorchestration/api/serviceInterfaces" - "queryorchestration/internal/collector" - - "github.com/go-playground/validator/v10" - - "google.golang.org/protobuf/types/known/emptypb" -) - -type JobCollectorController struct { - serviceinterfaces.UnimplementedJobCollectorServiceServer - collector collector.Service - validator *validator.Validate -} - -func NewJobCollectorController(collSvc collector.Service, validator *validator.Validate) *JobCollectorController { - return &JobCollectorController{ - collector: collSvc, - validator: validator, - } -} - -func (s *JobCollectorController) Get(ctx context.Context, req *serviceinterfaces.IdMessage) (*serviceinterfaces.JobCollector, error) { - return &serviceinterfaces.JobCollector{}, nil -} - -func (s *JobCollectorController) Create(ctx context.Context, req *serviceinterfaces.JobCollectorCreate) (*serviceinterfaces.IdMessage, error) { - return &serviceinterfaces.IdMessage{}, nil -} - -func (s *JobCollectorController) Update(ctx context.Context, req *serviceinterfaces.JobCollectorUpdate) (*emptypb.Empty, error) { - return &emptypb.Empty{}, nil -} diff --git a/api/api/parse.go b/api/api/parse.go deleted file mode 100644 index 70521ab6..00000000 --- a/api/api/parse.go +++ /dev/null @@ -1,45 +0,0 @@ -package controllers - -import ( - serviceinterfaces "queryorchestration/api/serviceInterfaces" - "queryorchestration/internal/query" - queryprocessor "queryorchestration/internal/queryProcessor" -) - -func ParseQuery(query *query.Query) *serviceinterfaces.Query { - requiredQueries := make([]string, len(query.RequiredQueryIDs)) - for index, id := range query.RequiredQueryIDs { - requiredQueries[index] = id.String() - } - - return &serviceinterfaces.Query{ - Id: query.ID.String(), - Type: ParseQueryType(query.Type), - ActiveVersion: query.ActiveVersion, - LatestVersion: query.LatestVersion, - RequiredQueries: requiredQueries, - Config: &query.Config, - } -} - -func ParseQueryType(qType queryprocessor.Type) serviceinterfaces.QueryType { - switch qType { - case queryprocessor.TypeJsonExtractor: - return serviceinterfaces.QueryType_QUERY_TYPE_JSON_EXTRACTOR - case queryprocessor.TypeContextFull: - return serviceinterfaces.QueryType_QUERY_TYPE_CONTEXT_FULL - default: - return serviceinterfaces.QueryType_QUERY_TYPE_UNSPECIFIED - } -} - -func ParseSpecQueryType(qType serviceinterfaces.QueryType) queryprocessor.Type { - switch qType { - case serviceinterfaces.QueryType_QUERY_TYPE_JSON_EXTRACTOR: - return queryprocessor.TypeJsonExtractor - case serviceinterfaces.QueryType_QUERY_TYPE_CONTEXT_FULL: - return queryprocessor.TypeContextFull - default: - return queryprocessor.TypeContextFull - } -} diff --git a/api/api/query.go b/api/api/query.go deleted file mode 100644 index 194c0d44..00000000 --- a/api/api/query.go +++ /dev/null @@ -1,139 +0,0 @@ -package controllers - -import ( - "context" - "fmt" - serviceinterfaces "queryorchestration/api/serviceInterfaces" - "queryorchestration/internal/query" - queryprocessor "queryorchestration/internal/queryProcessor" - - "github.com/go-playground/validator/v10" - "github.com/google/uuid" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/emptypb" -) - -type QueryController struct { - serviceinterfaces.UnimplementedQueryServiceServer - query query.Service - validator *validator.Validate -} - -func NewQueryController(querySvc query.Service, validator *validator.Validate) *QueryController { - return &QueryController{ - query: querySvc, - validator: validator, - } -} - -func (s *QueryController) List(ctx context.Context, req *serviceinterfaces.QueryFilter) (*serviceinterfaces.Queries, error) { - filters := query.ListFilters{} - - queries, err := s.query.List(ctx, filters) - if err != nil { - return nil, status.Error(codes.NotFound, fmt.Sprintf("Unable to list query: %s", err)) - } - - outQueries := make([]*serviceinterfaces.Query, len(queries)) - for index, query := range queries { - outQueries[index] = ParseQuery(query) - } - - return &serviceinterfaces.Queries{ - Queries: outQueries, - }, nil -} - -func (s *QueryController) Get(ctx context.Context, req *serviceinterfaces.IdMessage) (*serviceinterfaces.Query, error) { - id, err := uuid.Parse(req.GetId()) - if err != nil { - return nil, status.Error(codes.InvalidArgument, "Invalid ID") - } - - query, err := s.query.Get(ctx, id) - if err != nil { - return nil, status.Error(codes.NotFound, fmt.Sprintf("Unable to get query: %s", err)) - } - - return ParseQuery(query), nil -} - -func (s *QueryController) Create(ctx context.Context, req *serviceinterfaces.QueryCreate) (*serviceinterfaces.IdMessage, error) { - requiredQueryIDs := make([]uuid.UUID, len(req.GetRequiredQueries())) - for index, id := range req.GetRequiredQueries() { - parsedID, err := uuid.Parse(id) - if err != nil { - return nil, status.Error(codes.InvalidArgument, "Invalid ID") - } - - requiredQueryIDs[index] = parsedID - } - - id, err := s.query.Create(ctx, &queryprocessor.Create{ - Type: ParseSpecQueryType(req.GetType()), - RequiredQueryIDs: requiredQueryIDs, - }) - if err != nil { - return nil, status.Error(codes.Internal, fmt.Sprintf("Unable to create query: %s", err)) - } - - return &serviceinterfaces.IdMessage{ - Id: id.String(), - }, nil -} - -func (s *QueryController) Update(ctx context.Context, req *serviceinterfaces.QueryUpdate) (*emptypb.Empty, error) { - id, err := uuid.Parse(req.GetId()) - if err != nil { - return nil, status.Error(codes.InvalidArgument, "Invalid ID") - } - - err = s.query.Update(ctx, &queryprocessor.Update{ - ID: id, - }) - if err != nil { - return nil, status.Error(codes.Internal, fmt.Sprintf("Unable to update query: %s", err)) - } - - return &emptypb.Empty{}, nil -} - -func (s *QueryController) Deprecate(ctx context.Context, req *serviceinterfaces.IdMessage) (*emptypb.Empty, error) { - id, err := uuid.Parse(req.GetId()) - if err != nil { - return nil, status.Error(codes.InvalidArgument, "Invalid ID") - } - - err = s.query.Deprecate(ctx, id) - if err != nil { - return nil, status.Error(codes.Internal, fmt.Sprintf("Unable to deprecate query: %s", err)) - } - - return &emptypb.Empty{}, nil -} - -func (s *QueryController) Test(ctx context.Context, req *serviceinterfaces.QueryTestRequest) (*serviceinterfaces.QueryTestResponse, error) { - queryId, err := uuid.Parse(req.GetQueryId()) - if err != nil { - return nil, status.Error(codes.InvalidArgument, "Invalid Query ID") - } - docId, err := uuid.Parse(req.GetDocumentId()) - if err != nil { - return nil, status.Error(codes.InvalidArgument, "Invalid Document ID") - } - - value, err := s.query.Test(ctx, query.Test{ - QueryID: queryId, - QueryVersion: req.GetQueryVersion(), - DocumentID: docId, - }) - if err != nil { - return nil, status.Error(codes.Internal, fmt.Sprintf("Unable to test query: %s", err)) - } - - return &serviceinterfaces.QueryTestResponse{ - Value: value, - }, nil -} diff --git a/api/queue/queryrunner.go b/api/queryRunner/queryrunner.go similarity index 100% rename from api/queue/queryrunner.go rename to api/queryRunner/queryrunner.go diff --git a/api/queue/queryRunner_test.go b/api/queryRunner/queryrunner_test.go similarity index 97% rename from api/queue/queryRunner_test.go rename to api/queryRunner/queryrunner_test.go index ae524ded..e02fe99b 100644 --- a/api/queue/queryRunner_test.go +++ b/api/queryRunner/queryrunner_test.go @@ -3,7 +3,7 @@ package controllers_test import ( "context" "encoding/json" - controllers "queryorchestration/api/queue" + controllers "queryorchestration/api/queryRunner" "queryorchestration/internal/database" "queryorchestration/internal/database/repository" "queryorchestration/internal/document" diff --git a/api/queryService/api.gen.go b/api/queryService/api.gen.go new file mode 100644 index 00000000..9657e0c8 --- /dev/null +++ b/api/queryService/api.gen.go @@ -0,0 +1,457 @@ +// Package queryservice provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version 2.4.1 DO NOT EDIT. +package queryservice + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/labstack/echo/v4" + "github.com/oapi-codegen/runtime" +) + +// Defines values for QueryType. +const ( + CONTEXTFULL QueryType = "CONTEXT_FULL" + JSONEXTRACTOR QueryType = "JSON_EXTRACTOR" +) + +// ExportTrigger Payload for triggering an export. +type ExportTrigger struct { + // ExampleProperty Example property for ExportTrigger. + ExampleProperty *string `json:"example_property,omitempty"` +} + +// IdMessage defines model for IdMessage. +type IdMessage struct { + // Id Unique identifier. + Id string `json:"id"` +} + +// JobCollector JobCollector model. +type JobCollector struct { + // ExampleProperty Example property for JobCollector. + ExampleProperty *string `json:"example_property,omitempty"` +} + +// JobCollectorCreate Payload for creating a JobCollector. +type JobCollectorCreate struct { + // ExampleProperty Example property for JobCollectorCreate. + ExampleProperty *string `json:"example_property,omitempty"` +} + +// JobCollectorUpdate Payload for updating a JobCollector. +type JobCollectorUpdate struct { + // ExampleProperty Example property for JobCollectorUpdate. + ExampleProperty *string `json:"example_property,omitempty"` +} + +// ListQueries defines model for ListQueries. +type ListQueries struct { + // Queries List of queries. + Queries []Query `json:"queries"` +} + +// Query defines model for Query. +type Query struct { + // ActiveVersion The active version of the query. + ActiveVersion int32 `json:"active_version"` + + // Config Configuration for the query. + Config *string `json:"config,omitempty"` + + // Id Unique identifier for the query. + Id string `json:"id"` + + // LatestVersion The latest version of the query. + LatestVersion int32 `json:"latest_version"` + + // RequiredQueries List of required query IDs. + RequiredQueries []string `json:"required_queries"` + + // Type Specifies the type of the query. + Type QueryType `json:"type"` +} + +// QueryCreate defines model for QueryCreate. +type QueryCreate struct { + // Config Configuration for the new query. + Config *string `json:"config,omitempty"` + + // RequiredQueries List of required query IDs. + RequiredQueries *[]string `json:"required_queries,omitempty"` + + // Type Specifies the type of the query. + Type QueryType `json:"type"` +} + +// QueryTestRequest defines model for QueryTestRequest. +type QueryTestRequest struct { + // DocumentId ID of the document to test against. + DocumentId string `json:"document_id"` + + // QueryVersion Version of the query to use for testing. + QueryVersion int32 `json:"query_version"` +} + +// QueryTestResponse defines model for QueryTestResponse. +type QueryTestResponse struct { + // Value Result of the query test. + Value string `json:"value"` +} + +// QueryType Specifies the type of the query. +type QueryType string + +// QueryUpdate defines model for QueryUpdate. +type QueryUpdate struct { + // ActiveVersion Updated active version. + ActiveVersion *int32 `json:"active_version,omitempty"` + + // Config Updated configuration for the query. + Config *string `json:"config,omitempty"` + + // RequiredQueries Updated list of required query IDs. + RequiredQueries *[]string `json:"required_queries,omitempty"` +} + +// TriggerExportJSONRequestBody defines body for TriggerExport for application/json ContentType. +type TriggerExportJSONRequestBody = ExportTrigger + +// CreateJobCollectorJSONRequestBody defines body for CreateJobCollector for application/json ContentType. +type CreateJobCollectorJSONRequestBody = JobCollectorCreate + +// UpdateJobCollectorJSONRequestBody defines body for UpdateJobCollector for application/json ContentType. +type UpdateJobCollectorJSONRequestBody = JobCollectorUpdate + +// CreateQueryJSONRequestBody defines body for CreateQuery for application/json ContentType. +type CreateQueryJSONRequestBody = QueryCreate + +// UpdateQueryJSONRequestBody defines body for UpdateQuery for application/json ContentType. +type UpdateQueryJSONRequestBody = QueryUpdate + +// TestQueryJSONRequestBody defines body for TestQuery for application/json ContentType. +type TestQueryJSONRequestBody = QueryTestRequest + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Trigger an export + // (POST /exports/trigger) + TriggerExport(ctx echo.Context) error + // Create a job collector + // (POST /job-collectors) + CreateJobCollector(ctx echo.Context) error + // Get a job collector by ID + // (GET /job-collectors/{id}) + GetJobCollectorById(ctx echo.Context, id string) error + // Update a job collector + // (PUT /job-collectors/{id}) + UpdateJobCollector(ctx echo.Context, id string) error + // List queries + // (GET /queries) + ListQueries(ctx echo.Context) error + // Create a new query + // (POST /queries) + CreateQuery(ctx echo.Context) error + // Deprecate a query + // (DELETE /queries/{id}) + DeprecateQuery(ctx echo.Context, id string) error + // Get a query by ID + // (GET /queries/{id}) + GetQueryById(ctx echo.Context, id string) error + // Update a query + // (PATCH /queries/{id}) + UpdateQuery(ctx echo.Context, id string) error + // Test a query + // (POST /queries/{id}/test) + TestQuery(ctx echo.Context, id string) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// TriggerExport converts echo context to params. +func (w *ServerInterfaceWrapper) TriggerExport(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.TriggerExport(ctx) + return err +} + +// CreateJobCollector converts echo context to params. +func (w *ServerInterfaceWrapper) CreateJobCollector(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.CreateJobCollector(ctx) + return err +} + +// GetJobCollectorById converts echo context to params. +func (w *ServerInterfaceWrapper) GetJobCollectorById(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetJobCollectorById(ctx, id) + return err +} + +// UpdateJobCollector converts echo context to params. +func (w *ServerInterfaceWrapper) UpdateJobCollector(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.UpdateJobCollector(ctx, id) + return err +} + +// ListQueries converts echo context to params. +func (w *ServerInterfaceWrapper) ListQueries(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.ListQueries(ctx) + return err +} + +// CreateQuery converts echo context to params. +func (w *ServerInterfaceWrapper) CreateQuery(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.CreateQuery(ctx) + return err +} + +// DeprecateQuery converts echo context to params. +func (w *ServerInterfaceWrapper) DeprecateQuery(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.DeprecateQuery(ctx, id) + return err +} + +// GetQueryById converts echo context to params. +func (w *ServerInterfaceWrapper) GetQueryById(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetQueryById(ctx, id) + return err +} + +// UpdateQuery converts echo context to params. +func (w *ServerInterfaceWrapper) UpdateQuery(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.UpdateQuery(ctx, id) + return err +} + +// TestQuery converts echo context to params. +func (w *ServerInterfaceWrapper) TestQuery(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.TestQuery(ctx, id) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.POST(baseURL+"/exports/trigger", wrapper.TriggerExport) + router.POST(baseURL+"/job-collectors", wrapper.CreateJobCollector) + router.GET(baseURL+"/job-collectors/:id", wrapper.GetJobCollectorById) + router.PUT(baseURL+"/job-collectors/:id", wrapper.UpdateJobCollector) + router.GET(baseURL+"/queries", wrapper.ListQueries) + router.POST(baseURL+"/queries", wrapper.CreateQuery) + router.DELETE(baseURL+"/queries/:id", wrapper.DeprecateQuery) + router.GET(baseURL+"/queries/:id", wrapper.GetQueryById) + router.PATCH(baseURL+"/queries/:id", wrapper.UpdateQuery) + router.POST(baseURL+"/queries/:id/test", wrapper.TestQuery) + +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/9RZS2/bOBD+KwR3j17bfZx8S5Ns4aLbZFN3UaAIAloc2wwkUSFHbo3C/31BUm9RkVzb", + "RXtrZHKe33wznH6ngYwSGUOMms6+Ux1sIGL2n9ffEqlwocR6Dcp84KADJRIUMqYzest2oWScrKQi6A6J", + "eE1YTMBeHNMRTZRMQKEAKxC+sSgJ4SH7umvLvHYnSH7CCq/ZYaTiLgE6oxqNQrrfF1/k8hECpPsRnfN/", + "QGu2BqOjboXgbb2fYvGUAhEcYhQr0aVGwVMqFHA6+2LE3HsUv5PLSxmGEKD0xKz6K4kkh/BkUaqKHhik", + "6pVLBQzh+SwH5ozNcUvbyV1w9vyAI58S3utIas78JEecPQMdeS80/puCytTXjXkqf6jbYG4RuSLZAaNL", + "IET25J8KVnRG/5iUZT7JanxiNO1oaQdTiu1aQM/V+tDuJLQsZQGKLTxsQWlrYdPgxQaIO0OyM8Z83IB1", + "YWccWEkVMaQzKmJ89bKMnogRDB3tRzSQ8Uqs29Iv7fdUMfO3o6eq5EYaRsMIYYCckCFofN5rd+YYr/PU", + "PPTCIT/pxJP5VR0ZLfvrKMj/HgChhTno4cdMxKiJh1aoPF51oq0kqjrmDkNDDF+fyeTvEmN7uzNSC9B4", + "B08paGyHi8sgjSDGBx/651c5MvNjBCWxyGVrJmKN3rDZGHTj/z8P6I3cVIPLC2hDyoPqoBGIqjdNO3ri", + "oxMZaw+etixMPW3kDnQaYsMH8AakYaMT2G1NhoS6uo8JBIZ/tFVnbrY4A+I0MvLffbz58HD9eXF3cbm4", + "uaMjennzYXH9efHw96f37yt6y3xZvWW/PIzD3T3e4PEjqTsXGhxI4f0Fm0sOT1K47d5tPol4JduaL27n", + "RRnV/bHxJzcq2IDGzFcNaisC18RRYGh0+M5d3M7piBbZoS/G0/HUGCoTiFki6Iy+Gk/Hr8xIw3BjnZq4", + "wVxPsJzpE+nYoUEAsUBhONra6a6ZEScAbS0zQLF2zLlpbE6cm9MzMgeNbyTfZcyMEFstLElCEdibk0ft", + "YOVoro8E64+Rfb24UKVgP7hits6+nL44mfLyNWEVNydAG50spsCJTgMTp1UahruxScnr6dQX4i0LBSdZ", + "sMhScnN6P6I6jSJmxqo8sOWbyoCCrbUpd6f2o4MLvTcXJ49y+VeQD566O72uh2rCbB98lEsSVGfgenbd", + "4dq75jwp9jxHfqU8v6uGyb2FTpps5zJh9XxUMl6Nz3N5n3wXfG9sWAP6GhgqAVubfe26S1BXSZY7IlCT", + "+VUbDG8Bq2a82c25ZRjFIkAwmPviG3vLgaKuCSVRmT2Wfs15Q1d0RGMW2abBaRMBo0o2my33voWO6Vnw", + "2Q8QDshEqDNMvPbuAirHY4lkJdOYN2HxFrCJCZOg+VUvMkY0SbGrE2pHKsKOXA3xXwVuLDMUPjRh4GQ0", + "OOEYFKTFK/lEGDgvPWVD0yB66k19mo0mR1HJcSBz/vwQ91Qmrl6+CeuLCrJkGjgxE5EIEQypCgQlWBtw", + "1cXIGUu8qsZT4RdND4anqSyO7mS5Oa8zSfbN+VQEIU+NvVWr+gE93029ttBNPSZKbgUH3l3x7rrb95yn", + "vqqv+1+p77usnLHfF/noTmql0IruziEE36LzChIFQZbrosO7hD/T2YtreY4PoPPiKc9zIWft5r78FJrP", + "w6M9pVkEj7C+VI4OGMt6k/YWLF3tDp/DipT9PvNXtq7uqtD6wPVzKNkNZ0WaakNZm5cZBpth81iFngfM", + "YcdU7G8weFXXVIMaQydJnGTS8g9OBxL4BPOtrLdZX3+DIHUMbreuKrVrU9bZt6tYbq1nwA01PwqRfL35", + "awOkuu0ejpJT68+2yR6SWtg82u3xkUshu4bvBZy5A2rrz/StkjwNioUjmFE/VSGd0Q1iomeTCUvEOPvf", + "z3Ego8n2BTXpy7Q15d3kgNNEQWhrDGVlVs3gUrNxPxompfYkqQjzPUmGyswWoaWw+jZtf7//PwAA///0", + "wy/PGCEAAA==", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/api/queryService/controllers.go b/api/queryService/controllers.go new file mode 100644 index 00000000..8ed8e431 --- /dev/null +++ b/api/queryService/controllers.go @@ -0,0 +1,27 @@ +package queryservice + +import ( + "queryorchestration/internal/collector" + "queryorchestration/internal/export" + "queryorchestration/internal/query" + + "github.com/go-playground/validator/v10" +) + +type Services struct { + Export *export.Service + JobCollector *collector.Service + Query *query.Service +} + +type Controllers struct { + validator *validator.Validate + svc *Services +} + +func NewControllers(validator *validator.Validate, services *Services) *Controllers { + return &Controllers{ + validator: validator, + svc: services, + } +} diff --git a/api/queryService/controllers_test.go b/api/queryService/controllers_test.go new file mode 100644 index 00000000..3e4be39b --- /dev/null +++ b/api/queryService/controllers_test.go @@ -0,0 +1,14 @@ +package queryservice_test + +import ( + queryservice "queryorchestration/api/queryService" + "testing" + + "github.com/go-playground/validator/v10" + "github.com/stretchr/testify/assert" +) + +func TestNewControllers(t *testing.T) { + cons := queryservice.NewControllers(validator.New(), &queryservice.Services{}) + assert.NotNil(t, cons) +} diff --git a/api/queryService/export.go b/api/queryService/export.go new file mode 100644 index 00000000..60af915c --- /dev/null +++ b/api/queryService/export.go @@ -0,0 +1,11 @@ +package queryservice + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +func (s *Controllers) TriggerExport(ctx echo.Context) error { + return ctx.JSON(http.StatusOK, map[string]string{"status": "export triggered"}) +} diff --git a/api/queryService/export_test.go b/api/queryService/export_test.go new file mode 100644 index 00000000..29dbd7d1 --- /dev/null +++ b/api/queryService/export_test.go @@ -0,0 +1,27 @@ +package queryservice_test + +import ( + "net/http" + "net/http/httptest" + queryservice "queryorchestration/api/queryService" + "strings" + "testing" + + "github.com/go-playground/validator/v10" + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" +) + +func TestTriggerExport(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("")) + rec := httptest.NewRecorder() + ctx := e.NewContext(req, rec) + + cons := queryservice.NewControllers(validator.New(), &queryservice.Services{}) + + err := cons.TriggerExport(ctx) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, rec.Code) + assert.NotEmpty(t, rec.Body.String()) +} diff --git a/api/queryService/jobcollector.go b/api/queryService/jobcollector.go new file mode 100644 index 00000000..bed4a85d --- /dev/null +++ b/api/queryService/jobcollector.go @@ -0,0 +1,19 @@ +package queryservice + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +func (s *Controllers) GetJobCollectorById(ctx echo.Context, id string) error { + return ctx.JSON(http.StatusOK, map[string]string{"status": "export triggered"}) +} + +func (s *Controllers) CreateJobCollector(ctx echo.Context) error { + return ctx.JSON(http.StatusOK, map[string]string{"status": "export triggered"}) +} + +func (s *Controllers) UpdateJobCollector(ctx echo.Context, id string) error { + return ctx.JSON(http.StatusOK, map[string]string{"status": "export triggered"}) +} diff --git a/api/queryService/jobcollector_test.go b/api/queryService/jobcollector_test.go new file mode 100644 index 00000000..b51335e9 --- /dev/null +++ b/api/queryService/jobcollector_test.go @@ -0,0 +1,60 @@ +package queryservice_test + +import ( + "net/http" + "net/http/httptest" + queryservice "queryorchestration/api/queryService" + "strings" + "testing" + + "github.com/go-playground/validator/v10" + "github.com/google/uuid" + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" +) + +func TestCreateJobCollector(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("")) + rec := httptest.NewRecorder() + ctx := e.NewContext(req, rec) + + cons := queryservice.NewControllers(validator.New(), &queryservice.Services{}) + + err := cons.CreateJobCollector(ctx) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, rec.Code) + assert.NotEmpty(t, rec.Body.String()) +} + +func TestUpdateJobCollector(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("")) + rec := httptest.NewRecorder() + ctx := e.NewContext(req, rec) + + cons := queryservice.NewControllers(validator.New(), &queryservice.Services{}) + + id := uuid.New() + + err := cons.UpdateJobCollector(ctx, id.String()) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, rec.Code) + assert.NotEmpty(t, rec.Body.String()) +} + +func TestGetJobCollector(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("")) + rec := httptest.NewRecorder() + ctx := e.NewContext(req, rec) + + cons := queryservice.NewControllers(validator.New(), &queryservice.Services{}) + + id := uuid.New() + + err := cons.GetJobCollectorById(ctx, id.String()) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, rec.Code) + assert.NotEmpty(t, rec.Body.String()) +} diff --git a/api/queryService/parse.go b/api/queryService/parse.go new file mode 100644 index 00000000..d769f15d --- /dev/null +++ b/api/queryService/parse.go @@ -0,0 +1,72 @@ +package queryservice + +import ( + "errors" + "queryorchestration/internal/query" + queryprocessor "queryorchestration/internal/queryProcessor" +) + +func parseQueries(queries []*query.Query) ([]Query, error) { + outQueries := make([]Query, len(queries)) + for index, query := range queries { + qt, err := parseQuery(query) + if err != nil { + return nil, err + } + + outQueries[index] = *qt + } + + return outQueries, nil +} + +func parseQuery(query *query.Query) (*Query, error) { + requiredQueries := make([]string, len(query.RequiredQueryIDs)) + for index, id := range query.RequiredQueryIDs { + requiredQueries[index] = id.String() + } + + qt, err := parseQueryType(query.Type) + if err != nil { + return nil, err + } + + q := &Query{ + Id: query.ID.String(), + Type: qt, + ActiveVersion: query.ActiveVersion, + LatestVersion: query.LatestVersion, + } + + if query.Config != "" { + q.Config = &query.Config + } + + if len(query.RequiredQueryIDs) > 0 { + q.RequiredQueries = requiredQueries + } + + return q, nil +} + +func parseQueryType(qType queryprocessor.Type) (QueryType, error) { + switch qType { + case queryprocessor.TypeJsonExtractor: + return JSONEXTRACTOR, nil + case queryprocessor.TypeContextFull: + return CONTEXTFULL, nil + default: + return "", errors.New("invalid query type") + } +} + +func parseSpecQueryType(qType QueryType) (queryprocessor.Type, error) { + switch qType { + case JSONEXTRACTOR: + return queryprocessor.TypeJsonExtractor, nil + case CONTEXTFULL: + return queryprocessor.TypeContextFull, nil + default: + return queryprocessor.Type(-1), errors.New("invalid query type") + } +} diff --git a/api/queryService/parse_test.go b/api/queryService/parse_test.go new file mode 100644 index 00000000..931b3c03 --- /dev/null +++ b/api/queryService/parse_test.go @@ -0,0 +1,92 @@ +package queryservice + +import ( + "queryorchestration/internal/query" + queryprocessor "queryorchestration/internal/queryProcessor" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestParseQueries(t *testing.T) { + in := []*query.Query{ + { + ID: uuid.New(), + Type: queryprocessor.TypeContextFull, + ActiveVersion: 1, + LatestVersion: 2, + RequiredQueryIDs: []uuid.UUID{ + uuid.New(), + }, + Config: "hey", + }, + } + out, err := parseQueries(in) + assert.Nil(t, err) + assert.Len(t, out, len(in)) + assert.ElementsMatch(t, []Query{ + { + Id: in[0].ID.String(), + Type: CONTEXTFULL, + ActiveVersion: 1, + LatestVersion: 2, + RequiredQueries: []string{ + in[0].RequiredQueryIDs[0].String(), + }, + Config: &in[0].Config, + }, + }, out) +} + +func TestParseQuery(t *testing.T) { + in := &query.Query{ + ID: uuid.New(), + Type: queryprocessor.TypeContextFull, + ActiveVersion: 1, + LatestVersion: 2, + RequiredQueryIDs: []uuid.UUID{ + uuid.New(), + }, + Config: "hey", + } + out, err := parseQuery(in) + assert.Nil(t, err) + assert.EqualExportedValues(t, Query{ + Id: in.ID.String(), + Type: CONTEXTFULL, + ActiveVersion: 1, + LatestVersion: 2, + RequiredQueries: []string{ + in.RequiredQueryIDs[0].String(), + }, + Config: &in.Config, + }, + *out) +} + +func TestParseQueryType(t *testing.T) { + qt, err := parseQueryType(queryprocessor.TypeContextFull) + assert.Nil(t, err) + assert.Equal(t, CONTEXTFULL, qt) + + qt, err = parseQueryType(queryprocessor.TypeJsonExtractor) + assert.Nil(t, err) + assert.Equal(t, JSONEXTRACTOR, qt) + + _, err = parseQueryType(queryprocessor.Type(-1)) + assert.NotNil(t, err) +} + +func TestParseSpecQueryType(t *testing.T) { + qt, err := parseSpecQueryType(CONTEXTFULL) + assert.Nil(t, err) + assert.Equal(t, queryprocessor.Type(queryprocessor.TypeContextFull), qt) + + qt, err = parseSpecQueryType(JSONEXTRACTOR) + assert.Nil(t, err) + assert.Equal(t, queryprocessor.Type(queryprocessor.TypeJsonExtractor), qt) + + _, err = parseSpecQueryType("invalid") + assert.NotNil(t, err) +} diff --git a/api/queryService/query.go b/api/queryService/query.go new file mode 100644 index 00000000..57a69618 --- /dev/null +++ b/api/queryService/query.go @@ -0,0 +1,143 @@ +package queryservice + +import ( + "fmt" + "net/http" + "queryorchestration/internal/query" + queryprocessor "queryorchestration/internal/queryProcessor" + + "github.com/google/uuid" + "github.com/labstack/echo/v4" +) + +func (s *Controllers) ListQueries(ctx echo.Context) error { + filters := query.ListFilters{} + + queries, err := s.svc.Query.List(ctx.Request().Context(), filters) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Unable to list query: %s", err)) + } + + outQueries, err := parseQueries(queries) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Unable to list query: %s", err)) + } + + return ctx.JSON(http.StatusOK, ListQueries{ + Queries: outQueries, + }) +} + +func (s *Controllers) GetQueryById(ctx echo.Context, id string) error { + uid, err := uuid.Parse(id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid ID") + } + + query, err := s.svc.Query.Get(ctx.Request().Context(), uid) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Unable to get query: %s", err)) + } + + qt, err := parseQuery(query) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Unable to get query: %s", err)) + } + + return ctx.JSON(http.StatusOK, qt) +} + +func (s *Controllers) CreateQuery(ctx echo.Context) error { + req := QueryCreate{} + if err := ctx.Bind(&req); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err) + } + + requiredQueryIDs := []uuid.UUID{} + if req.RequiredQueries != nil { + requiredQueryIDs := make([]uuid.UUID, len(*req.RequiredQueries)) + for index, id := range *req.RequiredQueries { + parsedID, err := uuid.Parse(id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid Required ID") + } + + requiredQueryIDs[index] = parsedID + } + } + + qt, err := parseSpecQueryType(req.Type) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid Query Type") + } + + id, err := s.svc.Query.Create(ctx.Request().Context(), &queryprocessor.Create{ + Type: qt, + RequiredQueryIDs: requiredQueryIDs, + }) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unable to create query: %s", err)) + } + + return ctx.JSON(http.StatusCreated, IdMessage{ + Id: id.String(), + }) +} + +func (s *Controllers) UpdateQuery(ctx echo.Context, id string) error { + uid, err := uuid.Parse(id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid ID") + } + + err = s.svc.Query.Update(ctx.Request().Context(), &queryprocessor.Update{ + ID: uid, + }) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unable to update query: %s", err)) + } + + return ctx.NoContent(http.StatusOK) +} + +func (s *Controllers) DeprecateQuery(ctx echo.Context, id string) error { + uid, err := uuid.Parse(id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid ID") + } + + err = s.svc.Query.Deprecate(ctx.Request().Context(), uid) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unable to deprecate query: %s", err)) + } + + return ctx.NoContent(http.StatusOK) +} + +func (s *Controllers) TestQuery(ctx echo.Context, id string) error { + queryId, err := uuid.Parse(id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid Query ID") + } + req := QueryTestRequest{} + if err := ctx.Bind(&req); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err) + } + docId, err := uuid.Parse(req.DocumentId) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid Doc ID") + } + + value, err := s.svc.Query.Test(ctx.Request().Context(), query.Test{ + QueryID: queryId, + QueryVersion: req.QueryVersion, + DocumentID: docId, + }) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unable to test query: %s", err)) + } + + return ctx.JSON(http.StatusOK, QueryTestResponse{ + Value: value, + }) +} diff --git a/api/queryService/query_test.go b/api/queryService/query_test.go new file mode 100644 index 00000000..c82cc243 --- /dev/null +++ b/api/queryService/query_test.go @@ -0,0 +1,267 @@ +package queryservice_test + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + queryservice "queryorchestration/api/queryService" + "queryorchestration/internal/database" + "queryorchestration/internal/database/repository" + "queryorchestration/internal/query" + "strings" + "testing" + + "github.com/go-playground/validator/v10" + "github.com/google/uuid" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" + "github.com/labstack/echo/v4" + "github.com/pashagolub/pgxmock/v3" + "github.com/stretchr/testify/assert" +) + +func TestDeprecateQuery(t *testing.T) { + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatalf("failed to open pgxmock database: %v", err) + } + queries := repository.New(pool) + db := &database.Connection{ + Queries: queries, + Pool: pool, + } + + cons := queryservice.NewControllers(validator.New(), &queryservice.Services{ + Query: query.New(db), + }) + + id := uuid.New() + + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("")) + rec := httptest.NewRecorder() + ctx := e.NewContext(req, rec) + + pool.ExpectQuery("-- name: IsQueryDeprecated :one").WithArgs(database.MustToDBUUID(id)).WillReturnRows( + pgxmock.NewRows([]string{"isdeprecated"}). + AddRow(true), + ) + + err = cons.DeprecateQuery(ctx, id.String()) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, rec.Code) + assert.Empty(t, rec.Body.String()) +} + +func TestCreateQuery(t *testing.T) { + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatalf("failed to open pgxmock database: %v", err) + } + queries := repository.New(pool) + db := &database.Connection{ + Queries: queries, + Pool: pool, + } + + cons := queryservice.NewControllers(validator.New(), &queryservice.Services{ + Query: query.New(db), + }) + + body := queryservice.QueryCreate{ + Type: queryservice.CONTEXTFULL, + } + bodyBytes, err := json.Marshal(body) + assert.Nil(t, err) + + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(bodyBytes))) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + rec := httptest.NewRecorder() + ctx := e.NewContext(req, rec) + + id := uuid.New() + + pool.ExpectBeginTx(pgx.TxOptions{}) + pool.ExpectQuery("-- name: CreateQuery :one").WithArgs(repository.QuerytypeContextFull).WillReturnRows( + pgxmock.NewRows([]string{"id"}). + AddRow(database.MustToDBUUID(id)), + ) + pool.ExpectCommit() + + err = cons.CreateQuery(ctx) + assert.Nil(t, err) + assert.Equal(t, http.StatusCreated, rec.Code) + assert.Equal(t, fmt.Sprintf("{\"id\":\"%s\"}\n", id), rec.Body.String()) +} + +func TestListQueries(t *testing.T) { + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatalf("failed to open pgxmock database: %v", err) + } + queries := repository.New(pool) + db := &database.Connection{ + Queries: queries, + Pool: pool, + } + + cons := queryservice.NewControllers(validator.New(), &queryservice.Services{ + Query: query.New(db), + }) + + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("")) + rec := httptest.NewRecorder() + ctx := e.NewContext(req, rec) + + id := uuid.New() + + pool.ExpectQuery("-- name: ListQueries :many").WithArgs().WillReturnRows( + pgxmock.NewRows([]string{"id", "type", "activeVersion", "latestVersion", "config", "requiredIds"}). + AddRow(database.MustToDBUUID(id), repository.QuerytypeContextFull, int32(1), int32(2), []byte(""), []pgtype.UUID{}), + ) + + err = cons.ListQueries(ctx) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, rec.Code) + + var res queryservice.ListQueries + err = json.Unmarshal(rec.Body.Bytes(), &res) + assert.Nil(t, err) + assert.NotNil(t, res.Queries) + assert.ElementsMatch(t, res.Queries, []queryservice.Query{ + { + Id: id.String(), + Type: queryservice.CONTEXTFULL, + ActiveVersion: 1, + LatestVersion: 2, + }, + }) +} + +func TestGetQueryById(t *testing.T) { + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatalf("failed to open pgxmock database: %v", err) + } + queries := repository.New(pool) + db := &database.Connection{ + Queries: queries, + Pool: pool, + } + + cons := queryservice.NewControllers(validator.New(), &queryservice.Services{ + Query: query.New(db), + }) + + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("")) + rec := httptest.NewRecorder() + ctx := e.NewContext(req, rec) + + id := uuid.New() + + ctx.Set("id", id) + + pool.ExpectQuery("-- name: GetQuery :one").WithArgs(database.MustToDBUUID(id)).WillReturnRows( + pgxmock.NewRows([]string{"id", "type", "activeVersion", "latestVersion", "config", "requiredIds"}). + AddRow(database.MustToDBUUID(id), repository.QuerytypeContextFull, int32(1), int32(2), []byte(""), []pgtype.UUID{}), + ) + + err = cons.GetQueryById(ctx, id.String()) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, rec.Code) + + var res queryservice.Query + err = json.Unmarshal(rec.Body.Bytes(), &res) + assert.Nil(t, err) + assert.EqualExportedValues(t, queryservice.Query{ + Id: id.String(), + Type: queryservice.CONTEXTFULL, + ActiveVersion: 1, + LatestVersion: 2, + }, res) +} + +func TestUpdateQuery(t *testing.T) { + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatalf("failed to open pgxmock database: %v", err) + } + queries := repository.New(pool) + db := &database.Connection{ + Queries: queries, + Pool: pool, + } + + cons := queryservice.NewControllers(validator.New(), &queryservice.Services{ + Query: query.New(db), + }) + + body := queryservice.QueryUpdate{} + bodyBytes, err := json.Marshal(body) + assert.Nil(t, err) + + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(bodyBytes))) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + rec := httptest.NewRecorder() + ctx := e.NewContext(req, rec) + + id := uuid.New() + + ctx.Set("id", id) + + pool.ExpectQuery("-- name: GetQuery :one").WithArgs(database.MustToDBUUID(id)).WillReturnRows( + pgxmock.NewRows([]string{"id", "type", "activeVersion", "latestVersion", "config", "requiredIds"}). + AddRow(database.MustToDBUUID(id), repository.QuerytypeContextFull, int32(1), int32(2), []byte(""), []pgtype.UUID{}), + ) + + err = cons.UpdateQuery(ctx, id.String()) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, rec.Code) + assert.Empty(t, rec.Body.String()) +} + +func TestTestQuery(t *testing.T) { + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatalf("failed to open pgxmock database: %v", err) + } + queries := repository.New(pool) + db := &database.Connection{ + Queries: queries, + Pool: pool, + } + + cons := queryservice.NewControllers(validator.New(), &queryservice.Services{ + Query: query.New(db), + }) + + body := queryservice.QueryTestRequest{ + DocumentId: uuid.NewString(), + } + bodyBytes, err := json.Marshal(body) + assert.Nil(t, err) + + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(bodyBytes))) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + rec := httptest.NewRecorder() + ctx := e.NewContext(req, rec) + + id := uuid.New() + + err = cons.TestQuery(ctx, id.String()) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, rec.Code) + + var res queryservice.QueryTestResponse + err = json.Unmarshal(rec.Body.Bytes(), &res) + assert.Nil(t, err) + assert.EqualExportedValues(t, queryservice.QueryTestResponse{ + Value: "", + }, res) +} diff --git a/api/serviceInterfaces/main.pb.go b/api/serviceInterfaces/main.pb.go deleted file mode 100644 index 664a0ba4..00000000 --- a/api/serviceInterfaces/main.pb.go +++ /dev/null @@ -1,876 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.35.1 -// protoc v5.28.3 -// source: main.proto - -package serviceinterfaces - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - emptypb "google.golang.org/protobuf/types/known/emptypb" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type QueryType int32 - -const ( - QueryType_QUERY_TYPE_UNSPECIFIED QueryType = 0 - QueryType_QUERY_TYPE_JSON_EXTRACTOR QueryType = 1 - QueryType_QUERY_TYPE_CONTEXT_FULL QueryType = 2 -) - -// Enum value maps for QueryType. -var ( - QueryType_name = map[int32]string{ - 0: "QUERY_TYPE_UNSPECIFIED", - 1: "QUERY_TYPE_JSON_EXTRACTOR", - 2: "QUERY_TYPE_CONTEXT_FULL", - } - QueryType_value = map[string]int32{ - "QUERY_TYPE_UNSPECIFIED": 0, - "QUERY_TYPE_JSON_EXTRACTOR": 1, - "QUERY_TYPE_CONTEXT_FULL": 2, - } -) - -func (x QueryType) Enum() *QueryType { - p := new(QueryType) - *p = x - return p -} - -func (x QueryType) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (QueryType) Descriptor() protoreflect.EnumDescriptor { - return file_main_proto_enumTypes[0].Descriptor() -} - -func (QueryType) Type() protoreflect.EnumType { - return &file_main_proto_enumTypes[0] -} - -func (x QueryType) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use QueryType.Descriptor instead. -func (QueryType) EnumDescriptor() ([]byte, []int) { - return file_main_proto_rawDescGZIP(), []int{0} -} - -type QueryTestRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - QueryId string `protobuf:"bytes,1,opt,name=query_id,json=queryId,proto3" json:"query_id,omitempty"` - DocumentId string `protobuf:"bytes,2,opt,name=document_id,json=documentId,proto3" json:"document_id,omitempty"` - QueryVersion int32 `protobuf:"varint,3,opt,name=query_version,json=queryVersion,proto3" json:"query_version,omitempty"` -} - -func (x *QueryTestRequest) Reset() { - *x = QueryTestRequest{} - mi := &file_main_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *QueryTestRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*QueryTestRequest) ProtoMessage() {} - -func (x *QueryTestRequest) ProtoReflect() protoreflect.Message { - mi := &file_main_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use QueryTestRequest.ProtoReflect.Descriptor instead. -func (*QueryTestRequest) Descriptor() ([]byte, []int) { - return file_main_proto_rawDescGZIP(), []int{0} -} - -func (x *QueryTestRequest) GetQueryId() string { - if x != nil { - return x.QueryId - } - return "" -} - -func (x *QueryTestRequest) GetDocumentId() string { - if x != nil { - return x.DocumentId - } - return "" -} - -func (x *QueryTestRequest) GetQueryVersion() int32 { - if x != nil { - return x.QueryVersion - } - return 0 -} - -type QueryTestResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` -} - -func (x *QueryTestResponse) Reset() { - *x = QueryTestResponse{} - mi := &file_main_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *QueryTestResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*QueryTestResponse) ProtoMessage() {} - -func (x *QueryTestResponse) ProtoReflect() protoreflect.Message { - mi := &file_main_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use QueryTestResponse.ProtoReflect.Descriptor instead. -func (*QueryTestResponse) Descriptor() ([]byte, []int) { - return file_main_proto_rawDescGZIP(), []int{1} -} - -func (x *QueryTestResponse) GetValue() string { - if x != nil { - return x.Value - } - return "" -} - -type Query struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Type QueryType `protobuf:"varint,2,opt,name=type,proto3,enum=queryorchestration.QueryType" json:"type,omitempty"` - ActiveVersion int32 `protobuf:"varint,3,opt,name=active_version,json=activeVersion,proto3" json:"active_version,omitempty"` - LatestVersion int32 `protobuf:"varint,4,opt,name=latest_version,json=latestVersion,proto3" json:"latest_version,omitempty"` - Config *string `protobuf:"bytes,5,opt,name=config,proto3,oneof" json:"config,omitempty"` - RequiredQueries []string `protobuf:"bytes,6,rep,name=required_queries,json=requiredQueries,proto3" json:"required_queries,omitempty"` -} - -func (x *Query) Reset() { - *x = Query{} - mi := &file_main_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Query) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Query) ProtoMessage() {} - -func (x *Query) ProtoReflect() protoreflect.Message { - mi := &file_main_proto_msgTypes[2] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Query.ProtoReflect.Descriptor instead. -func (*Query) Descriptor() ([]byte, []int) { - return file_main_proto_rawDescGZIP(), []int{2} -} - -func (x *Query) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *Query) GetType() QueryType { - if x != nil { - return x.Type - } - return QueryType_QUERY_TYPE_UNSPECIFIED -} - -func (x *Query) GetActiveVersion() int32 { - if x != nil { - return x.ActiveVersion - } - return 0 -} - -func (x *Query) GetLatestVersion() int32 { - if x != nil { - return x.LatestVersion - } - return 0 -} - -func (x *Query) GetConfig() string { - if x != nil && x.Config != nil { - return *x.Config - } - return "" -} - -func (x *Query) GetRequiredQueries() []string { - if x != nil { - return x.RequiredQueries - } - return nil -} - -type QueryFilter struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *QueryFilter) Reset() { - *x = QueryFilter{} - mi := &file_main_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *QueryFilter) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*QueryFilter) ProtoMessage() {} - -func (x *QueryFilter) ProtoReflect() protoreflect.Message { - mi := &file_main_proto_msgTypes[3] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use QueryFilter.ProtoReflect.Descriptor instead. -func (*QueryFilter) Descriptor() ([]byte, []int) { - return file_main_proto_rawDescGZIP(), []int{3} -} - -type QueryCreate struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Type QueryType `protobuf:"varint,1,opt,name=type,proto3,enum=queryorchestration.QueryType" json:"type,omitempty"` - Config *string `protobuf:"bytes,2,opt,name=config,proto3,oneof" json:"config,omitempty"` - RequiredQueries []string `protobuf:"bytes,3,rep,name=required_queries,json=requiredQueries,proto3" json:"required_queries,omitempty"` -} - -func (x *QueryCreate) Reset() { - *x = QueryCreate{} - mi := &file_main_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *QueryCreate) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*QueryCreate) ProtoMessage() {} - -func (x *QueryCreate) ProtoReflect() protoreflect.Message { - mi := &file_main_proto_msgTypes[4] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use QueryCreate.ProtoReflect.Descriptor instead. -func (*QueryCreate) Descriptor() ([]byte, []int) { - return file_main_proto_rawDescGZIP(), []int{4} -} - -func (x *QueryCreate) GetType() QueryType { - if x != nil { - return x.Type - } - return QueryType_QUERY_TYPE_UNSPECIFIED -} - -func (x *QueryCreate) GetConfig() string { - if x != nil && x.Config != nil { - return *x.Config - } - return "" -} - -func (x *QueryCreate) GetRequiredQueries() []string { - if x != nil { - return x.RequiredQueries - } - return nil -} - -type QueryUpdate struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Config *string `protobuf:"bytes,2,opt,name=config,proto3,oneof" json:"config,omitempty"` - ActiveVersion *int32 `protobuf:"varint,3,opt,name=active_version,json=activeVersion,proto3,oneof" json:"active_version,omitempty"` - RequiredQueries []string `protobuf:"bytes,4,rep,name=required_queries,json=requiredQueries,proto3" json:"required_queries,omitempty"` -} - -func (x *QueryUpdate) Reset() { - *x = QueryUpdate{} - mi := &file_main_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *QueryUpdate) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*QueryUpdate) ProtoMessage() {} - -func (x *QueryUpdate) ProtoReflect() protoreflect.Message { - mi := &file_main_proto_msgTypes[5] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use QueryUpdate.ProtoReflect.Descriptor instead. -func (*QueryUpdate) Descriptor() ([]byte, []int) { - return file_main_proto_rawDescGZIP(), []int{5} -} - -func (x *QueryUpdate) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *QueryUpdate) GetConfig() string { - if x != nil && x.Config != nil { - return *x.Config - } - return "" -} - -func (x *QueryUpdate) GetActiveVersion() int32 { - if x != nil && x.ActiveVersion != nil { - return *x.ActiveVersion - } - return 0 -} - -func (x *QueryUpdate) GetRequiredQueries() []string { - if x != nil { - return x.RequiredQueries - } - return nil -} - -type Queries struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Queries []*Query `protobuf:"bytes,1,rep,name=queries,proto3" json:"queries,omitempty"` -} - -func (x *Queries) Reset() { - *x = Queries{} - mi := &file_main_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Queries) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Queries) ProtoMessage() {} - -func (x *Queries) ProtoReflect() protoreflect.Message { - mi := &file_main_proto_msgTypes[6] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Queries.ProtoReflect.Descriptor instead. -func (*Queries) Descriptor() ([]byte, []int) { - return file_main_proto_rawDescGZIP(), []int{6} -} - -func (x *Queries) GetQueries() []*Query { - if x != nil { - return x.Queries - } - return nil -} - -type JobCollector struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *JobCollector) Reset() { - *x = JobCollector{} - mi := &file_main_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *JobCollector) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*JobCollector) ProtoMessage() {} - -func (x *JobCollector) ProtoReflect() protoreflect.Message { - mi := &file_main_proto_msgTypes[7] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use JobCollector.ProtoReflect.Descriptor instead. -func (*JobCollector) Descriptor() ([]byte, []int) { - return file_main_proto_rawDescGZIP(), []int{7} -} - -type JobCollectorCreate struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *JobCollectorCreate) Reset() { - *x = JobCollectorCreate{} - mi := &file_main_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *JobCollectorCreate) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*JobCollectorCreate) ProtoMessage() {} - -func (x *JobCollectorCreate) ProtoReflect() protoreflect.Message { - mi := &file_main_proto_msgTypes[8] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use JobCollectorCreate.ProtoReflect.Descriptor instead. -func (*JobCollectorCreate) Descriptor() ([]byte, []int) { - return file_main_proto_rawDescGZIP(), []int{8} -} - -type JobCollectorUpdate struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *JobCollectorUpdate) Reset() { - *x = JobCollectorUpdate{} - mi := &file_main_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *JobCollectorUpdate) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*JobCollectorUpdate) ProtoMessage() {} - -func (x *JobCollectorUpdate) ProtoReflect() protoreflect.Message { - mi := &file_main_proto_msgTypes[9] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use JobCollectorUpdate.ProtoReflect.Descriptor instead. -func (*JobCollectorUpdate) Descriptor() ([]byte, []int) { - return file_main_proto_rawDescGZIP(), []int{9} -} - -type ExportTrigger struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *ExportTrigger) Reset() { - *x = ExportTrigger{} - mi := &file_main_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ExportTrigger) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ExportTrigger) ProtoMessage() {} - -func (x *ExportTrigger) ProtoReflect() protoreflect.Message { - mi := &file_main_proto_msgTypes[10] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ExportTrigger.ProtoReflect.Descriptor instead. -func (*ExportTrigger) Descriptor() ([]byte, []int) { - return file_main_proto_rawDescGZIP(), []int{10} -} - -type IdMessage struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *IdMessage) Reset() { - *x = IdMessage{} - mi := &file_main_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *IdMessage) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*IdMessage) ProtoMessage() {} - -func (x *IdMessage) ProtoReflect() protoreflect.Message { - mi := &file_main_proto_msgTypes[11] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use IdMessage.ProtoReflect.Descriptor instead. -func (*IdMessage) Descriptor() ([]byte, []int) { - return file_main_proto_rawDescGZIP(), []int{11} -} - -func (x *IdMessage) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -var File_main_proto protoreflect.FileDescriptor - -var file_main_proto_rawDesc = []byte{ - 0x0a, 0x0a, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x71, 0x75, - 0x65, 0x72, 0x79, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x73, 0x0a, - 0x10, 0x51, 0x75, 0x65, 0x72, 0x79, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x19, 0x0a, 0x08, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x71, 0x75, 0x65, 0x72, 0x79, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, - 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, - 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x71, 0x75, 0x65, 0x72, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x22, 0x29, 0x0a, 0x11, 0x51, 0x75, 0x65, 0x72, 0x79, 0x54, 0x65, 0x73, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xeb, 0x01, - 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x31, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x6f, 0x72, 0x63, - 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6c, 0x61, 0x74, 0x65, 0x73, - 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x88, 0x01, 0x01, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, - 0x64, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, - 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x0d, 0x0a, 0x0b, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x93, 0x01, 0x0a, 0x0b, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, - 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, - 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, - 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x88, 0x01, 0x01, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x51, 0x75, - 0x65, 0x72, 0x69, 0x65, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x22, 0xaf, 0x01, 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x1b, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x88, 0x01, 0x01, 0x12, 0x2a, 0x0a, - 0x0e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, - 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x51, 0x75, 0x65, - 0x72, 0x69, 0x65, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, - 0x11, 0x0a, 0x0f, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x22, 0x3e, 0x0a, 0x07, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x33, 0x0a, - 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, - 0x65, 0x73, 0x22, 0x0e, 0x0a, 0x0c, 0x4a, 0x6f, 0x62, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x6f, 0x72, 0x22, 0x14, 0x0a, 0x12, 0x4a, 0x6f, 0x62, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x6f, 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x22, 0x14, 0x0a, 0x12, 0x4a, 0x6f, 0x62, 0x43, - 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, 0x0f, - 0x0a, 0x0d, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x22, - 0x1b, 0x0a, 0x09, 0x49, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x2a, 0x63, 0x0a, 0x09, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x51, 0x55, 0x45, - 0x52, 0x59, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, - 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x51, 0x55, 0x45, 0x52, 0x59, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x4a, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x58, 0x54, 0x52, 0x41, 0x43, 0x54, - 0x4f, 0x52, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x51, 0x55, 0x45, 0x52, 0x59, 0x5f, 0x54, 0x59, - 0x50, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x10, - 0x02, 0x32, 0xbb, 0x03, 0x0a, 0x0c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x12, 0x44, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1f, 0x2e, 0x71, 0x75, 0x65, - 0x72, 0x79, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x1a, 0x1b, 0x2e, 0x71, 0x75, - 0x65, 0x72, 0x79, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x2e, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x3f, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, - 0x1d, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x49, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x19, - 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x48, 0x0a, 0x06, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x6f, 0x72, 0x63, 0x68, 0x65, - 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x1a, 0x1d, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x6f, 0x72, 0x63, 0x68, - 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x49, 0x64, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x12, 0x41, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x2e, - 0x71, 0x75, 0x65, 0x72, 0x79, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x1a, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, 0x0a, 0x09, 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, - 0x61, 0x74, 0x65, 0x12, 0x1d, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x6f, 0x72, 0x63, 0x68, 0x65, - 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x49, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x53, 0x0a, 0x04, 0x54, 0x65, - 0x73, 0x74, 0x12, 0x24, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x54, 0x65, 0x73, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, - 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, - 0xf8, 0x01, 0x0a, 0x13, 0x4a, 0x6f, 0x62, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x12, 0x26, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4a, 0x6f, 0x62, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x1a, 0x1d, 0x2e, 0x71, 0x75, 0x65, 0x72, - 0x79, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x49, - 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x48, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x12, 0x26, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4a, 0x6f, 0x62, 0x43, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x6f, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x12, 0x46, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x1d, 0x2e, 0x71, 0x75, 0x65, 0x72, - 0x79, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x49, - 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x20, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, - 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4a, 0x6f, - 0x62, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x32, 0x5c, 0x0a, 0x0d, 0x45, 0x78, - 0x70, 0x6f, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4b, 0x0a, 0x07, 0x54, - 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x12, 0x21, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x6f, 0x72, - 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x45, 0x78, 0x70, 0x6f, - 0x72, 0x74, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x1a, 0x1d, 0x2e, 0x71, 0x75, 0x65, 0x72, - 0x79, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x49, - 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x15, 0x5a, 0x13, 0x2e, 0x2f, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_main_proto_rawDescOnce sync.Once - file_main_proto_rawDescData = file_main_proto_rawDesc -) - -func file_main_proto_rawDescGZIP() []byte { - file_main_proto_rawDescOnce.Do(func() { - file_main_proto_rawDescData = protoimpl.X.CompressGZIP(file_main_proto_rawDescData) - }) - return file_main_proto_rawDescData -} - -var file_main_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_main_proto_msgTypes = make([]protoimpl.MessageInfo, 12) -var file_main_proto_goTypes = []any{ - (QueryType)(0), // 0: queryorchestration.QueryType - (*QueryTestRequest)(nil), // 1: queryorchestration.QueryTestRequest - (*QueryTestResponse)(nil), // 2: queryorchestration.QueryTestResponse - (*Query)(nil), // 3: queryorchestration.Query - (*QueryFilter)(nil), // 4: queryorchestration.QueryFilter - (*QueryCreate)(nil), // 5: queryorchestration.QueryCreate - (*QueryUpdate)(nil), // 6: queryorchestration.QueryUpdate - (*Queries)(nil), // 7: queryorchestration.Queries - (*JobCollector)(nil), // 8: queryorchestration.JobCollector - (*JobCollectorCreate)(nil), // 9: queryorchestration.JobCollectorCreate - (*JobCollectorUpdate)(nil), // 10: queryorchestration.JobCollectorUpdate - (*ExportTrigger)(nil), // 11: queryorchestration.ExportTrigger - (*IdMessage)(nil), // 12: queryorchestration.IdMessage - (*emptypb.Empty)(nil), // 13: google.protobuf.Empty -} -var file_main_proto_depIdxs = []int32{ - 0, // 0: queryorchestration.Query.type:type_name -> queryorchestration.QueryType - 0, // 1: queryorchestration.QueryCreate.type:type_name -> queryorchestration.QueryType - 3, // 2: queryorchestration.Queries.queries:type_name -> queryorchestration.Query - 4, // 3: queryorchestration.QueryService.List:input_type -> queryorchestration.QueryFilter - 12, // 4: queryorchestration.QueryService.Get:input_type -> queryorchestration.IdMessage - 5, // 5: queryorchestration.QueryService.Create:input_type -> queryorchestration.QueryCreate - 6, // 6: queryorchestration.QueryService.Update:input_type -> queryorchestration.QueryUpdate - 12, // 7: queryorchestration.QueryService.Deprecate:input_type -> queryorchestration.IdMessage - 1, // 8: queryorchestration.QueryService.Test:input_type -> queryorchestration.QueryTestRequest - 9, // 9: queryorchestration.JobCollectorService.Create:input_type -> queryorchestration.JobCollectorCreate - 10, // 10: queryorchestration.JobCollectorService.Update:input_type -> queryorchestration.JobCollectorUpdate - 12, // 11: queryorchestration.JobCollectorService.Get:input_type -> queryorchestration.IdMessage - 11, // 12: queryorchestration.ExportService.Trigger:input_type -> queryorchestration.ExportTrigger - 7, // 13: queryorchestration.QueryService.List:output_type -> queryorchestration.Queries - 3, // 14: queryorchestration.QueryService.Get:output_type -> queryorchestration.Query - 12, // 15: queryorchestration.QueryService.Create:output_type -> queryorchestration.IdMessage - 13, // 16: queryorchestration.QueryService.Update:output_type -> google.protobuf.Empty - 13, // 17: queryorchestration.QueryService.Deprecate:output_type -> google.protobuf.Empty - 2, // 18: queryorchestration.QueryService.Test:output_type -> queryorchestration.QueryTestResponse - 12, // 19: queryorchestration.JobCollectorService.Create:output_type -> queryorchestration.IdMessage - 13, // 20: queryorchestration.JobCollectorService.Update:output_type -> google.protobuf.Empty - 8, // 21: queryorchestration.JobCollectorService.Get:output_type -> queryorchestration.JobCollector - 12, // 22: queryorchestration.ExportService.Trigger:output_type -> queryorchestration.IdMessage - 13, // [13:23] is the sub-list for method output_type - 3, // [3:13] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name -} - -func init() { file_main_proto_init() } -func file_main_proto_init() { - if File_main_proto != nil { - return - } - file_main_proto_msgTypes[2].OneofWrappers = []any{} - file_main_proto_msgTypes[4].OneofWrappers = []any{} - file_main_proto_msgTypes[5].OneofWrappers = []any{} - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_main_proto_rawDesc, - NumEnums: 1, - NumMessages: 12, - NumExtensions: 0, - NumServices: 3, - }, - GoTypes: file_main_proto_goTypes, - DependencyIndexes: file_main_proto_depIdxs, - EnumInfos: file_main_proto_enumTypes, - MessageInfos: file_main_proto_msgTypes, - }.Build() - File_main_proto = out.File - file_main_proto_rawDesc = nil - file_main_proto_goTypes = nil - file_main_proto_depIdxs = nil -} diff --git a/api/serviceInterfaces/main_grpc.pb.go b/api/serviceInterfaces/main_grpc.pb.go deleted file mode 100644 index 6a41ebd3..00000000 --- a/api/serviceInterfaces/main_grpc.pb.go +++ /dev/null @@ -1,549 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.3.0 -// - protoc v5.28.3 -// source: main.proto - -package serviceinterfaces - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - emptypb "google.golang.org/protobuf/types/known/emptypb" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -const ( - QueryService_List_FullMethodName = "/queryorchestration.QueryService/List" - QueryService_Get_FullMethodName = "/queryorchestration.QueryService/Get" - QueryService_Create_FullMethodName = "/queryorchestration.QueryService/Create" - QueryService_Update_FullMethodName = "/queryorchestration.QueryService/Update" - QueryService_Deprecate_FullMethodName = "/queryorchestration.QueryService/Deprecate" - QueryService_Test_FullMethodName = "/queryorchestration.QueryService/Test" -) - -// QueryServiceClient is the client API for QueryService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type QueryServiceClient interface { - List(ctx context.Context, in *QueryFilter, opts ...grpc.CallOption) (*Queries, error) - Get(ctx context.Context, in *IdMessage, opts ...grpc.CallOption) (*Query, error) - Create(ctx context.Context, in *QueryCreate, opts ...grpc.CallOption) (*IdMessage, error) - Update(ctx context.Context, in *QueryUpdate, opts ...grpc.CallOption) (*emptypb.Empty, error) - Deprecate(ctx context.Context, in *IdMessage, opts ...grpc.CallOption) (*emptypb.Empty, error) - Test(ctx context.Context, in *QueryTestRequest, opts ...grpc.CallOption) (*QueryTestResponse, error) -} - -type queryServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewQueryServiceClient(cc grpc.ClientConnInterface) QueryServiceClient { - return &queryServiceClient{cc} -} - -func (c *queryServiceClient) List(ctx context.Context, in *QueryFilter, opts ...grpc.CallOption) (*Queries, error) { - out := new(Queries) - err := c.cc.Invoke(ctx, QueryService_List_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *queryServiceClient) Get(ctx context.Context, in *IdMessage, opts ...grpc.CallOption) (*Query, error) { - out := new(Query) - err := c.cc.Invoke(ctx, QueryService_Get_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *queryServiceClient) Create(ctx context.Context, in *QueryCreate, opts ...grpc.CallOption) (*IdMessage, error) { - out := new(IdMessage) - err := c.cc.Invoke(ctx, QueryService_Create_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *queryServiceClient) Update(ctx context.Context, in *QueryUpdate, opts ...grpc.CallOption) (*emptypb.Empty, error) { - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, QueryService_Update_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *queryServiceClient) Deprecate(ctx context.Context, in *IdMessage, opts ...grpc.CallOption) (*emptypb.Empty, error) { - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, QueryService_Deprecate_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *queryServiceClient) Test(ctx context.Context, in *QueryTestRequest, opts ...grpc.CallOption) (*QueryTestResponse, error) { - out := new(QueryTestResponse) - err := c.cc.Invoke(ctx, QueryService_Test_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// QueryServiceServer is the server API for QueryService service. -// All implementations must embed UnimplementedQueryServiceServer -// for forward compatibility -type QueryServiceServer interface { - List(context.Context, *QueryFilter) (*Queries, error) - Get(context.Context, *IdMessage) (*Query, error) - Create(context.Context, *QueryCreate) (*IdMessage, error) - Update(context.Context, *QueryUpdate) (*emptypb.Empty, error) - Deprecate(context.Context, *IdMessage) (*emptypb.Empty, error) - Test(context.Context, *QueryTestRequest) (*QueryTestResponse, error) - mustEmbedUnimplementedQueryServiceServer() -} - -// UnimplementedQueryServiceServer must be embedded to have forward compatible implementations. -type UnimplementedQueryServiceServer struct { -} - -func (UnimplementedQueryServiceServer) List(context.Context, *QueryFilter) (*Queries, error) { - return nil, status.Errorf(codes.Unimplemented, "method List not implemented") -} -func (UnimplementedQueryServiceServer) Get(context.Context, *IdMessage) (*Query, error) { - return nil, status.Errorf(codes.Unimplemented, "method Get not implemented") -} -func (UnimplementedQueryServiceServer) Create(context.Context, *QueryCreate) (*IdMessage, error) { - return nil, status.Errorf(codes.Unimplemented, "method Create not implemented") -} -func (UnimplementedQueryServiceServer) Update(context.Context, *QueryUpdate) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method Update not implemented") -} -func (UnimplementedQueryServiceServer) Deprecate(context.Context, *IdMessage) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method Deprecate not implemented") -} -func (UnimplementedQueryServiceServer) Test(context.Context, *QueryTestRequest) (*QueryTestResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Test not implemented") -} -func (UnimplementedQueryServiceServer) mustEmbedUnimplementedQueryServiceServer() {} - -// UnsafeQueryServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to QueryServiceServer will -// result in compilation errors. -type UnsafeQueryServiceServer interface { - mustEmbedUnimplementedQueryServiceServer() -} - -func RegisterQueryServiceServer(s grpc.ServiceRegistrar, srv QueryServiceServer) { - s.RegisterService(&QueryService_ServiceDesc, srv) -} - -func _QueryService_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryFilter) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QueryServiceServer).List(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: QueryService_List_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServiceServer).List(ctx, req.(*QueryFilter)) - } - return interceptor(ctx, in, info, handler) -} - -func _QueryService_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(IdMessage) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QueryServiceServer).Get(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: QueryService_Get_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServiceServer).Get(ctx, req.(*IdMessage)) - } - return interceptor(ctx, in, info, handler) -} - -func _QueryService_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryCreate) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QueryServiceServer).Create(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: QueryService_Create_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServiceServer).Create(ctx, req.(*QueryCreate)) - } - return interceptor(ctx, in, info, handler) -} - -func _QueryService_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryUpdate) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QueryServiceServer).Update(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: QueryService_Update_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServiceServer).Update(ctx, req.(*QueryUpdate)) - } - return interceptor(ctx, in, info, handler) -} - -func _QueryService_Deprecate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(IdMessage) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QueryServiceServer).Deprecate(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: QueryService_Deprecate_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServiceServer).Deprecate(ctx, req.(*IdMessage)) - } - return interceptor(ctx, in, info, handler) -} - -func _QueryService_Test_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryTestRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QueryServiceServer).Test(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: QueryService_Test_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServiceServer).Test(ctx, req.(*QueryTestRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// QueryService_ServiceDesc is the grpc.ServiceDesc for QueryService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var QueryService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "queryorchestration.QueryService", - HandlerType: (*QueryServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "List", - Handler: _QueryService_List_Handler, - }, - { - MethodName: "Get", - Handler: _QueryService_Get_Handler, - }, - { - MethodName: "Create", - Handler: _QueryService_Create_Handler, - }, - { - MethodName: "Update", - Handler: _QueryService_Update_Handler, - }, - { - MethodName: "Deprecate", - Handler: _QueryService_Deprecate_Handler, - }, - { - MethodName: "Test", - Handler: _QueryService_Test_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "main.proto", -} - -const ( - JobCollectorService_Create_FullMethodName = "/queryorchestration.JobCollectorService/Create" - JobCollectorService_Update_FullMethodName = "/queryorchestration.JobCollectorService/Update" - JobCollectorService_Get_FullMethodName = "/queryorchestration.JobCollectorService/Get" -) - -// JobCollectorServiceClient is the client API for JobCollectorService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type JobCollectorServiceClient interface { - Create(ctx context.Context, in *JobCollectorCreate, opts ...grpc.CallOption) (*IdMessage, error) - Update(ctx context.Context, in *JobCollectorUpdate, opts ...grpc.CallOption) (*emptypb.Empty, error) - Get(ctx context.Context, in *IdMessage, opts ...grpc.CallOption) (*JobCollector, error) -} - -type jobCollectorServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewJobCollectorServiceClient(cc grpc.ClientConnInterface) JobCollectorServiceClient { - return &jobCollectorServiceClient{cc} -} - -func (c *jobCollectorServiceClient) Create(ctx context.Context, in *JobCollectorCreate, opts ...grpc.CallOption) (*IdMessage, error) { - out := new(IdMessage) - err := c.cc.Invoke(ctx, JobCollectorService_Create_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *jobCollectorServiceClient) Update(ctx context.Context, in *JobCollectorUpdate, opts ...grpc.CallOption) (*emptypb.Empty, error) { - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, JobCollectorService_Update_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *jobCollectorServiceClient) Get(ctx context.Context, in *IdMessage, opts ...grpc.CallOption) (*JobCollector, error) { - out := new(JobCollector) - err := c.cc.Invoke(ctx, JobCollectorService_Get_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// JobCollectorServiceServer is the server API for JobCollectorService service. -// All implementations must embed UnimplementedJobCollectorServiceServer -// for forward compatibility -type JobCollectorServiceServer interface { - Create(context.Context, *JobCollectorCreate) (*IdMessage, error) - Update(context.Context, *JobCollectorUpdate) (*emptypb.Empty, error) - Get(context.Context, *IdMessage) (*JobCollector, error) - mustEmbedUnimplementedJobCollectorServiceServer() -} - -// UnimplementedJobCollectorServiceServer must be embedded to have forward compatible implementations. -type UnimplementedJobCollectorServiceServer struct { -} - -func (UnimplementedJobCollectorServiceServer) Create(context.Context, *JobCollectorCreate) (*IdMessage, error) { - return nil, status.Errorf(codes.Unimplemented, "method Create not implemented") -} -func (UnimplementedJobCollectorServiceServer) Update(context.Context, *JobCollectorUpdate) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method Update not implemented") -} -func (UnimplementedJobCollectorServiceServer) Get(context.Context, *IdMessage) (*JobCollector, error) { - return nil, status.Errorf(codes.Unimplemented, "method Get not implemented") -} -func (UnimplementedJobCollectorServiceServer) mustEmbedUnimplementedJobCollectorServiceServer() {} - -// UnsafeJobCollectorServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to JobCollectorServiceServer will -// result in compilation errors. -type UnsafeJobCollectorServiceServer interface { - mustEmbedUnimplementedJobCollectorServiceServer() -} - -func RegisterJobCollectorServiceServer(s grpc.ServiceRegistrar, srv JobCollectorServiceServer) { - s.RegisterService(&JobCollectorService_ServiceDesc, srv) -} - -func _JobCollectorService_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(JobCollectorCreate) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(JobCollectorServiceServer).Create(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: JobCollectorService_Create_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(JobCollectorServiceServer).Create(ctx, req.(*JobCollectorCreate)) - } - return interceptor(ctx, in, info, handler) -} - -func _JobCollectorService_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(JobCollectorUpdate) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(JobCollectorServiceServer).Update(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: JobCollectorService_Update_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(JobCollectorServiceServer).Update(ctx, req.(*JobCollectorUpdate)) - } - return interceptor(ctx, in, info, handler) -} - -func _JobCollectorService_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(IdMessage) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(JobCollectorServiceServer).Get(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: JobCollectorService_Get_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(JobCollectorServiceServer).Get(ctx, req.(*IdMessage)) - } - return interceptor(ctx, in, info, handler) -} - -// JobCollectorService_ServiceDesc is the grpc.ServiceDesc for JobCollectorService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var JobCollectorService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "queryorchestration.JobCollectorService", - HandlerType: (*JobCollectorServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Create", - Handler: _JobCollectorService_Create_Handler, - }, - { - MethodName: "Update", - Handler: _JobCollectorService_Update_Handler, - }, - { - MethodName: "Get", - Handler: _JobCollectorService_Get_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "main.proto", -} - -const ( - ExportService_Trigger_FullMethodName = "/queryorchestration.ExportService/Trigger" -) - -// ExportServiceClient is the client API for ExportService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type ExportServiceClient interface { - Trigger(ctx context.Context, in *ExportTrigger, opts ...grpc.CallOption) (*IdMessage, error) -} - -type exportServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewExportServiceClient(cc grpc.ClientConnInterface) ExportServiceClient { - return &exportServiceClient{cc} -} - -func (c *exportServiceClient) Trigger(ctx context.Context, in *ExportTrigger, opts ...grpc.CallOption) (*IdMessage, error) { - out := new(IdMessage) - err := c.cc.Invoke(ctx, ExportService_Trigger_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// ExportServiceServer is the server API for ExportService service. -// All implementations must embed UnimplementedExportServiceServer -// for forward compatibility -type ExportServiceServer interface { - Trigger(context.Context, *ExportTrigger) (*IdMessage, error) - mustEmbedUnimplementedExportServiceServer() -} - -// UnimplementedExportServiceServer must be embedded to have forward compatible implementations. -type UnimplementedExportServiceServer struct { -} - -func (UnimplementedExportServiceServer) Trigger(context.Context, *ExportTrigger) (*IdMessage, error) { - return nil, status.Errorf(codes.Unimplemented, "method Trigger not implemented") -} -func (UnimplementedExportServiceServer) mustEmbedUnimplementedExportServiceServer() {} - -// UnsafeExportServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to ExportServiceServer will -// result in compilation errors. -type UnsafeExportServiceServer interface { - mustEmbedUnimplementedExportServiceServer() -} - -func RegisterExportServiceServer(s grpc.ServiceRegistrar, srv ExportServiceServer) { - s.RegisterService(&ExportService_ServiceDesc, srv) -} - -func _ExportService_Trigger_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ExportTrigger) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ExportServiceServer).Trigger(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: ExportService_Trigger_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ExportServiceServer).Trigger(ctx, req.(*ExportTrigger)) - } - return interceptor(ctx, in, info, handler) -} - -// ExportService_ServiceDesc is the grpc.ServiceDesc for ExportService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var ExportService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "queryorchestration.ExportService", - HandlerType: (*ExportServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Trigger", - Handler: _ExportService_Trigger_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "main.proto", -} diff --git a/cmd/queryRunner/main.go b/cmd/queryRunner/main.go index d6d630e4..f8a8d4e9 100644 --- a/cmd/queryRunner/main.go +++ b/cmd/queryRunner/main.go @@ -2,7 +2,7 @@ package main import ( "context" - controllers "queryorchestration/api/queue" + controllers "queryorchestration/api/queryRunner" "queryorchestration/internal/document" "queryorchestration/internal/queue" "queryorchestration/internal/server" diff --git a/cmd/queryService/main.go b/cmd/queryService/main.go index 26067354..4e2bdd1c 100644 --- a/cmd/queryService/main.go +++ b/cmd/queryService/main.go @@ -2,32 +2,48 @@ package main import ( "context" - controllers "queryorchestration/api/api" - serviceinterfaces "queryorchestration/api/serviceInterfaces" + "log" + queryservice "queryorchestration/api/queryService" "queryorchestration/internal/api" "queryorchestration/internal/collector" "queryorchestration/internal/export" "queryorchestration/internal/query" "queryorchestration/internal/server" + "github.com/labstack/echo/v4" _ "github.com/lib/pq" - "google.golang.org/grpc" ) func main() { ctx := context.Background() - controllers := func(cfg *server.Config) *grpc.Server { - grpcServer := grpc.NewServer() - serviceinterfaces.RegisterQueryServiceServer(grpcServer, controllers.NewQueryController(*query.New(cfg.Database), cfg.Validator)) - serviceinterfaces.RegisterExportServiceServer(grpcServer, controllers.NewExportController(*export.New(cfg.Database), cfg.Validator)) - serviceinterfaces.RegisterJobCollectorServiceServer(grpcServer, controllers.NewJobCollectorController(*collector.New(cfg.Database), cfg.Validator)) + registerHandlers := func(cfg *server.Config, e *echo.Echo) *api.APIConfig { + exp := export.New(cfg.Database) + col := collector.New(cfg.Database) + que := query.New(cfg.Database) - return grpcServer + services := &queryservice.Services{ + Export: exp, + JobCollector: col, + Query: que, + } + + cons := queryservice.NewControllers(cfg.Validator, services) + + queryservice.RegisterHandlersWithBaseURL(e, cons, "") + + swagger, err := queryservice.GetSwagger() + if err != nil { + log.Panicf("Error loading swagger: %s", err) + } + + return &api.APIConfig{ + Swagger: swagger, + } } server := api.New(ctx, &api.Config{ - GetControllers: controllers, + RegisterHandlers: registerHandlers, }) server.Listen() diff --git a/devbox.json b/devbox.json index 9653c71a..5906ecc9 100644 --- a/devbox.json +++ b/devbox.json @@ -14,7 +14,8 @@ "go-task@3.39.2", "hadolint@2.12.0", "gotools@0.25.0", - "nodePackages.jsonlint@1.6.3" + "nodePackages.jsonlint@1.6.3", + "oapi-codegen@2.4.1" ], "shell": { "init_hook": [ @@ -33,7 +34,8 @@ "DB_PASS": "pass", "DB_HOST": "localhost", "DB_PORT": "5432", - "DB_NAME": "query_orchestration" + "DB_NAME": "query_orchestration", + "DB_NOSSL": "1" }, "env_from": ".env" } diff --git a/devbox.lock b/devbox.lock index c6b9737d..cf4fcdd1 100644 --- a/devbox.lock +++ b/devbox.lock @@ -389,6 +389,54 @@ } } }, + "oapi-codegen@2.4.1": { + "last_modified": "2024-12-23T21:10:33Z", + "resolved": "github:NixOS/nixpkgs/de1864217bfa9b5845f465e771e0ecb48b30e02d#oapi-codegen", + "source": "devbox-search", + "version": "2.4.1", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/k70yyicfc2wq4i4kpikql4aj8lbwrw0w-oapi-codegen-2.4.1", + "default": true + } + ], + "store_path": "/nix/store/k70yyicfc2wq4i4kpikql4aj8lbwrw0w-oapi-codegen-2.4.1" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/7wazb5glxl4nx6ibxawcdlmykqzalg35-oapi-codegen-2.4.1", + "default": true + } + ], + "store_path": "/nix/store/7wazb5glxl4nx6ibxawcdlmykqzalg35-oapi-codegen-2.4.1" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/pgh1zsh740gfnfr9c1l92i6q1rqmha50-oapi-codegen-2.4.1", + "default": true + } + ], + "store_path": "/nix/store/pgh1zsh740gfnfr9c1l92i6q1rqmha50-oapi-codegen-2.4.1" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/yws21f8ff1a82l63d0xk1gniir43c8y5-oapi-codegen-2.4.1", + "default": true + } + ], + "store_path": "/nix/store/yws21f8ff1a82l63d0xk1gniir43c8y5-oapi-codegen-2.4.1" + } + } + }, "protobuf@28.3": { "last_modified": "2024-12-03T12:40:06Z", "resolved": "github:NixOS/nixpkgs/566e53c2ad750c84f6d31f9ccb9d00f823165550#protobuf", diff --git a/go.mod b/go.mod index 30619cc9..e5bd72d4 100644 --- a/go.mod +++ b/go.mod @@ -6,42 +6,70 @@ require ( github.com/aws/aws-sdk-go-v2 v1.32.5 github.com/aws/aws-sdk-go-v2/config v1.28.5 github.com/aws/aws-sdk-go-v2/service/sqs v1.37.1 + github.com/getkin/kin-openapi v0.128.0 github.com/go-playground/validator/v10 v10.23.0 github.com/golang-migrate/migrate/v4 v4.18.1 github.com/jackc/pgx/v5 v5.7.1 + github.com/labstack/echo/v4 v4.13.3 github.com/lib/pq v1.10.9 + github.com/oapi-codegen/runtime v1.1.1 github.com/stretchr/testify v1.10.0 + github.com/swaggo/echo-swagger v1.4.1 github.com/testcontainers/testcontainers-go v0.34.0 github.com/tidwall/gjson v1.18.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 go.opentelemetry.io/otel/sdk v1.33.0 - google.golang.org/grpc v1.68.1 - google.golang.org/protobuf v1.35.2 ) require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/invopop/yaml v0.3.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/labstack/gommon v0.4.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/moby/sys/userns v0.1.0 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/stretchr/objx v0.5.2 // indirect + github.com/swaggo/files/v2 v2.0.0 // indirect + github.com/swaggo/swag v1.8.12 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect go.opentelemetry.io/proto/otlp v1.4.0 // indirect go.uber.org/atomic v1.7.0 // indirect golang.org/x/sync v0.10.0 // indirect + golang.org/x/time v0.8.0 // indirect + golang.org/x/tools v0.24.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/grpc v1.68.1 // indirect + google.golang.org/protobuf v1.35.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) require ( @@ -99,8 +127,8 @@ require ( go.opentelemetry.io/otel v1.33.0 go.opentelemetry.io/otel/metric v1.33.0 // indirect go.opentelemetry.io/otel/trace v1.33.0 // indirect - golang.org/x/crypto v0.30.0 // indirect - golang.org/x/net v0.32.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect diff --git a/go.sum b/go.sum index 16061620..ab3fb738 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,17 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/aws/aws-sdk-go-v2 v1.32.5 h1:U8vdWJuY7ruAkzaOdD7guwJjD06YSKmnKCJs7s3IkIo= github.com/aws/aws-sdk-go-v2 v1.32.5/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0= @@ -34,6 +43,7 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 h1:6SZUVRQNvExYlMLbHdlKB48x0fLb github.com/aws/aws-sdk-go-v2/service/sts v1.33.1/go.mod h1:GqWyYCwLXnlUB1lOAXQyNSPqPLQJvmo8J0DWBzp9mtg= github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -42,6 +52,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -61,6 +72,10 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/getkin/kin-openapi v0.128.0 h1:jqq3D9vC9pPq1dGcOCv7yOp1DaEe7c/T1vzcLbITSp4= +github.com/getkin/kin-openapi v0.128.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -69,6 +84,18 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -77,6 +104,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y= @@ -96,6 +125,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= +github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -104,14 +135,24 @@ github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= +github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -121,6 +162,16 @@ github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a h1:3Bm7EwfUQUvhNe github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= @@ -133,14 +184,21 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= +github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pashagolub/pgxmock/v3 v3.4.0 h1:87VMr2q7m2+6VzXo4Tsp9kMklGlj6mMN19Hp/bp2Rwo= github.com/pashagolub/pgxmock/v3 v3.4.0/go.mod h1:FvCl7xqPbLLI3XohihJ1NzXnikjM3q/NWSixg4t9hrU= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -158,18 +216,26 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk= +github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc= +github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= +github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= +github.com/swaggo/swag v1.8.12 h1:pctzkNPu0AlQP2royqX3apjKCQonAnf7KGoxeO4y64w= +github.com/swaggo/swag v1.8.12/go.mod h1:lNfm6Gg+oAq3zRJQNEMBE66LIJKM44mxFqhEEgy2its= github.com/testcontainers/testcontainers-go v0.34.0 h1:5fbgF0vIN5u+nD3IWabQwRybuB4GY8G2HHgCkbMzMHo= github.com/testcontainers/testcontainers-go v0.34.0/go.mod h1:6P/kMkQe8yqPHfPWNulFGdFHTD8HB2vLq/231xY2iPQ= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= @@ -184,6 +250,12 @@ github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5I github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= @@ -215,16 +287,19 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -234,27 +309,36 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -268,9 +352,15 @@ google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe0 google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= diff --git a/internal/api/listener.go b/internal/api/listener.go index ba59fa69..5d924bde 100644 --- a/internal/api/listener.go +++ b/internal/api/listener.go @@ -4,23 +4,32 @@ import ( "context" "log" "net" + "net/http" "queryorchestration/internal/server" "strconv" + "github.com/getkin/kin-openapi/openapi3" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" _ "github.com/lib/pq" - "google.golang.org/grpc" + echoSwagger "github.com/swaggo/echo-swagger" + "gopkg.in/yaml.v3" ) type Config struct { - GetControllers func(*server.Config) *grpc.Server - BasePath string + RegisterHandlers func(*server.Config, *echo.Echo) *APIConfig + BasePath string +} + +type APIConfig struct { + Swagger *openapi3.T } type Server struct { - controllers *grpc.Server - port int - host string - address string + port int + host string + address string + server *echo.Echo } func New(ctx context.Context, cfg *Config) *Server { @@ -28,30 +37,58 @@ func New(ctx context.Context, cfg *Config) *Server { BasePath: cfg.BasePath, }) + e := echo.New() + + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + e.Use(middleware.CORS()) + + apicfg := cfg.RegisterHandlers(serverCfg, e) + + e.GET("/swagger/doc.json", func(c echo.Context) error { + spec, err := apicfg.Swagger.MarshalJSON() + if err != nil { + return err + } + return c.Blob(http.StatusOK, "application/json", spec) + }) + e.GET("/swagger/doc.yaml", func(c echo.Context) error { + spec, err := apicfg.Swagger.MarshalYAML() + if err != nil { + return err + } + yaml, err := yaml.Marshal(spec) + if err != nil { + return err + } + + return c.Blob(http.StatusOK, "application/yaml", yaml) + }) + + e.GET("/swagger/*", echoSwagger.EchoWrapHandler( + echoSwagger.DocExpansion("full"), + echoSwagger.DeepLinking(true), + echoSwagger.DomID("swagger-ui"), + echoSwagger.PersistAuthorization(true), + )) + host := "0.0.0.0" port := 8080 address := net.JoinHostPort(host, strconv.Itoa(port)) - controllers := cfg.GetControllers(serverCfg) - return &Server{ - controllers: controllers, - port: port, - host: host, - address: address, + port: port, + host: host, + address: address, + server: e, } } func (s *Server) Listen() { - lis, err := net.Listen("tcp", s.address) - if err != nil { - log.Panicf("failed to listen: %v", err) - } - log.Printf("Listening on port %d", s.port) - err = s.controllers.Serve(lis) + err := s.server.Start(s.address) if err != nil { - log.Panicf("Failed to serve: %v", err) + s.server.Logger.Fatal(err) } } diff --git a/internal/api/listener_test.go b/internal/api/listener_test.go index 804a495a..195271f0 100644 --- a/internal/api/listener_test.go +++ b/internal/api/listener_test.go @@ -9,8 +9,8 @@ import ( "queryorchestration/internal/test" "testing" + "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" - "google.golang.org/grpc" ) func TestNew(t *testing.T) { @@ -23,12 +23,12 @@ func TestNew(t *testing.T) { }) defer cleanup() - getControllers := func(c *server.Config) *grpc.Server { + registerHandlers := func(c *server.Config, e *echo.Echo) *APIConfig { return nil } cfg := &Config{ - GetControllers: getControllers, - BasePath: path.Join(os.Getenv("PWD"), "../.."), + RegisterHandlers: registerHandlers, + BasePath: path.Join(os.Getenv("PWD"), "../.."), } server := New(ctx, cfg) diff --git a/internal/database/migrations_test.go b/internal/database/migrations_test.go index 67aba862..c06db865 100644 --- a/internal/database/migrations_test.go +++ b/internal/database/migrations_test.go @@ -25,6 +25,13 @@ func TestRunMigrations(t *testing.T) { func TestRunMigrationsNoDB(t *testing.T) { ctx := context.Background() + t.Setenv("DB_USER", "invalid_user") + t.Setenv("DB_PASS", "invalid_pass") + t.Setenv("DB_HOST", "invalid_host") + t.Setenv("DB_PORT", "5432") + t.Setenv("DB_NAME", "invalid_name") + t.Setenv("DB_NOSSL", "1") + assert.Panics(t, func() { database.RunMigrations(ctx, &database.MigrationConfig{ BasePath: path.Join(os.Getenv("PWD"), "../.."), diff --git a/internal/queue/receive_test.go b/internal/queue/receive_test.go index a62d4522..ed3f69d9 100644 --- a/internal/queue/receive_test.go +++ b/internal/queue/receive_test.go @@ -2,7 +2,6 @@ package queue_test import ( "context" - "log" "queryorchestration/internal/queue" "queryorchestration/internal/test" "testing" @@ -31,7 +30,5 @@ func TestReceive(t *testing.T) { assert.Len(t, result.Messages, 1) message := result.Messages[0] - log.Print(message.Attributes) - assert.Equal(t, "\"example_body\"", *message.Body) } diff --git a/internal/test/api.go b/internal/test/api.go index d17483b7..84b3a4c4 100644 --- a/internal/test/api.go +++ b/internal/test/api.go @@ -7,8 +7,6 @@ import ( "github.com/docker/go-connections/nat" "github.com/testcontainers/testcontainers-go" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" ) type APIContainerConfig struct { @@ -17,7 +15,7 @@ type APIContainerConfig struct { Network *testcontainers.DockerNetwork } -func CreateAPIContainer(t *testing.T, ctx context.Context, config *APIContainerConfig) (*grpc.ClientConn, func()) { +func CreateAPIContainer(t *testing.T, ctx context.Context, config *APIContainerConfig) (string, func()) { port, err := nat.NewPort("tcp", "8080") if err != nil { t.Fatalf("Failed to create port: %v", err) @@ -40,38 +38,23 @@ func CreateAPIContainer(t *testing.T, ctx context.Context, config *APIContainerC t.Fatalf("Failed to extract port: %v", err) } - conn, err := grpc.NewClient( - fmt.Sprintf("%s:%s", host, mappedPort.Port()), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - if err != nil { - t.Fatalf("Failed to connect to gRPC server: %v", err) - } - - return conn, func() { - err = conn.Close() - if err != nil { - t.Error(err) - } - - cleanup() - } + return fmt.Sprintf("http://%s:%s", host, mappedPort.Port()), cleanup } -func CreateAPIWithDependencies(t *testing.T, ctx context.Context, serviceName string) (*grpc.ClientConn, func()) { +func CreateAPIWithDependencies(t *testing.T, ctx context.Context, serviceName string) (string, func()) { network, ncleanup := CreateNetwork(t, ctx) dbconfig, dbcleanup := CreateDB(t, ctx, &CreateDatabaseConfig{ Network: network, }) - conn, ccleanup := CreateAPIContainer(t, ctx, &APIContainerConfig{ + address, ccleanup := CreateAPIContainer(t, ctx, &APIContainerConfig{ ServiceName: serviceName, DB: dbconfig.External, Network: network, }) - return conn, func() { + return address, func() { ncleanup() dbcleanup() ccleanup() diff --git a/pkg/queryService/api.gen.go b/pkg/queryService/api.gen.go new file mode 100644 index 00000000..4cce23e7 --- /dev/null +++ b/pkg/queryService/api.gen.go @@ -0,0 +1,1509 @@ +// Package queryservice provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version 2.4.1 DO NOT EDIT. +package queryservice + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/oapi-codegen/runtime" +) + +// Defines values for QueryType. +const ( + CONTEXTFULL QueryType = "CONTEXT_FULL" + JSONEXTRACTOR QueryType = "JSON_EXTRACTOR" +) + +// ExportTrigger Payload for triggering an export. +type ExportTrigger struct { + // ExampleProperty Example property for ExportTrigger. + ExampleProperty *string `json:"example_property,omitempty"` +} + +// IdMessage defines model for IdMessage. +type IdMessage struct { + // Id Unique identifier. + Id string `json:"id"` +} + +// JobCollector JobCollector model. +type JobCollector struct { + // ExampleProperty Example property for JobCollector. + ExampleProperty *string `json:"example_property,omitempty"` +} + +// JobCollectorCreate Payload for creating a JobCollector. +type JobCollectorCreate struct { + // ExampleProperty Example property for JobCollectorCreate. + ExampleProperty *string `json:"example_property,omitempty"` +} + +// JobCollectorUpdate Payload for updating a JobCollector. +type JobCollectorUpdate struct { + // ExampleProperty Example property for JobCollectorUpdate. + ExampleProperty *string `json:"example_property,omitempty"` +} + +// ListQueries defines model for ListQueries. +type ListQueries struct { + // Queries List of queries. + Queries []Query `json:"queries"` +} + +// Query defines model for Query. +type Query struct { + // ActiveVersion The active version of the query. + ActiveVersion int32 `json:"active_version"` + + // Config Configuration for the query. + Config *string `json:"config,omitempty"` + + // Id Unique identifier for the query. + Id string `json:"id"` + + // LatestVersion The latest version of the query. + LatestVersion int32 `json:"latest_version"` + + // RequiredQueries List of required query IDs. + RequiredQueries []string `json:"required_queries"` + + // Type Specifies the type of the query. + Type QueryType `json:"type"` +} + +// QueryCreate defines model for QueryCreate. +type QueryCreate struct { + // Config Configuration for the new query. + Config *string `json:"config,omitempty"` + + // RequiredQueries List of required query IDs. + RequiredQueries *[]string `json:"required_queries,omitempty"` + + // Type Specifies the type of the query. + Type QueryType `json:"type"` +} + +// QueryTestRequest defines model for QueryTestRequest. +type QueryTestRequest struct { + // DocumentId ID of the document to test against. + DocumentId string `json:"document_id"` + + // QueryVersion Version of the query to use for testing. + QueryVersion int32 `json:"query_version"` +} + +// QueryTestResponse defines model for QueryTestResponse. +type QueryTestResponse struct { + // Value Result of the query test. + Value string `json:"value"` +} + +// QueryType Specifies the type of the query. +type QueryType string + +// QueryUpdate defines model for QueryUpdate. +type QueryUpdate struct { + // ActiveVersion Updated active version. + ActiveVersion *int32 `json:"active_version,omitempty"` + + // Config Updated configuration for the query. + Config *string `json:"config,omitempty"` + + // RequiredQueries Updated list of required query IDs. + RequiredQueries *[]string `json:"required_queries,omitempty"` +} + +// TriggerExportJSONRequestBody defines body for TriggerExport for application/json ContentType. +type TriggerExportJSONRequestBody = ExportTrigger + +// CreateJobCollectorJSONRequestBody defines body for CreateJobCollector for application/json ContentType. +type CreateJobCollectorJSONRequestBody = JobCollectorCreate + +// UpdateJobCollectorJSONRequestBody defines body for UpdateJobCollector for application/json ContentType. +type UpdateJobCollectorJSONRequestBody = JobCollectorUpdate + +// CreateQueryJSONRequestBody defines body for CreateQuery for application/json ContentType. +type CreateQueryJSONRequestBody = QueryCreate + +// UpdateQueryJSONRequestBody defines body for UpdateQuery for application/json ContentType. +type UpdateQueryJSONRequestBody = QueryUpdate + +// TestQueryJSONRequestBody defines body for TestQuery for application/json ContentType. +type TestQueryJSONRequestBody = QueryTestRequest + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // TriggerExportWithBody request with any body + TriggerExportWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + TriggerExport(ctx context.Context, body TriggerExportJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateJobCollectorWithBody request with any body + CreateJobCollectorWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateJobCollector(ctx context.Context, body CreateJobCollectorJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetJobCollectorById request + GetJobCollectorById(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateJobCollectorWithBody request with any body + UpdateJobCollectorWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateJobCollector(ctx context.Context, id string, body UpdateJobCollectorJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ListQueries request + ListQueries(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateQueryWithBody request with any body + CreateQueryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateQuery(ctx context.Context, body CreateQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeprecateQuery request + DeprecateQuery(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetQueryById request + GetQueryById(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateQueryWithBody request with any body + UpdateQueryWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateQuery(ctx context.Context, id string, body UpdateQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // TestQueryWithBody request with any body + TestQueryWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + TestQuery(ctx context.Context, id string, body TestQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) TriggerExportWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTriggerExportRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) TriggerExport(ctx context.Context, body TriggerExportJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTriggerExportRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateJobCollectorWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateJobCollectorRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateJobCollector(ctx context.Context, body CreateJobCollectorJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateJobCollectorRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetJobCollectorById(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetJobCollectorByIdRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateJobCollectorWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateJobCollectorRequestWithBody(c.Server, id, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateJobCollector(ctx context.Context, id string, body UpdateJobCollectorJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateJobCollectorRequest(c.Server, id, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ListQueries(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListQueriesRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateQueryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateQueryRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateQuery(ctx context.Context, body CreateQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateQueryRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeprecateQuery(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeprecateQueryRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetQueryById(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetQueryByIdRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateQueryWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateQueryRequestWithBody(c.Server, id, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateQuery(ctx context.Context, id string, body UpdateQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateQueryRequest(c.Server, id, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) TestQueryWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTestQueryRequestWithBody(c.Server, id, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) TestQuery(ctx context.Context, id string, body TestQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewTestQueryRequest(c.Server, id, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewTriggerExportRequest calls the generic TriggerExport builder with application/json body +func NewTriggerExportRequest(server string, body TriggerExportJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewTriggerExportRequestWithBody(server, "application/json", bodyReader) +} + +// NewTriggerExportRequestWithBody generates requests for TriggerExport with any type of body +func NewTriggerExportRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/exports/trigger") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewCreateJobCollectorRequest calls the generic CreateJobCollector builder with application/json body +func NewCreateJobCollectorRequest(server string, body CreateJobCollectorJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateJobCollectorRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreateJobCollectorRequestWithBody generates requests for CreateJobCollector with any type of body +func NewCreateJobCollectorRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/job-collectors") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewGetJobCollectorByIdRequest generates requests for GetJobCollectorById +func NewGetJobCollectorByIdRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/job-collectors/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUpdateJobCollectorRequest calls the generic UpdateJobCollector builder with application/json body +func NewUpdateJobCollectorRequest(server string, id string, body UpdateJobCollectorJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateJobCollectorRequestWithBody(server, id, "application/json", bodyReader) +} + +// NewUpdateJobCollectorRequestWithBody generates requests for UpdateJobCollector with any type of body +func NewUpdateJobCollectorRequestWithBody(server string, id string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/job-collectors/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewListQueriesRequest generates requests for ListQueries +func NewListQueriesRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/queries") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateQueryRequest calls the generic CreateQuery builder with application/json body +func NewCreateQueryRequest(server string, body CreateQueryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateQueryRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreateQueryRequestWithBody generates requests for CreateQuery with any type of body +func NewCreateQueryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/queries") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewDeprecateQueryRequest generates requests for DeprecateQuery +func NewDeprecateQueryRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/queries/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetQueryByIdRequest generates requests for GetQueryById +func NewGetQueryByIdRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/queries/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUpdateQueryRequest calls the generic UpdateQuery builder with application/json body +func NewUpdateQueryRequest(server string, id string, body UpdateQueryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateQueryRequestWithBody(server, id, "application/json", bodyReader) +} + +// NewUpdateQueryRequestWithBody generates requests for UpdateQuery with any type of body +func NewUpdateQueryRequestWithBody(server string, id string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/queries/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PATCH", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewTestQueryRequest calls the generic TestQuery builder with application/json body +func NewTestQueryRequest(server string, id string, body TestQueryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewTestQueryRequestWithBody(server, id, "application/json", bodyReader) +} + +// NewTestQueryRequestWithBody generates requests for TestQuery with any type of body +func NewTestQueryRequestWithBody(server string, id string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/queries/%s/test", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // TriggerExportWithBodyWithResponse request with any body + TriggerExportWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TriggerExportResponse, error) + + TriggerExportWithResponse(ctx context.Context, body TriggerExportJSONRequestBody, reqEditors ...RequestEditorFn) (*TriggerExportResponse, error) + + // CreateJobCollectorWithBodyWithResponse request with any body + CreateJobCollectorWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateJobCollectorResponse, error) + + CreateJobCollectorWithResponse(ctx context.Context, body CreateJobCollectorJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateJobCollectorResponse, error) + + // GetJobCollectorByIdWithResponse request + GetJobCollectorByIdWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetJobCollectorByIdResponse, error) + + // UpdateJobCollectorWithBodyWithResponse request with any body + UpdateJobCollectorWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateJobCollectorResponse, error) + + UpdateJobCollectorWithResponse(ctx context.Context, id string, body UpdateJobCollectorJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateJobCollectorResponse, error) + + // ListQueriesWithResponse request + ListQueriesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListQueriesResponse, error) + + // CreateQueryWithBodyWithResponse request with any body + CreateQueryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateQueryResponse, error) + + CreateQueryWithResponse(ctx context.Context, body CreateQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateQueryResponse, error) + + // DeprecateQueryWithResponse request + DeprecateQueryWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DeprecateQueryResponse, error) + + // GetQueryByIdWithResponse request + GetQueryByIdWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetQueryByIdResponse, error) + + // UpdateQueryWithBodyWithResponse request with any body + UpdateQueryWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateQueryResponse, error) + + UpdateQueryWithResponse(ctx context.Context, id string, body UpdateQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateQueryResponse, error) + + // TestQueryWithBodyWithResponse request with any body + TestQueryWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TestQueryResponse, error) + + TestQueryWithResponse(ctx context.Context, id string, body TestQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*TestQueryResponse, error) +} + +type TriggerExportResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *IdMessage +} + +// Status returns HTTPResponse.Status +func (r TriggerExportResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r TriggerExportResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateJobCollectorResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *IdMessage +} + +// Status returns HTTPResponse.Status +func (r CreateJobCollectorResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateJobCollectorResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetJobCollectorByIdResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *JobCollector +} + +// Status returns HTTPResponse.Status +func (r GetJobCollectorByIdResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetJobCollectorByIdResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UpdateJobCollectorResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r UpdateJobCollectorResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateJobCollectorResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ListQueriesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *ListQueries +} + +// Status returns HTTPResponse.Status +func (r ListQueriesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListQueriesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateQueryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *IdMessage +} + +// Status returns HTTPResponse.Status +func (r CreateQueryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateQueryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeprecateQueryResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r DeprecateQueryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeprecateQueryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetQueryByIdResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Query +} + +// Status returns HTTPResponse.Status +func (r GetQueryByIdResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetQueryByIdResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UpdateQueryResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r UpdateQueryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateQueryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type TestQueryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *QueryTestResponse +} + +// Status returns HTTPResponse.Status +func (r TestQueryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r TestQueryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// TriggerExportWithBodyWithResponse request with arbitrary body returning *TriggerExportResponse +func (c *ClientWithResponses) TriggerExportWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TriggerExportResponse, error) { + rsp, err := c.TriggerExportWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseTriggerExportResponse(rsp) +} + +func (c *ClientWithResponses) TriggerExportWithResponse(ctx context.Context, body TriggerExportJSONRequestBody, reqEditors ...RequestEditorFn) (*TriggerExportResponse, error) { + rsp, err := c.TriggerExport(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseTriggerExportResponse(rsp) +} + +// CreateJobCollectorWithBodyWithResponse request with arbitrary body returning *CreateJobCollectorResponse +func (c *ClientWithResponses) CreateJobCollectorWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateJobCollectorResponse, error) { + rsp, err := c.CreateJobCollectorWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateJobCollectorResponse(rsp) +} + +func (c *ClientWithResponses) CreateJobCollectorWithResponse(ctx context.Context, body CreateJobCollectorJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateJobCollectorResponse, error) { + rsp, err := c.CreateJobCollector(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateJobCollectorResponse(rsp) +} + +// GetJobCollectorByIdWithResponse request returning *GetJobCollectorByIdResponse +func (c *ClientWithResponses) GetJobCollectorByIdWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetJobCollectorByIdResponse, error) { + rsp, err := c.GetJobCollectorById(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetJobCollectorByIdResponse(rsp) +} + +// UpdateJobCollectorWithBodyWithResponse request with arbitrary body returning *UpdateJobCollectorResponse +func (c *ClientWithResponses) UpdateJobCollectorWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateJobCollectorResponse, error) { + rsp, err := c.UpdateJobCollectorWithBody(ctx, id, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateJobCollectorResponse(rsp) +} + +func (c *ClientWithResponses) UpdateJobCollectorWithResponse(ctx context.Context, id string, body UpdateJobCollectorJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateJobCollectorResponse, error) { + rsp, err := c.UpdateJobCollector(ctx, id, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateJobCollectorResponse(rsp) +} + +// ListQueriesWithResponse request returning *ListQueriesResponse +func (c *ClientWithResponses) ListQueriesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListQueriesResponse, error) { + rsp, err := c.ListQueries(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseListQueriesResponse(rsp) +} + +// CreateQueryWithBodyWithResponse request with arbitrary body returning *CreateQueryResponse +func (c *ClientWithResponses) CreateQueryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateQueryResponse, error) { + rsp, err := c.CreateQueryWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateQueryResponse(rsp) +} + +func (c *ClientWithResponses) CreateQueryWithResponse(ctx context.Context, body CreateQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateQueryResponse, error) { + rsp, err := c.CreateQuery(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateQueryResponse(rsp) +} + +// DeprecateQueryWithResponse request returning *DeprecateQueryResponse +func (c *ClientWithResponses) DeprecateQueryWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DeprecateQueryResponse, error) { + rsp, err := c.DeprecateQuery(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeprecateQueryResponse(rsp) +} + +// GetQueryByIdWithResponse request returning *GetQueryByIdResponse +func (c *ClientWithResponses) GetQueryByIdWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetQueryByIdResponse, error) { + rsp, err := c.GetQueryById(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetQueryByIdResponse(rsp) +} + +// UpdateQueryWithBodyWithResponse request with arbitrary body returning *UpdateQueryResponse +func (c *ClientWithResponses) UpdateQueryWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateQueryResponse, error) { + rsp, err := c.UpdateQueryWithBody(ctx, id, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateQueryResponse(rsp) +} + +func (c *ClientWithResponses) UpdateQueryWithResponse(ctx context.Context, id string, body UpdateQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateQueryResponse, error) { + rsp, err := c.UpdateQuery(ctx, id, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateQueryResponse(rsp) +} + +// TestQueryWithBodyWithResponse request with arbitrary body returning *TestQueryResponse +func (c *ClientWithResponses) TestQueryWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TestQueryResponse, error) { + rsp, err := c.TestQueryWithBody(ctx, id, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseTestQueryResponse(rsp) +} + +func (c *ClientWithResponses) TestQueryWithResponse(ctx context.Context, id string, body TestQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*TestQueryResponse, error) { + rsp, err := c.TestQuery(ctx, id, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseTestQueryResponse(rsp) +} + +// ParseTriggerExportResponse parses an HTTP response from a TriggerExportWithResponse call +func ParseTriggerExportResponse(rsp *http.Response) (*TriggerExportResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &TriggerExportResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest IdMessage + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseCreateJobCollectorResponse parses an HTTP response from a CreateJobCollectorWithResponse call +func ParseCreateJobCollectorResponse(rsp *http.Response) (*CreateJobCollectorResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateJobCollectorResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest IdMessage + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseGetJobCollectorByIdResponse parses an HTTP response from a GetJobCollectorByIdWithResponse call +func ParseGetJobCollectorByIdResponse(rsp *http.Response) (*GetJobCollectorByIdResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetJobCollectorByIdResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest JobCollector + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUpdateJobCollectorResponse parses an HTTP response from a UpdateJobCollectorWithResponse call +func ParseUpdateJobCollectorResponse(rsp *http.Response) (*UpdateJobCollectorResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateJobCollectorResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseListQueriesResponse parses an HTTP response from a ListQueriesWithResponse call +func ParseListQueriesResponse(rsp *http.Response) (*ListQueriesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListQueriesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ListQueries + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateQueryResponse parses an HTTP response from a CreateQueryWithResponse call +func ParseCreateQueryResponse(rsp *http.Response) (*CreateQueryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateQueryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest IdMessage + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseDeprecateQueryResponse parses an HTTP response from a DeprecateQueryWithResponse call +func ParseDeprecateQueryResponse(rsp *http.Response) (*DeprecateQueryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeprecateQueryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetQueryByIdResponse parses an HTTP response from a GetQueryByIdWithResponse call +func ParseGetQueryByIdResponse(rsp *http.Response) (*GetQueryByIdResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetQueryByIdResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Query + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUpdateQueryResponse parses an HTTP response from a UpdateQueryWithResponse call +func ParseUpdateQueryResponse(rsp *http.Response) (*UpdateQueryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateQueryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseTestQueryResponse parses an HTTP response from a TestQueryWithResponse call +func ParseTestQueryResponse(rsp *http.Response) (*TestQueryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &TestQueryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest QueryTestResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} diff --git a/scripts/Taskfile.yml b/scripts/Taskfile.yml index 80e74b0e..74e42300 100644 --- a/scripts/Taskfile.yml +++ b/scripts/Taskfile.yml @@ -17,9 +17,9 @@ includes: docker: dir: "{{.CONTEXT}}" taskfile: docker.yml - proto: + openapi: dir: "{{.CONTEXT}}" - taskfile: proto.yml + taskfile: openapi.yml compose: dir: "{{.CONTEXT}}" taskfile: compose.yml @@ -32,39 +32,40 @@ tasks: cmds: - touch .env fullsuite: - deps: - - lint - - test:unit:coverage - - test:integration + cmds: + - task lint + - task test:unit:coverage + - task test:integration generate: deps: - db:generate - - proto:generate + - openapi:generate lint: cmds: - - task code:lint + - task go:lint - task db:lint - task yaml:lint - task json:lint - task docker:lint - task compose:lint - - task proto:lint + - task openapi:lint lint:fix: cmds: - - task code:lint:fix - - task proto:lint:fix + - task go:lint:fix + - task openapi:lint:fix - task db:lint - task yaml:lint - task json:lint:fix - task docker:lint - task compose:lint - code:lint: + go:lint: cmds: - golangci-lint run - code:lint:fix: + go:lint:fix: + deps: + - go:lint cmds: - gofmt -w . - - golangci-lint run yaml:lint: cmds: - yamllint . -s diff --git a/scripts/openapi.yml b/scripts/openapi.yml new file mode 100644 index 00000000..dcf9a536 --- /dev/null +++ b/scripts/openapi.yml @@ -0,0 +1,52 @@ +--- +# https://taskfile.dev + +version: '3' + +tasks: + lint: + cmds: + - echo "OpenAPI lint" + lint:fix: + cmds: + - echo "OpenAPI lint:fix" + generate: + deps: + - generate:server + - generate:client + generate:client: + cmds: + - | + for file in serviceAPIs/*.yml serviceAPIs/*.yaml; do + if [[ -f "$file" ]]; then + service=$(basename "$file") + service=${service%.*} + service_lower=${service,,} + outputloc=./pkg/$service + mkdir -p $outputloc + oapi-codegen \ + -generate "models,client" \ + -config .openapi.yml \ + -package $service_lower \ + -o $outputloc/api.gen.go \ + $file + fi + done + generate:server: + cmds: + - | + for file in serviceAPIs/*.yml serviceAPIs/*.yaml; do + if [[ -f "$file" ]]; then + service=$(basename "$file") + service=${service%.*} + service_lower=${service,,} + outputloc=./api/$service + mkdir -p $outputloc + oapi-codegen \ + -generate "models,embedded-spec,echo-server" \ + -config .openapi.yml \ + -package $service_lower \ + -o $outputloc/api.gen.go \ + $file + fi + done diff --git a/scripts/proto.yml b/scripts/proto.yml deleted file mode 100644 index 2b3f773e..00000000 --- a/scripts/proto.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -# https://taskfile.dev - -version: '3' - -vars: - PROTO_DIR: serviceInterfaces - API_DIR: api - -tasks: - lint: - cmds: - - protolint lint {{.PROTO_DIR}} - lint:fix: - cmds: - - protolint lint -fix {{.PROTO_DIR}} - generate: - cmds: - - | - protoc --proto_path={{.PROTO_DIR}} \ - --go_out={{.API_DIR}}/serviceInterfaces --go-grpc_out={{.API_DIR}} \ - --go_opt=paths=source_relative --experimental_allow_proto3_optional \ - main.proto diff --git a/scripts/tests.yml b/scripts/tests.yml index 954fd878..1a57b92a 100644 --- a/scripts/tests.yml +++ b/scripts/tests.yml @@ -14,18 +14,22 @@ tasks: vars: INTERNAL: "./internal/..." API: "./api/..." + PKG: "./pkg/..." cmds: - mkdir -p {{.OUT_DIR}} - | - go test {{.INTERNAL}} {{.API}} \ - -coverpkg={{.INTERNAL}},{{.API}} -coverprofile={{.COVERAGE_FILE}} + go test {{.INTERNAL}} {{.API}} {{.PKG}} \ + -coverpkg={{.INTERNAL}},{{.API}},{{.PKG}} \ + -coverprofile={{.COVERAGE_FILE}} unit:coverage: deps: - unit vars: TMP_FILE: "{{.OUT_DIR}}/coverage.tmp" + FILTER_COVERAGE_FILE: "{{.OUT_DIR}}/filtered_coverage.out" cmds: - - go tool cover -func={{.COVERAGE_FILE}} > {{.TMP_FILE}} + - grep -v ".gen.go" {{.COVERAGE_FILE}} > {{.FILTER_COVERAGE_FILE}} + - go tool cover -func={{.FILTER_COVERAGE_FILE}} > {{.TMP_FILE}} - cat {{.TMP_FILE}} - | COVERAGE=$(grep total: {{.TMP_FILE}} \ diff --git a/serviceAPIs/queryService.yaml b/serviceAPIs/queryService.yaml new file mode 100644 index 00000000..5bcf5b76 --- /dev/null +++ b/serviceAPIs/queryService.yaml @@ -0,0 +1,403 @@ +--- +openapi: 3.0.3 +info: + title: Query Orchestration API + description: API documentation for the Query Orchestration services. + version: 1.0.0 +servers: + - url: https://api.example.com/v1 + description: Production server +tags: + - name: QueryService + description: Operations related to queries + - name: JobCollectorService + description: Operations related to job collectors + - name: ExportService + description: Operations related to exports + +paths: + /queries: + get: + operationId: listQueries + tags: + - QueryService + summary: List queries + description: Retrieves a list of queries based on filter criteria. + responses: + '200': + description: A list of queries. + content: + application/json: + schema: + $ref: '#/components/schemas/ListQueries' + '400': + description: Invalid request parameters. + '404': + description: Query not found. + post: + operationId: createQuery + tags: + - QueryService + summary: Create a new query + description: Creates a new query with the provided details. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/QueryCreate' + responses: + '201': + description: Query created successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/IdMessage' + '400': + description: Invalid request body. + + /queries/{id}: + get: + operationId: getQueryById + tags: + - QueryService + summary: Get a query by ID + description: Retrieves a specific query by its ID. + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The ID of the query to retrieve. + responses: + '200': + description: Query details. + content: + application/json: + schema: + $ref: '#/components/schemas/Query' + '400': + description: Invalid request parameters. + '404': + description: Query not found. + patch: + operationId: updateQuery + tags: + - QueryService + summary: Update a query + description: Updates an existing query with new details. + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The ID of the query to update. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/QueryUpdate' + responses: + '200': + description: Query updated successfully. + '400': + description: Invalid request body. + delete: + operationId: deprecateQuery + tags: + - QueryService + summary: Deprecate a query + description: Deprecates a specific query by its ID. + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The ID of the query to deprecate. + responses: + '200': + description: Query deprecated successfully. + '400': + description: Invalid request body. + '404': + description: Query not found. + + /queries/{id}/test: + post: + operationId: testQuery + tags: + - QueryService + summary: Test a query + description: Executes a test run of a query with the provided parameters. + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The ID of the query to test. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/QueryTestRequest' + responses: + '200': + description: Test result. + content: + application/json: + schema: + $ref: '#/components/schemas/QueryTestResponse' + '400': + description: Invalid request body. + + /job-collectors: + post: + operationId: createJobCollector + tags: + - JobCollectorService + summary: Create a job collector + description: Creates a new job collector. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/JobCollectorCreate' + responses: + '201': + description: Job collector created successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/IdMessage' + '400': + description: Invalid request body. + + /job-collectors/{id}: + get: + operationId: getJobCollectorById + tags: + - JobCollectorService + summary: Get a job collector by ID + description: Retrieves a specific job collector by its ID. + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The ID of the job collector to retrieve. + responses: + '200': + description: Job collector details. + content: + application/json: + schema: + $ref: '#/components/schemas/JobCollector' + '404': + description: Job collector not found. + put: + operationId: updateJobCollector + tags: + - JobCollectorService + summary: Update a job collector + description: Updates an existing job collector with new details. + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The ID of the job collector to update. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/JobCollectorUpdate' + responses: + '204': + description: Job collector updated successfully. + '400': + description: Invalid request body. + '404': + description: Job collector not found. + + /exports/trigger: + post: + operationId: triggerExport + tags: + - ExportService + summary: Trigger an export + description: Initiates the export process. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExportTrigger' + responses: + '201': + description: Export triggered successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/IdMessage' + '400': + description: Invalid request body. + + +components: + schemas: + QueryType: + type: string + enum: + - JSON_EXTRACTOR + - CONTEXT_FULL + description: Specifies the type of the query. + + Query: + type: object + properties: + id: + type: string + description: Unique identifier for the query. + type: + $ref: '#/components/schemas/QueryType' + description: Type of the query. + active_version: + type: integer + format: int32 + description: The active version of the query. + latest_version: + type: integer + format: int32 + description: The latest version of the query. + config: + type: string + description: Configuration for the query. + required_queries: + type: array + items: + type: string + description: List of required query IDs. + required: + - id + - type + - active_version + - latest_version + - required_queries + + ListQueries: + type: object + properties: + queries: + type: array + items: + $ref: '#/components/schemas/Query' + description: List of queries. + required: + - queries + + QueryCreate: + type: object + properties: + type: + $ref: '#/components/schemas/QueryType' + description: Type of the query to create. + config: + type: string + description: Configuration for the new query. + required_queries: + type: array + items: + type: string + description: List of required query IDs. + required: + - type + + QueryUpdate: + type: object + properties: + config: + type: string + description: Updated configuration for the query. + active_version: + type: integer + format: int32 + description: Updated active version. + required_queries: + type: array + items: + type: string + description: Updated list of required query IDs. + + QueryTestRequest: + type: object + properties: + document_id: + type: string + description: ID of the document to test against. + query_version: + type: integer + format: int32 + description: Version of the query to use for testing. + required: + - document_id + - query_version + + QueryTestResponse: + type: object + properties: + value: + type: string + description: Result of the query test. + required: + - value + + IdMessage: + type: object + properties: + id: + type: string + description: Unique identifier. + required: + - id + + JobCollector: + type: object + properties: + example_property: + type: string + description: Example property for JobCollector. + description: JobCollector model. + + JobCollectorCreate: + type: object + properties: + example_property: + type: string + description: Example property for JobCollectorCreate. + description: Payload for creating a JobCollector. + + JobCollectorUpdate: + type: object + properties: + example_property: + type: string + description: Example property for JobCollectorUpdate. + description: Payload for updating a JobCollector. + + ExportTrigger: + type: object + properties: + example_property: + type: string + description: Example property for ExportTrigger. + description: Payload for triggering an export. diff --git a/serviceInterfaces/main.proto b/serviceInterfaces/main.proto deleted file mode 100644 index a9fa4c4d..00000000 --- a/serviceInterfaces/main.proto +++ /dev/null @@ -1,84 +0,0 @@ -syntax = "proto3"; - -package queryorchestration; - -import "google/protobuf/empty.proto"; - -option go_package = "./serviceinterfaces"; - -service QueryService { - rpc List(QueryFilter) returns (Queries); - rpc Get(IdMessage) returns (Query); - rpc Create(QueryCreate) returns (IdMessage); - rpc Update(QueryUpdate) returns (google.protobuf.Empty); - rpc Deprecate(IdMessage) returns (google.protobuf.Empty); - rpc Test(QueryTestRequest) returns (QueryTestResponse); -} - -message QueryTestRequest { - string query_id = 1; - string document_id = 2; - int32 query_version = 3; -} - -message QueryTestResponse { - string value = 1; -} - -enum QueryType { - QUERY_TYPE_UNSPECIFIED = 0; - QUERY_TYPE_JSON_EXTRACTOR = 1; - QUERY_TYPE_CONTEXT_FULL = 2; -} - -message Query { - string id = 1; - QueryType type = 2; - int32 active_version = 3; - int32 latest_version = 4; - optional string config = 5; - repeated string required_queries = 6; -} - -message QueryFilter {} - -message QueryCreate { - QueryType type = 1; - optional string config = 2; - repeated string required_queries = 3; -} - -message QueryUpdate { - string id = 1; - optional string config = 2; - optional int32 active_version = 3; - repeated string required_queries = 4; -} - -message Queries { - repeated Query queries = 1; -} - -service JobCollectorService { - rpc Create(JobCollectorCreate) returns (IdMessage); - rpc Update(JobCollectorUpdate) returns (google.protobuf.Empty); - rpc Get(IdMessage) returns (JobCollector); -} - -message JobCollector { -} -message JobCollectorCreate { -} -message JobCollectorUpdate { -} - -service ExportService { - rpc Trigger(ExportTrigger) returns (IdMessage); -} - -message ExportTrigger { -} - -message IdMessage { - string id = 1; -} diff --git a/test/queryService/exportservice_test.go b/test/queryService/exportservice_test.go index 1017a908..22bb4df3 100644 --- a/test/queryService/exportservice_test.go +++ b/test/queryService/exportservice_test.go @@ -2,8 +2,8 @@ package integration_test import ( "context" - serviceinterfaces "queryorchestration/api/serviceInterfaces" "queryorchestration/internal/test" + queryservice "queryorchestration/pkg/queryService" "testing" "github.com/stretchr/testify/assert" @@ -12,12 +12,13 @@ import ( func TestExportService(t *testing.T) { ctx := context.Background() - conn, cleanup := test.CreateAPIWithDependencies(t, ctx, "queryService") + address, cleanup := test.CreateAPIWithDependencies(t, ctx, "queryService") defer cleanup() - expClient := serviceinterfaces.NewExportServiceClient(conn) + client, err := queryservice.NewClientWithResponses(address) + assert.Nil(t, err) - idRes, err := expClient.Trigger(ctx, &serviceinterfaces.ExportTrigger{}) + idRes, err := client.TriggerExportWithResponse(ctx, queryservice.ExportTrigger{}) assert.Nil(t, err) assert.NotNil(t, idRes) } diff --git a/test/queryService/jobcollectorservice_test.go b/test/queryService/jobcollectorservice_test.go index 279cbf82..c59cb3a1 100644 --- a/test/queryService/jobcollectorservice_test.go +++ b/test/queryService/jobcollectorservice_test.go @@ -2,30 +2,34 @@ package integration_test import ( "context" - serviceinterfaces "queryorchestration/api/serviceInterfaces" "queryorchestration/internal/test" + queryservice "queryorchestration/pkg/queryService" "testing" + "github.com/google/uuid" "github.com/stretchr/testify/assert" ) func TestJobCollectorService(t *testing.T) { ctx := context.Background() - conn, cleanup := test.CreateAPIWithDependencies(t, ctx, "queryService") + address, cleanup := test.CreateAPIWithDependencies(t, ctx, "queryService") defer cleanup() - collClient := serviceinterfaces.NewJobCollectorServiceClient(conn) + client, err := queryservice.NewClientWithResponses(address) + assert.Nil(t, err) - idRes, err := collClient.Create(ctx, &serviceinterfaces.JobCollectorCreate{}) + idRes, err := client.CreateJobCollectorWithResponse(ctx, queryservice.JobCollectorCreate{}) assert.Nil(t, err) assert.NotNil(t, idRes) - collRes, err := collClient.Get(ctx, &serviceinterfaces.IdMessage{}) + id := uuid.New() + + collRes, err := client.GetJobCollectorByIdWithResponse(ctx, id.String()) assert.Nil(t, err) assert.NotNil(t, collRes) - res, err := collClient.Update(ctx, &serviceinterfaces.JobCollectorUpdate{}) + res, err := client.UpdateJobCollectorWithResponse(ctx, id.String(), queryservice.JobCollectorUpdate{}) assert.Nil(t, err) assert.NotNil(t, res) } diff --git a/test/queryService/listqueries_test.go b/test/queryService/listqueries_test.go deleted file mode 100644 index 10809ab0..00000000 --- a/test/queryService/listqueries_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package integration_test - -import ( - "context" - serviceinterfaces "queryorchestration/api/serviceInterfaces" - "queryorchestration/internal/test" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestQueryServiceList(t *testing.T) { - ctx := context.Background() - - conn, cleanup := test.CreateAPIWithDependencies(t, ctx, "queryService") - defer cleanup() - - client := serviceinterfaces.NewQueryServiceClient(conn) - - contextRes, err := client.Create(ctx, &serviceinterfaces.QueryCreate{ - Type: serviceinterfaces.QueryType_QUERY_TYPE_CONTEXT_FULL, - Config: nil, - RequiredQueries: []string{}, - }) - assert.Nil(t, err) - - config := "\"path\":\".key\"" - _, err = client.Create(ctx, &serviceinterfaces.QueryCreate{ - Type: serviceinterfaces.QueryType_QUERY_TYPE_JSON_EXTRACTOR, - Config: &config, - RequiredQueries: []string{ - contextRes.Id, - }, - }) - assert.Nil(t, err) - - queriesRes, err := client.List(ctx, &serviceinterfaces.QueryFilter{}) - assert.Nil(t, err) - assert.Len(t, queriesRes.Queries, 2) -} diff --git a/test/queryService/openapi_test.go b/test/queryService/openapi_test.go new file mode 100644 index 00000000..88c8f49e --- /dev/null +++ b/test/queryService/openapi_test.go @@ -0,0 +1,33 @@ +package integration_test + +import ( + "context" + "fmt" + "net/http" + "queryorchestration/internal/test" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestQueryServiceOpenAPI(t *testing.T) { + ctx := context.Background() + + address, cleanup := test.CreateAPIWithDependencies(t, ctx, "queryService") + defer cleanup() + + resp, err := http.Get(fmt.Sprintf("%s/swagger/doc.json", address)) + assert.Nil(t, err) + assert.NotNil(t, resp) + assert.Equal(t, http.StatusOK, resp.StatusCode) + + resp, err = http.Get(fmt.Sprintf("%s/swagger/doc.yaml", address)) + assert.Nil(t, err) + assert.NotNil(t, resp) + assert.Equal(t, http.StatusOK, resp.StatusCode) + + resp, err = http.Get(fmt.Sprintf("%s/swagger/index.html", address)) + assert.Nil(t, err) + assert.NotNil(t, resp) + assert.Equal(t, http.StatusOK, resp.StatusCode) +} diff --git a/test/queryService/queryservice_test.go b/test/queryService/queryservice_test.go index 6dfddefc..072d656c 100644 --- a/test/queryService/queryservice_test.go +++ b/test/queryService/queryservice_test.go @@ -2,8 +2,8 @@ package integration_test import ( "context" - serviceinterfaces "queryorchestration/api/serviceInterfaces" "queryorchestration/internal/test" + queryservice "queryorchestration/pkg/queryService" "testing" "github.com/stretchr/testify/assert" @@ -12,48 +12,49 @@ import ( func TestQueryService(t *testing.T) { ctx := context.Background() - conn, cleanup := test.CreateAPIWithDependencies(t, ctx, "queryService") + address, cleanup := test.CreateAPIWithDependencies(t, ctx, "queryService") defer cleanup() - client := serviceinterfaces.NewQueryServiceClient(conn) + client, err := queryservice.NewClientWithResponses(address) + assert.Nil(t, err) - idRes, err := client.Create(ctx, &serviceinterfaces.QueryCreate{ - Type: serviceinterfaces.QueryType_QUERY_TYPE_CONTEXT_FULL, - Config: nil, - RequiredQueries: []string{}, + idRes, err := client.CreateQueryWithResponse(ctx, queryservice.QueryCreate{ + Type: queryservice.CONTEXTFULL, }) assert.Nil(t, err) - id := idRes.GetId() + assert.NotNil(t, idRes) + assert.NotNil(t, idRes.JSON201) + id := idRes.JSON201.Id assert.NotEmpty(t, id) - queryRes, err := client.Get(ctx, &serviceinterfaces.IdMessage{ - Id: id, - }) + queryRes, err := client.GetQueryByIdWithResponse(ctx, id) assert.Nil(t, err) - assert.Equal(t, id, queryRes.Id) - assert.Equal(t, serviceinterfaces.QueryType_QUERY_TYPE_CONTEXT_FULL, queryRes.Type) - assert.Equal(t, int32(1), queryRes.ActiveVersion) - assert.Equal(t, int32(1), queryRes.LatestVersion) - assert.Equal(t, "", *queryRes.Config) - assert.Equal(t, []string(nil), queryRes.RequiredQueries) + assert.Equal(t, id, queryRes.JSON200.Id) + assert.Equal(t, queryservice.CONTEXTFULL, queryRes.JSON200.Type) + assert.Equal(t, int32(1), queryRes.JSON200.ActiveVersion) + assert.Equal(t, int32(1), queryRes.JSON200.LatestVersion) + assert.Nil(t, queryRes.JSON200.Config) + assert.Nil(t, queryRes.JSON200.RequiredQueries) - res, err := client.Update(ctx, &serviceinterfaces.QueryUpdate{ - Id: id, + queriesRes, err := client.ListQueriesWithResponse(ctx) + assert.Nil(t, err) + assert.Len(t, queriesRes.JSON200.Queries, 1) + assert.Equal(t, id, queriesRes.JSON200.Queries[0].Id) + + res, err := client.UpdateQueryWithResponse(ctx, id, queryservice.QueryUpdate{ Config: nil, ActiveVersion: nil, - RequiredQueries: []string{}, + RequiredQueries: &[]string{}, }) assert.Nil(t, err) assert.NotNil(t, res) - queryRes, err = client.Get(ctx, &serviceinterfaces.IdMessage{ - Id: id, - }) + queryRes, err = client.GetQueryByIdWithResponse(ctx, id) assert.Nil(t, err) - assert.Equal(t, id, queryRes.Id) - assert.Equal(t, serviceinterfaces.QueryType_QUERY_TYPE_CONTEXT_FULL, queryRes.Type) - assert.Equal(t, int32(1), queryRes.ActiveVersion) - assert.Equal(t, int32(1), queryRes.LatestVersion) - assert.Equal(t, "", *queryRes.Config) - assert.Equal(t, []string(nil), queryRes.RequiredQueries) + assert.Equal(t, id, queryRes.JSON200.Id) + assert.Equal(t, queryservice.CONTEXTFULL, queryRes.JSON200.Type) + assert.Equal(t, int32(1), queryRes.JSON200.ActiveVersion) + assert.Equal(t, int32(1), queryRes.JSON200.LatestVersion) + assert.Nil(t, queryRes.JSON200.Config) + assert.Nil(t, queryRes.JSON200.RequiredQueries) } diff --git a/test/queryService/testquery_test.go b/test/queryService/testquery_test.go index 83ec8864..cad5d702 100644 --- a/test/queryService/testquery_test.go +++ b/test/queryService/testquery_test.go @@ -2,8 +2,8 @@ package integration_test import ( "context" - serviceinterfaces "queryorchestration/api/serviceInterfaces" "queryorchestration/internal/test" + queryservice "queryorchestration/pkg/queryService" "testing" "github.com/google/uuid" @@ -13,24 +13,22 @@ import ( func TestQueryServiceTest(t *testing.T) { ctx := context.Background() - conn, cleanup := test.CreateAPIWithDependencies(t, ctx, "queryService") + address, cleanup := test.CreateAPIWithDependencies(t, ctx, "queryService") defer cleanup() - client := serviceinterfaces.NewQueryServiceClient(conn) + client, err := queryservice.NewClientWithResponses(address) + assert.Nil(t, err) - idRes, err := client.Create(ctx, &serviceinterfaces.QueryCreate{ - Type: serviceinterfaces.QueryType_QUERY_TYPE_CONTEXT_FULL, - Config: nil, - RequiredQueries: []string{}, + idRes, err := client.CreateQueryWithResponse(ctx, queryservice.QueryCreate{ + Type: queryservice.CONTEXTFULL, }) assert.Nil(t, err) - id := idRes.GetId() + id := idRes.JSON201.Id assert.NotEmpty(t, id) docId := uuid.New() - testRes, err := client.Test(ctx, &serviceinterfaces.QueryTestRequest{ - QueryId: id, + testRes, err := client.TestQueryWithResponse(ctx, id, queryservice.QueryTestRequest{ DocumentId: docId.String(), QueryVersion: int32(1), }) diff --git a/vendor/github.com/KyleBanks/depth/.gitignore b/vendor/github.com/KyleBanks/depth/.gitignore new file mode 100644 index 00000000..8c2e171e --- /dev/null +++ b/vendor/github.com/KyleBanks/depth/.gitignore @@ -0,0 +1,16 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ + +bin/ diff --git a/vendor/github.com/KyleBanks/depth/.travis.yml b/vendor/github.com/KyleBanks/depth/.travis.yml new file mode 100644 index 00000000..ab506f93 --- /dev/null +++ b/vendor/github.com/KyleBanks/depth/.travis.yml @@ -0,0 +1,9 @@ +language: go +sudo: false +go: + - 1.9.x +before_install: + - go get github.com/mattn/goveralls +script: + - $HOME/gopath/bin/goveralls -service=travis-ci +#script: go test $(go list ./... | grep -v vendor/) diff --git a/vendor/github.com/KyleBanks/depth/LICENSE b/vendor/github.com/KyleBanks/depth/LICENSE new file mode 100644 index 00000000..070b426b --- /dev/null +++ b/vendor/github.com/KyleBanks/depth/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Kyle Banks + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/KyleBanks/depth/Makefile b/vendor/github.com/KyleBanks/depth/Makefile new file mode 100644 index 00000000..acd35bb4 --- /dev/null +++ b/vendor/github.com/KyleBanks/depth/Makefile @@ -0,0 +1,32 @@ +VERSION = 1.2.1 + +RELEASE_PKG = ./cmd/depth +INSTALL_PKG = $(RELEASE_PKG) + + +# Remote includes require 'mmake' +# github.com/tj/mmake +include github.com/KyleBanks/make/go/install +include github.com/KyleBanks/make/go/sanity +include github.com/KyleBanks/make/go/release +include github.com/KyleBanks/make/go/bench +include github.com/KyleBanks/make/git/precommit + +# Runs a number of depth commands as examples of what's possible. +example: | install + depth github.com/KyleBanks/depth/cmd/depth strings ./ + + depth -internal strings + + depth -json github.com/KyleBanks/depth/cmd/depth + + depth -test github.com/KyleBanks/depth/cmd/depth + + depth -test -internal strings + + depth -test -internal -max 3 strings + + depth . + + depth ./cmd/depth +.PHONY: example diff --git a/vendor/github.com/KyleBanks/depth/README.md b/vendor/github.com/KyleBanks/depth/README.md new file mode 100644 index 00000000..0de954fd --- /dev/null +++ b/vendor/github.com/KyleBanks/depth/README.md @@ -0,0 +1,232 @@ +# depth + +[![GoDoc](https://godoc.org/github.com/KyleBanks/depth?status.svg)](https://godoc.org/github.com/KyleBanks/depth)  +[![Build Status](https://travis-ci.org/KyleBanks/depth.svg?branch=master)](https://travis-ci.org/KyleBanks/depth)  +[![Go Report Card](https://goreportcard.com/badge/github.com/KyleBanks/depth)](https://goreportcard.com/report/github.com/KyleBanks/depth)  +[![Coverage Status](https://coveralls.io/repos/github/KyleBanks/depth/badge.svg?branch=master)](https://coveralls.io/github/KyleBanks/depth?branch=master) + +`depth` is tool to retrieve and visualize Go source code dependency trees. + +## Install + +Download the appropriate binary for your platform from the [Releases](https://github.com/KyleBanks/depth/releases) page, or: + +```sh +go get github.com/KyleBanks/depth/cmd/depth +``` + +## Usage + +`depth` can be used as a standalone command-line application, or as a package within your own project. + +### Command-Line + +Simply execute `depth` with one or more package names to visualize. You can use the fully qualified import path of the package, like so: + +```sh +$ depth github.com/KyleBanks/depth/cmd/depth +github.com/KyleBanks/depth/cmd/depth + ├ encoding/json + ├ flag + ├ fmt + ├ io + ├ log + ├ os + ├ strings + └ github.com/KyleBanks/depth + ├ fmt + ├ go/build + ├ path + ├ sort + └ strings +12 dependencies (11 internal, 1 external, 0 testing). +``` + +Or you can use a relative path, for example: + +```sh +$ depth . +$ depth ./cmd/depth +$ depth ../ +``` + +You can also use `depth` on the Go standard library: + +```sh +$ depth strings +strings + ├ errors + ├ io + ├ unicode + └ unicode/utf8 +5 dependencies (5 internal, 0 external, 0 testing). +``` + +Visualizing multiple packages at a time is supported by simply naming the packages you'd like to visualize: + +```sh +$ depth strings github.com/KyleBanks/depth +strings + ├ errors + ├ io + ├ unicode + └ unicode/utf8 +5 dependencies (5 internal, 0 external, 0 testing). +github.com/KyleBanks/depth + ├ fmt + ├ go/build + ├ path + ├ sort + └ strings +7 dependencies (7 internal, 0 external, 0 testing). +``` + +#### `-internal` + +By default, `depth` only resolves the top level of dependencies for standard library packages, however you can use the `-internal` flag to visualize all internal dependencies: + +```sh +$ depth -internal strings +strings + ├ errors + ├ io + ├ errors + └ sync + ├ internal/race + └ unsafe + ├ runtime + ├ runtime/internal/atomic + └ unsafe + ├ runtime/internal/sys + └ unsafe + ├ sync/atomic + └ unsafe + └ unsafe + ├ unicode + └ unicode/utf8 +12 dependencies (12 internal, 0 external, 0 testing). +``` + +#### `-max` + +The `-max` flag limits the dependency tree to the maximum depth provided. For example, if you supply `-max 1` on the `depth` package, your output would look like so: + +``` +$ depth -max 1 github.com/KyleBanks/depth/cmd/depth +github.com/KyleBanks/depth/cmd/depth + ├ encoding/json + ├ flag + ├ fmt + ├ io + ├ log + ├ os + ├ strings + └ github.com/KyleBanks/depth +7 dependencies (6 internal, 1 external, 0 testing). +``` + +The `-max` flag is particularly useful in conjunction with the `-internal` flag which can lead to very deep dependency trees. + +#### `-test` + +By default, `depth` ignores dependencies that are only required for testing. However, you can view test dependencies using the `-test` flag: + +```sh +$ depth -test strings +strings + ├ bytes + ├ errors + ├ fmt + ├ io + ├ io/ioutil + ├ math/rand + ├ reflect + ├ sync + ├ testing + ├ unicode + ├ unicode/utf8 + └ unsafe +13 dependencies (13 internal, 0 external, 8 testing). +``` + +#### `-explain target-package` + +The `-explain` flag instructs `depth` to print import chains in which the +`target-package` is found: + +```sh +$ depth -explain strings github.com/KyleBanks/depth/cmd/depth +github.com/KyleBanks/depth/cmd/depth -> strings +github.com/KyleBanks/depth/cmd/depth -> github.com/KyleBanks/depth -> strings +``` + +#### `-json` + +The `-json` flag instructs `depth` to output dependencies in JSON format: + +```sh +$ depth -json github.com/KyleBanks/depth/cmd/depth +{ + "name": "github.com/KyleBanks/depth/cmd/depth", + "deps": [ + { + "name": "encoding/json", + "internal": true, + "deps": null + }, + ... + { + "name": "github.com/KyleBanks/depth", + "internal": false, + "deps": [ + { + "name": "go/build", + "internal": true, + "deps": null + }, + ... + ] + } + ] +} +``` + +### Integrating With Your Project + +The `depth` package can easily be used to retrieve the dependency tree for a particular package in your own project. For example, here's how you would retrieve the dependency tree for the `strings` package: + +```go +import "github.com/KyleBanks/depth" + +var t depth.Tree +err := t.Resolve("strings") +if err != nil { + log.Fatal(err) +} + +// Output: "'strings' has 4 dependencies." +log.Printf("'%v' has %v dependencies.", t.Root.Name, len(t.Root.Deps)) +``` + +For additional customization, simply set the appropriate flags on the `Tree` before resolving: + +```go +import "github.com/KyleBanks/depth" + +t := depth.Tree { + ResolveInternal: true, + ResolveTest: true, + MaxDepth: 10, +} + + +err := t.Resolve("strings") +``` + +## Author + +`depth` was developed by [Kyle Banks](https://twitter.com/kylewbanks). + +## License + +`depth` is available under the [MIT](./LICENSE) license. diff --git a/vendor/github.com/KyleBanks/depth/depth.go b/vendor/github.com/KyleBanks/depth/depth.go new file mode 100644 index 00000000..115c28ac --- /dev/null +++ b/vendor/github.com/KyleBanks/depth/depth.go @@ -0,0 +1,129 @@ +// Package depth provides the ability to traverse and retrieve Go source code dependencies in the form of +// internal and external packages. +// +// For example, the dependencies of the stdlib `strings` package can be resolved like so: +// +// import "github.com/KyleBanks/depth" +// +// var t depth.Tree +// err := t.Resolve("strings") +// if err != nil { +// log.Fatal(err) +// } +// +// // Output: "strings has 4 dependencies." +// log.Printf("%v has %v dependencies.", t.Root.Name, len(t.Root.Deps)) +// +// For additional customization, simply set the appropriate flags on the `Tree` before resolving: +// +// import "github.com/KyleBanks/depth" +// +// t := depth.Tree { +// ResolveInternal: true, +// ResolveTest: true, +// MaxDepth: 10, +// } +// err := t.Resolve("strings") +package depth + +import ( + "errors" + "go/build" + "os" +) + +// ErrRootPkgNotResolved is returned when the root Pkg of the Tree cannot be resolved, +// typically because it does not exist. +var ErrRootPkgNotResolved = errors.New("unable to resolve root package") + +// Importer defines a type that can import a package and return its details. +type Importer interface { + Import(name, srcDir string, im build.ImportMode) (*build.Package, error) +} + +// Tree represents the top level of a Pkg and the configuration used to +// initialize and represent its contents. +type Tree struct { + Root *Pkg + + ResolveInternal bool + ResolveTest bool + MaxDepth int + + Importer Importer + + importCache map[string]struct{} +} + +// Resolve recursively finds all dependencies for the root Pkg name provided, +// and the packages it depends on. +func (t *Tree) Resolve(name string) error { + pwd, err := os.Getwd() + if err != nil { + return err + } + + t.Root = &Pkg{ + Name: name, + Tree: t, + SrcDir: pwd, + Test: false, + } + + // Reset the import cache each time to ensure a reused Tree doesn't + // reuse the same cache. + t.importCache = nil + + // Allow custom importers, but use build.Default if none is provided. + if t.Importer == nil { + t.Importer = &build.Default + } + + t.Root.Resolve(t.Importer) + if !t.Root.Resolved { + return ErrRootPkgNotResolved + } + + return nil +} + +// shouldResolveInternal determines if internal packages should be further resolved beyond the +// current parent. +// +// For example, if the parent Pkg is `github.com/foo/bar` and true is returned, all the +// internal dependencies it relies on will be resolved. If for example `strings` is one of those +// dependencies, and it is passed as the parent here, false may be returned and its internal +// dependencies will not be resolved. +func (t *Tree) shouldResolveInternal(parent *Pkg) bool { + if t.ResolveInternal { + return true + } + + return parent == t.Root +} + +// isAtMaxDepth returns true when the depth of the Pkg provided is at or beyond the maximum +// depth allowed by the tree. +// +// If the Tree has a MaxDepth of zero, true is never returned. +func (t *Tree) isAtMaxDepth(p *Pkg) bool { + if t.MaxDepth == 0 { + return false + } + + return p.depth() >= t.MaxDepth +} + +// hasSeenImport returns true if the import name provided has already been seen within the tree. +// This function only returns false for a name once. +func (t *Tree) hasSeenImport(name string) bool { + if t.importCache == nil { + t.importCache = make(map[string]struct{}) + } + + if _, ok := t.importCache[name]; ok { + return true + } + t.importCache[name] = struct{}{} + return false +} diff --git a/vendor/github.com/KyleBanks/depth/pkg.go b/vendor/github.com/KyleBanks/depth/pkg.go new file mode 100644 index 00000000..1d54d717 --- /dev/null +++ b/vendor/github.com/KyleBanks/depth/pkg.go @@ -0,0 +1,184 @@ +package depth + +import ( + "bytes" + "go/build" + "path" + "sort" + "strings" +) + +// Pkg represents a Go source package, and its dependencies. +type Pkg struct { + Name string `json:"name"` + SrcDir string `json:"-"` + + Internal bool `json:"internal"` + Resolved bool `json:"resolved"` + Test bool `json:"-"` + + Tree *Tree `json:"-"` + Parent *Pkg `json:"-"` + Deps []Pkg `json:"deps"` + + Raw *build.Package `json:"-"` +} + +// Resolve recursively finds all dependencies for the Pkg and the packages it depends on. +func (p *Pkg) Resolve(i Importer) { + // Resolved is always true, regardless of if we skip the import, + // it is only false if there is an error while importing. + p.Resolved = true + + name := p.cleanName() + if name == "" { + return + } + + // Stop resolving imports if we've reached max depth or found a duplicate. + var importMode build.ImportMode + if p.Tree.hasSeenImport(name) || p.Tree.isAtMaxDepth(p) { + importMode = build.FindOnly + } + + pkg, err := i.Import(name, p.SrcDir, importMode) + if err != nil { + // TODO: Check the error type? + p.Resolved = false + return + } + p.Raw = pkg + + // Update the name with the fully qualified import path. + p.Name = pkg.ImportPath + + // If this is an internal dependency, we may need to skip it. + if pkg.Goroot { + p.Internal = true + if !p.Tree.shouldResolveInternal(p) { + return + } + } + + //first we set the regular dependencies, then we add the test dependencies + //sharing the same set. This allows us to mark all test-only deps linearly + unique := make(map[string]struct{}) + p.setDeps(i, pkg.Imports, pkg.Dir, unique, false) + if p.Tree.ResolveTest { + p.setDeps(i, append(pkg.TestImports, pkg.XTestImports...), pkg.Dir, unique, true) + } +} + +// setDeps takes a slice of import paths and the source directory they are relative to, +// and creates the Deps of the Pkg. Each dependency is also further resolved prior to being added +// to the Pkg. +func (p *Pkg) setDeps(i Importer, imports []string, srcDir string, unique map[string]struct{}, isTest bool) { + for _, imp := range imports { + // Mostly for testing files where cyclic imports are allowed. + if imp == p.Name { + continue + } + + // Skip duplicates. + if _, ok := unique[imp]; ok { + continue + } + unique[imp] = struct{}{} + + p.addDep(i, imp, srcDir, isTest) + } + + sort.Sort(byInternalAndName(p.Deps)) +} + +// addDep creates a Pkg and it's dependencies from an imported package name. +func (p *Pkg) addDep(i Importer, name string, srcDir string, isTest bool) { + dep := Pkg{ + Name: name, + SrcDir: srcDir, + Tree: p.Tree, + Parent: p, + Test: isTest, + } + dep.Resolve(i) + + p.Deps = append(p.Deps, dep) +} + +// isParent goes recursively up the chain of Pkgs to determine if the name provided is ever a +// parent of the current Pkg. +func (p *Pkg) isParent(name string) bool { + if p.Parent == nil { + return false + } + + if p.Parent.Name == name { + return true + } + + return p.Parent.isParent(name) +} + +// depth returns the depth of the Pkg within the Tree. +func (p *Pkg) depth() int { + if p.Parent == nil { + return 0 + } + + return p.Parent.depth() + 1 +} + +// cleanName returns a cleaned version of the Pkg name used for resolving dependencies. +// +// If an empty string is returned, dependencies should not be resolved. +func (p *Pkg) cleanName() string { + name := p.Name + + // C 'package' cannot be resolved. + if name == "C" { + return "" + } + + // Internal golang_org/* packages must be prefixed with vendor/ + // + // Thanks to @davecheney for this: + // https://github.com/davecheney/graphpkg/blob/master/main.go#L46 + if strings.HasPrefix(name, "golang_org") { + name = path.Join("vendor", name) + } + + return name +} + +// String returns a string representation of the Pkg containing the Pkg name and status. +func (p *Pkg) String() string { + b := bytes.NewBufferString(p.Name) + + if !p.Resolved { + b.Write([]byte(" (unresolved)")) + } + + return b.String() +} + +// byInternalAndName ensures a slice of Pkgs are sorted such that the internal stdlib +// packages are always above external packages (ie. github.com/whatever). +type byInternalAndName []Pkg + +func (b byInternalAndName) Len() int { + return len(b) +} + +func (b byInternalAndName) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +func (b byInternalAndName) Less(i, j int) bool { + if b[i].Internal && !b[j].Internal { + return true + } else if !b[i].Internal && b[j].Internal { + return false + } + + return b[i].Name < b[j].Name +} diff --git a/vendor/github.com/PuerkitoBio/purell/.gitignore b/vendor/github.com/PuerkitoBio/purell/.gitignore new file mode 100644 index 00000000..748e4c80 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/purell/.gitignore @@ -0,0 +1,5 @@ +*.sublime-* +.DS_Store +*.swp +*.swo +tags diff --git a/vendor/github.com/PuerkitoBio/purell/.travis.yml b/vendor/github.com/PuerkitoBio/purell/.travis.yml new file mode 100644 index 00000000..cf31e6af --- /dev/null +++ b/vendor/github.com/PuerkitoBio/purell/.travis.yml @@ -0,0 +1,12 @@ +language: go + +go: + - 1.4.x + - 1.5.x + - 1.6.x + - 1.7.x + - 1.8.x + - 1.9.x + - "1.10.x" + - "1.11.x" + - tip diff --git a/vendor/github.com/PuerkitoBio/purell/LICENSE b/vendor/github.com/PuerkitoBio/purell/LICENSE new file mode 100644 index 00000000..4b9986de --- /dev/null +++ b/vendor/github.com/PuerkitoBio/purell/LICENSE @@ -0,0 +1,12 @@ +Copyright (c) 2012, Martin Angers +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/PuerkitoBio/purell/README.md b/vendor/github.com/PuerkitoBio/purell/README.md new file mode 100644 index 00000000..07de0c49 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/purell/README.md @@ -0,0 +1,188 @@ +# Purell + +Purell is a tiny Go library to normalize URLs. It returns a pure URL. Pure-ell. Sanitizer and all. Yeah, I know... + +Based on the [wikipedia paper][wiki] and the [RFC 3986 document][rfc]. + +[![build status](https://travis-ci.org/PuerkitoBio/purell.svg?branch=master)](http://travis-ci.org/PuerkitoBio/purell) + +## Install + +`go get github.com/PuerkitoBio/purell` + +## Changelog + +* **v1.1.1** : Fix failing test due to Go1.12 changes (thanks to @ianlancetaylor). +* **2016-11-14 (v1.1.0)** : IDN: Conform to RFC 5895: Fold character width (thanks to @beeker1121). +* **2016-07-27 (v1.0.0)** : Normalize IDN to ASCII (thanks to @zenovich). +* **2015-02-08** : Add fix for relative paths issue ([PR #5][pr5]) and add fix for unnecessary encoding of reserved characters ([see issue #7][iss7]). +* **v0.2.0** : Add benchmarks, Attempt IDN support. +* **v0.1.0** : Initial release. + +## Examples + +From `example_test.go` (note that in your code, you would import "github.com/PuerkitoBio/purell", and would prefix references to its methods and constants with "purell."): + +```go +package purell + +import ( + "fmt" + "net/url" +) + +func ExampleNormalizeURLString() { + if normalized, err := NormalizeURLString("hTTp://someWEBsite.com:80/Amazing%3f/url/", + FlagLowercaseScheme|FlagLowercaseHost|FlagUppercaseEscapes); err != nil { + panic(err) + } else { + fmt.Print(normalized) + } + // Output: http://somewebsite.com:80/Amazing%3F/url/ +} + +func ExampleMustNormalizeURLString() { + normalized := MustNormalizeURLString("hTTpS://someWEBsite.com:443/Amazing%fa/url/", + FlagsUnsafeGreedy) + fmt.Print(normalized) + + // Output: http://somewebsite.com/Amazing%FA/url +} + +func ExampleNormalizeURL() { + if u, err := url.Parse("Http://SomeUrl.com:8080/a/b/.././c///g?c=3&a=1&b=9&c=0#target"); err != nil { + panic(err) + } else { + normalized := NormalizeURL(u, FlagsUsuallySafeGreedy|FlagRemoveDuplicateSlashes|FlagRemoveFragment) + fmt.Print(normalized) + } + + // Output: http://someurl.com:8080/a/c/g?c=3&a=1&b=9&c=0 +} +``` + +## API + +As seen in the examples above, purell offers three methods, `NormalizeURLString(string, NormalizationFlags) (string, error)`, `MustNormalizeURLString(string, NormalizationFlags) (string)` and `NormalizeURL(*url.URL, NormalizationFlags) (string)`. They all normalize the provided URL based on the specified flags. Here are the available flags: + +```go +const ( + // Safe normalizations + FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1 + FlagLowercaseHost // http://HOST -> http://host + FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF + FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA + FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$ + FlagRemoveDefaultPort // http://host:80 -> http://host + FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path + + // Usually safe normalizations + FlagRemoveTrailingSlash // http://host/path/ -> http://host/path + FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags) + FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c + + // Unsafe normalizations + FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/ + FlagRemoveFragment // http://host/path#fragment -> http://host/path + FlagForceHTTP // https://host -> http://host + FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b + FlagRemoveWWW // http://www.host/ -> http://host/ + FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags) + FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3 + + // Normalizations not in the wikipedia article, required to cover tests cases + // submitted by jehiah + FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147 + FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147 + FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147 + FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path + FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path + + // Convenience set of safe normalizations + FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator + + // For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags, + // while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix". + + // Convenience set of usually safe normalizations (includes FlagsSafe) + FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments + FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments + + // Convenience set of unsafe normalizations (includes FlagsUsuallySafe) + FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery + FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery + + // Convenience set of all available flags + FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator + FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator +) +``` + +For convenience, the set of flags `FlagsSafe`, `FlagsUsuallySafe[Greedy|NonGreedy]`, `FlagsUnsafe[Greedy|NonGreedy]` and `FlagsAll[Greedy|NonGreedy]` are provided for the similarly grouped normalizations on [wikipedia's URL normalization page][wiki]. You can add (using the bitwise OR `|` operator) or remove (using the bitwise AND NOT `&^` operator) individual flags from the sets if required, to build your own custom set. + +The [full godoc reference is available on gopkgdoc][godoc]. + +Some things to note: + +* `FlagDecodeUnnecessaryEscapes`, `FlagEncodeNecessaryEscapes`, `FlagUppercaseEscapes` and `FlagRemoveEmptyQuerySeparator` are always implicitly set, because internally, the URL string is parsed as an URL object, which automatically decodes unnecessary escapes, uppercases and encodes necessary ones, and removes empty query separators (an unnecessary `?` at the end of the url). So this operation cannot **not** be done. For this reason, `FlagRemoveEmptyQuerySeparator` (as well as the other three) has been included in the `FlagsSafe` convenience set, instead of `FlagsUnsafe`, where Wikipedia puts it. + +* The `FlagDecodeUnnecessaryEscapes` decodes the following escapes (*from -> to*): + - %24 -> $ + - %26 -> & + - %2B-%3B -> +,-./0123456789:; + - %3D -> = + - %40-%5A -> @ABCDEFGHIJKLMNOPQRSTUVWXYZ + - %5F -> _ + - %61-%7A -> abcdefghijklmnopqrstuvwxyz + - %7E -> ~ + + +* When the `NormalizeURL` function is used (passing an URL object), this source URL object is modified (that is, after the call, the URL object will be modified to reflect the normalization). + +* The *replace IP with domain name* normalization (`http://208.77.188.166/ → http://www.example.com/`) is obviously not possible for a library without making some network requests. This is not implemented in purell. + +* The *remove unused query string parameters* and *remove default query parameters* are also not implemented, since this is a very case-specific normalization, and it is quite trivial to do with an URL object. + +### Safe vs Usually Safe vs Unsafe + +Purell allows you to control the level of risk you take while normalizing an URL. You can aggressively normalize, play it totally safe, or anything in between. + +Consider the following URL: + +`HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid` + +Normalizing with the `FlagsSafe` gives: + +`https://www.root.com/toto/tE%1F///a/./b/../c/?z=3&w=2&a=4&w=1#invalid` + +With the `FlagsUsuallySafeGreedy`: + +`https://www.root.com/toto/tE%1F///a/c?z=3&w=2&a=4&w=1#invalid` + +And with `FlagsUnsafeGreedy`: + +`http://root.com/toto/tE%1F/a/c?a=4&w=1&w=2&z=3` + +## TODOs + +* Add a class/default instance to allow specifying custom directory index names? At the moment, removing directory index removes `(^|/)((?:default|index)\.\w{1,4})$`. + +## Thanks / Contributions + +@rogpeppe +@jehiah +@opennota +@pchristopher1275 +@zenovich +@beeker1121 + +## License + +The [BSD 3-Clause license][bsd]. + +[bsd]: http://opensource.org/licenses/BSD-3-Clause +[wiki]: http://en.wikipedia.org/wiki/URL_normalization +[rfc]: http://tools.ietf.org/html/rfc3986#section-6 +[godoc]: http://go.pkgdoc.org/github.com/PuerkitoBio/purell +[pr5]: https://github.com/PuerkitoBio/purell/pull/5 +[iss7]: https://github.com/PuerkitoBio/purell/issues/7 diff --git a/vendor/github.com/PuerkitoBio/purell/purell.go b/vendor/github.com/PuerkitoBio/purell/purell.go new file mode 100644 index 00000000..6d0fc190 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/purell/purell.go @@ -0,0 +1,379 @@ +/* +Package purell offers URL normalization as described on the wikipedia page: +http://en.wikipedia.org/wiki/URL_normalization +*/ +package purell + +import ( + "bytes" + "fmt" + "net/url" + "regexp" + "sort" + "strconv" + "strings" + + "github.com/PuerkitoBio/urlesc" + "golang.org/x/net/idna" + "golang.org/x/text/unicode/norm" + "golang.org/x/text/width" +) + +// A set of normalization flags determines how a URL will +// be normalized. +type NormalizationFlags uint + +const ( + // Safe normalizations + FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1 + FlagLowercaseHost // http://HOST -> http://host + FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF + FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA + FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$ + FlagRemoveDefaultPort // http://host:80 -> http://host + FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path + + // Usually safe normalizations + FlagRemoveTrailingSlash // http://host/path/ -> http://host/path + FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags) + FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c + + // Unsafe normalizations + FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/ + FlagRemoveFragment // http://host/path#fragment -> http://host/path + FlagForceHTTP // https://host -> http://host + FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b + FlagRemoveWWW // http://www.host/ -> http://host/ + FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags) + FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3 + + // Normalizations not in the wikipedia article, required to cover tests cases + // submitted by jehiah + FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147 + FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147 + FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147 + FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path + FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path + + // Convenience set of safe normalizations + FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator + + // For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags, + // while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix". + + // Convenience set of usually safe normalizations (includes FlagsSafe) + FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments + FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments + + // Convenience set of unsafe normalizations (includes FlagsUsuallySafe) + FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery + FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery + + // Convenience set of all available flags + FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator + FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator +) + +const ( + defaultHttpPort = ":80" + defaultHttpsPort = ":443" +) + +// Regular expressions used by the normalizations +var rxPort = regexp.MustCompile(`(:\d+)/?$`) +var rxDirIndex = regexp.MustCompile(`(^|/)((?:default|index)\.\w{1,4})$`) +var rxDupSlashes = regexp.MustCompile(`/{2,}`) +var rxDWORDHost = regexp.MustCompile(`^(\d+)((?:\.+)?(?:\:\d*)?)$`) +var rxOctalHost = regexp.MustCompile(`^(0\d*)\.(0\d*)\.(0\d*)\.(0\d*)((?:\.+)?(?:\:\d*)?)$`) +var rxHexHost = regexp.MustCompile(`^0x([0-9A-Fa-f]+)((?:\.+)?(?:\:\d*)?)$`) +var rxHostDots = regexp.MustCompile(`^(.+?)(:\d+)?$`) +var rxEmptyPort = regexp.MustCompile(`:+$`) + +// Map of flags to implementation function. +// FlagDecodeUnnecessaryEscapes has no action, since it is done automatically +// by parsing the string as an URL. Same for FlagUppercaseEscapes and FlagRemoveEmptyQuerySeparator. + +// Since maps have undefined traversing order, make a slice of ordered keys +var flagsOrder = []NormalizationFlags{ + FlagLowercaseScheme, + FlagLowercaseHost, + FlagRemoveDefaultPort, + FlagRemoveDirectoryIndex, + FlagRemoveDotSegments, + FlagRemoveFragment, + FlagForceHTTP, // Must be after remove default port (because https=443/http=80) + FlagRemoveDuplicateSlashes, + FlagRemoveWWW, + FlagAddWWW, + FlagSortQuery, + FlagDecodeDWORDHost, + FlagDecodeOctalHost, + FlagDecodeHexHost, + FlagRemoveUnnecessaryHostDots, + FlagRemoveEmptyPortSeparator, + FlagRemoveTrailingSlash, // These two (add/remove trailing slash) must be last + FlagAddTrailingSlash, +} + +// ... and then the map, where order is unimportant +var flags = map[NormalizationFlags]func(*url.URL){ + FlagLowercaseScheme: lowercaseScheme, + FlagLowercaseHost: lowercaseHost, + FlagRemoveDefaultPort: removeDefaultPort, + FlagRemoveDirectoryIndex: removeDirectoryIndex, + FlagRemoveDotSegments: removeDotSegments, + FlagRemoveFragment: removeFragment, + FlagForceHTTP: forceHTTP, + FlagRemoveDuplicateSlashes: removeDuplicateSlashes, + FlagRemoveWWW: removeWWW, + FlagAddWWW: addWWW, + FlagSortQuery: sortQuery, + FlagDecodeDWORDHost: decodeDWORDHost, + FlagDecodeOctalHost: decodeOctalHost, + FlagDecodeHexHost: decodeHexHost, + FlagRemoveUnnecessaryHostDots: removeUnncessaryHostDots, + FlagRemoveEmptyPortSeparator: removeEmptyPortSeparator, + FlagRemoveTrailingSlash: removeTrailingSlash, + FlagAddTrailingSlash: addTrailingSlash, +} + +// MustNormalizeURLString returns the normalized string, and panics if an error occurs. +// It takes an URL string as input, as well as the normalization flags. +func MustNormalizeURLString(u string, f NormalizationFlags) string { + result, e := NormalizeURLString(u, f) + if e != nil { + panic(e) + } + return result +} + +// NormalizeURLString returns the normalized string, or an error if it can't be parsed into an URL object. +// It takes an URL string as input, as well as the normalization flags. +func NormalizeURLString(u string, f NormalizationFlags) (string, error) { + parsed, err := url.Parse(u) + if err != nil { + return "", err + } + + if f&FlagLowercaseHost == FlagLowercaseHost { + parsed.Host = strings.ToLower(parsed.Host) + } + + // The idna package doesn't fully conform to RFC 5895 + // (https://tools.ietf.org/html/rfc5895), so we do it here. + // Taken from Go 1.8 cycle source, courtesy of bradfitz. + // TODO: Remove when (if?) idna package conforms to RFC 5895. + parsed.Host = width.Fold.String(parsed.Host) + parsed.Host = norm.NFC.String(parsed.Host) + if parsed.Host, err = idna.ToASCII(parsed.Host); err != nil { + return "", err + } + + return NormalizeURL(parsed, f), nil +} + +// NormalizeURL returns the normalized string. +// It takes a parsed URL object as input, as well as the normalization flags. +func NormalizeURL(u *url.URL, f NormalizationFlags) string { + for _, k := range flagsOrder { + if f&k == k { + flags[k](u) + } + } + return urlesc.Escape(u) +} + +func lowercaseScheme(u *url.URL) { + if len(u.Scheme) > 0 { + u.Scheme = strings.ToLower(u.Scheme) + } +} + +func lowercaseHost(u *url.URL) { + if len(u.Host) > 0 { + u.Host = strings.ToLower(u.Host) + } +} + +func removeDefaultPort(u *url.URL) { + if len(u.Host) > 0 { + scheme := strings.ToLower(u.Scheme) + u.Host = rxPort.ReplaceAllStringFunc(u.Host, func(val string) string { + if (scheme == "http" && val == defaultHttpPort) || (scheme == "https" && val == defaultHttpsPort) { + return "" + } + return val + }) + } +} + +func removeTrailingSlash(u *url.URL) { + if l := len(u.Path); l > 0 { + if strings.HasSuffix(u.Path, "/") { + u.Path = u.Path[:l-1] + } + } else if l = len(u.Host); l > 0 { + if strings.HasSuffix(u.Host, "/") { + u.Host = u.Host[:l-1] + } + } +} + +func addTrailingSlash(u *url.URL) { + if l := len(u.Path); l > 0 { + if !strings.HasSuffix(u.Path, "/") { + u.Path += "/" + } + } else if l = len(u.Host); l > 0 { + if !strings.HasSuffix(u.Host, "/") { + u.Host += "/" + } + } +} + +func removeDotSegments(u *url.URL) { + if len(u.Path) > 0 { + var dotFree []string + var lastIsDot bool + + sections := strings.Split(u.Path, "/") + for _, s := range sections { + if s == ".." { + if len(dotFree) > 0 { + dotFree = dotFree[:len(dotFree)-1] + } + } else if s != "." { + dotFree = append(dotFree, s) + } + lastIsDot = (s == "." || s == "..") + } + // Special case if host does not end with / and new path does not begin with / + u.Path = strings.Join(dotFree, "/") + if u.Host != "" && !strings.HasSuffix(u.Host, "/") && !strings.HasPrefix(u.Path, "/") { + u.Path = "/" + u.Path + } + // Special case if the last segment was a dot, make sure the path ends with a slash + if lastIsDot && !strings.HasSuffix(u.Path, "/") { + u.Path += "/" + } + } +} + +func removeDirectoryIndex(u *url.URL) { + if len(u.Path) > 0 { + u.Path = rxDirIndex.ReplaceAllString(u.Path, "$1") + } +} + +func removeFragment(u *url.URL) { + u.Fragment = "" +} + +func forceHTTP(u *url.URL) { + if strings.ToLower(u.Scheme) == "https" { + u.Scheme = "http" + } +} + +func removeDuplicateSlashes(u *url.URL) { + if len(u.Path) > 0 { + u.Path = rxDupSlashes.ReplaceAllString(u.Path, "/") + } +} + +func removeWWW(u *url.URL) { + if len(u.Host) > 0 && strings.HasPrefix(strings.ToLower(u.Host), "www.") { + u.Host = u.Host[4:] + } +} + +func addWWW(u *url.URL) { + if len(u.Host) > 0 && !strings.HasPrefix(strings.ToLower(u.Host), "www.") { + u.Host = "www." + u.Host + } +} + +func sortQuery(u *url.URL) { + q := u.Query() + + if len(q) > 0 { + arKeys := make([]string, len(q)) + i := 0 + for k := range q { + arKeys[i] = k + i++ + } + sort.Strings(arKeys) + buf := new(bytes.Buffer) + for _, k := range arKeys { + sort.Strings(q[k]) + for _, v := range q[k] { + if buf.Len() > 0 { + buf.WriteRune('&') + } + buf.WriteString(fmt.Sprintf("%s=%s", k, urlesc.QueryEscape(v))) + } + } + + // Rebuild the raw query string + u.RawQuery = buf.String() + } +} + +func decodeDWORDHost(u *url.URL) { + if len(u.Host) > 0 { + if matches := rxDWORDHost.FindStringSubmatch(u.Host); len(matches) > 2 { + var parts [4]int64 + + dword, _ := strconv.ParseInt(matches[1], 10, 0) + for i, shift := range []uint{24, 16, 8, 0} { + parts[i] = dword >> shift & 0xFF + } + u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[2]) + } + } +} + +func decodeOctalHost(u *url.URL) { + if len(u.Host) > 0 { + if matches := rxOctalHost.FindStringSubmatch(u.Host); len(matches) > 5 { + var parts [4]int64 + + for i := 1; i <= 4; i++ { + parts[i-1], _ = strconv.ParseInt(matches[i], 8, 0) + } + u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[5]) + } + } +} + +func decodeHexHost(u *url.URL) { + if len(u.Host) > 0 { + if matches := rxHexHost.FindStringSubmatch(u.Host); len(matches) > 2 { + // Conversion is safe because of regex validation + parsed, _ := strconv.ParseInt(matches[1], 16, 0) + // Set host as DWORD (base 10) encoded host + u.Host = fmt.Sprintf("%d%s", parsed, matches[2]) + // The rest is the same as decoding a DWORD host + decodeDWORDHost(u) + } + } +} + +func removeUnncessaryHostDots(u *url.URL) { + if len(u.Host) > 0 { + if matches := rxHostDots.FindStringSubmatch(u.Host); len(matches) > 1 { + // Trim the leading and trailing dots + u.Host = strings.Trim(matches[1], ".") + if len(matches) > 2 { + u.Host += matches[2] + } + } + } +} + +func removeEmptyPortSeparator(u *url.URL) { + if len(u.Host) > 0 { + u.Host = rxEmptyPort.ReplaceAllString(u.Host, "") + } +} diff --git a/vendor/github.com/PuerkitoBio/urlesc/.travis.yml b/vendor/github.com/PuerkitoBio/urlesc/.travis.yml new file mode 100644 index 00000000..ba6b225f --- /dev/null +++ b/vendor/github.com/PuerkitoBio/urlesc/.travis.yml @@ -0,0 +1,15 @@ +language: go + +go: + - 1.4.x + - 1.5.x + - 1.6.x + - 1.7.x + - 1.8.x + - tip + +install: + - go build . + +script: + - go test -v diff --git a/vendor/github.com/PuerkitoBio/urlesc/LICENSE b/vendor/github.com/PuerkitoBio/urlesc/LICENSE new file mode 100644 index 00000000..74487567 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/urlesc/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/PuerkitoBio/urlesc/README.md b/vendor/github.com/PuerkitoBio/urlesc/README.md new file mode 100644 index 00000000..57aff0a5 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/urlesc/README.md @@ -0,0 +1,16 @@ +urlesc [![Build Status](https://travis-ci.org/PuerkitoBio/urlesc.svg?branch=master)](https://travis-ci.org/PuerkitoBio/urlesc) [![GoDoc](http://godoc.org/github.com/PuerkitoBio/urlesc?status.svg)](http://godoc.org/github.com/PuerkitoBio/urlesc) +====== + +Package urlesc implements query escaping as per RFC 3986. + +It contains some parts of the net/url package, modified so as to allow +some reserved characters incorrectly escaped by net/url (see [issue 5684](https://github.com/golang/go/issues/5684)). + +## Install + + go get github.com/PuerkitoBio/urlesc + +## License + +Go license (BSD-3-Clause) + diff --git a/vendor/github.com/PuerkitoBio/urlesc/urlesc.go b/vendor/github.com/PuerkitoBio/urlesc/urlesc.go new file mode 100644 index 00000000..1b846245 --- /dev/null +++ b/vendor/github.com/PuerkitoBio/urlesc/urlesc.go @@ -0,0 +1,180 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package urlesc implements query escaping as per RFC 3986. +// It contains some parts of the net/url package, modified so as to allow +// some reserved characters incorrectly escaped by net/url. +// See https://github.com/golang/go/issues/5684 +package urlesc + +import ( + "bytes" + "net/url" + "strings" +) + +type encoding int + +const ( + encodePath encoding = 1 + iota + encodeUserPassword + encodeQueryComponent + encodeFragment +) + +// Return true if the specified character should be escaped when +// appearing in a URL string, according to RFC 3986. +func shouldEscape(c byte, mode encoding) bool { + // §2.3 Unreserved characters (alphanum) + if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' { + return false + } + + switch c { + case '-', '.', '_', '~': // §2.3 Unreserved characters (mark) + return false + + // §2.2 Reserved characters (reserved) + case ':', '/', '?', '#', '[', ']', '@', // gen-delims + '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': // sub-delims + // Different sections of the URL allow a few of + // the reserved characters to appear unescaped. + switch mode { + case encodePath: // §3.3 + // The RFC allows sub-delims and : @. + // '/', '[' and ']' can be used to assign meaning to individual path + // segments. This package only manipulates the path as a whole, + // so we allow those as well. That leaves only ? and # to escape. + return c == '?' || c == '#' + + case encodeUserPassword: // §3.2.1 + // The RFC allows : and sub-delims in + // userinfo. The parsing of userinfo treats ':' as special so we must escape + // all the gen-delims. + return c == ':' || c == '/' || c == '?' || c == '#' || c == '[' || c == ']' || c == '@' + + case encodeQueryComponent: // §3.4 + // The RFC allows / and ?. + return c != '/' && c != '?' + + case encodeFragment: // §4.1 + // The RFC text is silent but the grammar allows + // everything, so escape nothing but # + return c == '#' + } + } + + // Everything else must be escaped. + return true +} + +// QueryEscape escapes the string so it can be safely placed +// inside a URL query. +func QueryEscape(s string) string { + return escape(s, encodeQueryComponent) +} + +func escape(s string, mode encoding) string { + spaceCount, hexCount := 0, 0 + for i := 0; i < len(s); i++ { + c := s[i] + if shouldEscape(c, mode) { + if c == ' ' && mode == encodeQueryComponent { + spaceCount++ + } else { + hexCount++ + } + } + } + + if spaceCount == 0 && hexCount == 0 { + return s + } + + t := make([]byte, len(s)+2*hexCount) + j := 0 + for i := 0; i < len(s); i++ { + switch c := s[i]; { + case c == ' ' && mode == encodeQueryComponent: + t[j] = '+' + j++ + case shouldEscape(c, mode): + t[j] = '%' + t[j+1] = "0123456789ABCDEF"[c>>4] + t[j+2] = "0123456789ABCDEF"[c&15] + j += 3 + default: + t[j] = s[i] + j++ + } + } + return string(t) +} + +var uiReplacer = strings.NewReplacer( + "%21", "!", + "%27", "'", + "%28", "(", + "%29", ")", + "%2A", "*", +) + +// unescapeUserinfo unescapes some characters that need not to be escaped as per RFC3986. +func unescapeUserinfo(s string) string { + return uiReplacer.Replace(s) +} + +// Escape reassembles the URL into a valid URL string. +// The general form of the result is one of: +// +// scheme:opaque +// scheme://userinfo@host/path?query#fragment +// +// If u.Opaque is non-empty, String uses the first form; +// otherwise it uses the second form. +// +// In the second form, the following rules apply: +// - if u.Scheme is empty, scheme: is omitted. +// - if u.User is nil, userinfo@ is omitted. +// - if u.Host is empty, host/ is omitted. +// - if u.Scheme and u.Host are empty and u.User is nil, +// the entire scheme://userinfo@host/ is omitted. +// - if u.Host is non-empty and u.Path begins with a /, +// the form host/path does not add its own /. +// - if u.RawQuery is empty, ?query is omitted. +// - if u.Fragment is empty, #fragment is omitted. +func Escape(u *url.URL) string { + var buf bytes.Buffer + if u.Scheme != "" { + buf.WriteString(u.Scheme) + buf.WriteByte(':') + } + if u.Opaque != "" { + buf.WriteString(u.Opaque) + } else { + if u.Scheme != "" || u.Host != "" || u.User != nil { + buf.WriteString("//") + if ui := u.User; ui != nil { + buf.WriteString(unescapeUserinfo(ui.String())) + buf.WriteByte('@') + } + if h := u.Host; h != "" { + buf.WriteString(h) + } + } + if u.Path != "" && u.Path[0] != '/' && u.Host != "" { + buf.WriteByte('/') + } + buf.WriteString(escape(u.Path, encodePath)) + } + if u.RawQuery != "" { + buf.WriteByte('?') + buf.WriteString(u.RawQuery) + } + if u.Fragment != "" { + buf.WriteByte('#') + buf.WriteString(escape(u.Fragment, encodeFragment)) + } + return buf.String() +} diff --git a/vendor/github.com/apapsch/go-jsonmerge/v2/.editorconfig b/vendor/github.com/apapsch/go-jsonmerge/v2/.editorconfig new file mode 100644 index 00000000..fbb2c8f3 --- /dev/null +++ b/vendor/github.com/apapsch/go-jsonmerge/v2/.editorconfig @@ -0,0 +1,6 @@ +[*] +end_of_line = lf +insert_final_newline = true + +[*.{cmd,bat}] +end_of_line = crlf diff --git a/vendor/github.com/apapsch/go-jsonmerge/v2/.gitattributes b/vendor/github.com/apapsch/go-jsonmerge/v2/.gitattributes new file mode 100644 index 00000000..9be55028 --- /dev/null +++ b/vendor/github.com/apapsch/go-jsonmerge/v2/.gitattributes @@ -0,0 +1,175 @@ +## AUTO-DETECT - Handle line endings automatically for files detected +## as text and leave all files detected as binary untouched. +## This will handle all files NOT defined below. +* text=auto + +# Custom for Visual Studio +*.sln text eol=crlf +*.csproj text eol=crlf +*.vbproj text eol=crlf +*.fsproj text eol=crlf +*.dbproj text eol=crlf + +*.vcxproj text eol=crlf +*.vcxitems text eol=crlf +*.props text eol=crlf +*.filters text eol=crlf + +# Documents +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain +*.csv text +*.sql text +*.ini text + +## SOURCE CODE +*.go text eol=lf +*.c text eol=lf +*.h text eol=lf +*.bat text eol=crlf +*.cmd text eol=crlf +*.coffee text eol=lf + +*.htm text diff=html +*.html text diff=html +*.xml text diff=html +*.xhtml text diff=html + +*.js text eol=lf +*.jsx text eol=lf +*.json text eol=lf +*.ts text eol=lf + +*.css text diff=css eol=lf +*.scss text diff=css eol=lf +*.less text diff=css eol=lf +*.sass text eol=lf + +*.sh text eol=lf + +## DOCUMENTATION +*.md text eol=lf +*.txt text +AUTHORS text eol=lf +CHANGELOG text eol=lf +CHANGES text eol=lf +CONTRIBUTING text eol=lf +COPYING text eol=lf +INSTALL text eol=lf +license text eol=lf +LICENSE text eol=lf +NEWS text eol=lf +readme text eol=lf +*README* text eol=lf +TODO text eol=lf + +## TEMPLATES +*.dot text +*.ejs text +*.haml text +*.handlebars text +*.hbs text +*.hbt text +*.jade text +*.latte text +*.mustache text +*.tmpl text + +## LINTERS +.csslintrc text eol=lf +.eslintrc text eol=lf +.jscsrc text eol=lf +.jshintrc text eol=lf +.jshintignore text eol=lf +.stylelintrc text eol=lf + +## CONFIGS +*.bowerrc text eol=lf +*.cnf text +*.conf text +*.config text +.editorconfig text eol=lf +.gitattributes text eol=lf +.gitconfig text eol=lf +.gitignore text eol=lf +*.npmignore text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +Makefile text eol=lf +makefile text eol=lf + +## GRAPHICS +*.ai binary +*.bmp binary +*.eps binary +*.gif binary +*.ico binary +*.jng binary +*.jp2 binary +*.jpg binary +*.jpeg binary +*.jpx binary +*.jxr binary +*.pdf binary +*.png binary +*.psb binary +*.psd binary +*.svg text +*.svgz binary +*.tif binary +*.tiff binary +*.wbmp binary +*.webp binary + +## AUDIO +*.kar binary +*.m4a binary +*.mid binary +*.midi binary +*.mp3 binary +*.ogg binary +*.ra binary + +## VIDEO +*.3gpp binary +*.3gp binary +*.as binary +*.asf binary +*.asx binary +*.fla binary +*.flv binary +*.m4v binary +*.mng binary +*.mov binary +*.mp4 binary +*.mpeg binary +*.mpg binary +*.swc binary +*.swf binary +*.webm binary + +## ARCHIVES +*.7z binary +*.gz binary +*.rar binary +*.tar binary +*.zip binary + +## FONTS +*.ttf binary +*.eot binary +*.otf binary +*.woff binary +*.woff2 binary + +## EXECUTABLES +*.exe binary +*.dll binary diff --git a/vendor/github.com/apapsch/go-jsonmerge/v2/.gitignore b/vendor/github.com/apapsch/go-jsonmerge/v2/.gitignore new file mode 100644 index 00000000..5efe425b --- /dev/null +++ b/vendor/github.com/apapsch/go-jsonmerge/v2/.gitignore @@ -0,0 +1,11 @@ +# GoLand +/.idea/ + +/vendor/ + +/cmd/cmd.exe +/cmd/cmd + +/artifacts/ +/test/ +/cmd/test/ diff --git a/vendor/github.com/apapsch/go-jsonmerge/v2/.gitlab-ci.yml b/vendor/github.com/apapsch/go-jsonmerge/v2/.gitlab-ci.yml new file mode 100644 index 00000000..a0d178f9 --- /dev/null +++ b/vendor/github.com/apapsch/go-jsonmerge/v2/.gitlab-ci.yml @@ -0,0 +1,42 @@ +variables: + GOPROJ: "github.com/RaveNoX/go-jsonmerge" + + +stages: +- test +- build + +test: + tags: + - docker + - linux + image: golang:latest + stage: test + script: + - mkdir -p artifacts + - go test -cover -v -coverprofile="./artifacts/cover.out" ./ + - go tool cover -html="./artifacts/cover.out" -o "./artifacts/cover.htm" + - go test -cover -v -coverprofile="./artifacts/cover_cmd.out" ./cmd/jsonmerge + - go tool cover -html="./artifacts/cover_cmd.out" -o "./artifacts/cover_cmd.htm" + artifacts: + paths: + - artifacts/* + +build: + stage: build + tags: + - docker + - linux + image: golang:latest + script: + - mkdir -p artifacts + - echo "Building for Linux" + - GOOS=linux GOARCH=amd64 go build -o artifacts/jsonmerge ./cmd/jsonmerge + - echo "Building for MacOS (darwin)" + - GOOS=darwin GOARCH=amd64 go build -o artifacts/jsonmerge_darwin ./cmd/jsonmerge + - echo "Building for Windows" + - GOOS=windows GOARCH=amd64 go build -o artifacts/jsonmerge.exe ./cmd/jsonmerge + artifacts: + paths: + - artifacts/* + diff --git a/vendor/github.com/apapsch/go-jsonmerge/v2/.travis.yml b/vendor/github.com/apapsch/go-jsonmerge/v2/.travis.yml new file mode 100644 index 00000000..eae1a8a2 --- /dev/null +++ b/vendor/github.com/apapsch/go-jsonmerge/v2/.travis.yml @@ -0,0 +1,19 @@ +language: go + +go: +- 1.x + +install: +- mkdir -p artifacts + +env: + - GO111MODULE=on + +script: +- go test -cover -v -coverprofile="./artifacts/cover.out" ./ +- go tool cover -html="./artifacts/cover.out" -o "./artifacts/cover.htm" +- go test -cover -v -coverprofile="./artifacts/cover_cmd.out" ./cmd/jsonmerge +- go tool cover -html="./artifacts/cover_cmd.out" -o "./artifacts/cover_cmd.htm" +- GOARCH=amd64 GOOS=linux go build -o artifacts/jsonmerge ./cmd/jsonmerge +- GOARCH=amd64 GOOS=windows go build -o artifacts/jsonmerge.exe ./cmd/jsonmerge +- GOARCH=amd64 GOOS=darwin go build -o artifacts/jsonmerge_darwin ./cmd/jsonmerge diff --git a/vendor/github.com/apapsch/go-jsonmerge/v2/LICENSE b/vendor/github.com/apapsch/go-jsonmerge/v2/LICENSE new file mode 100644 index 00000000..f23057e4 --- /dev/null +++ b/vendor/github.com/apapsch/go-jsonmerge/v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016-2019 Artur Kraev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/apapsch/go-jsonmerge/v2/README.md b/vendor/github.com/apapsch/go-jsonmerge/v2/README.md new file mode 100644 index 00000000..e11bd4c4 --- /dev/null +++ b/vendor/github.com/apapsch/go-jsonmerge/v2/README.md @@ -0,0 +1,81 @@ +# go-jsonmerge +[![Build Status](https://travis-ci.org/RaveNoX/go-jsonmerge.svg?branch=master)](https://travis-ci.org/RaveNoX/go-jsonmerge) +[![GoDoc](https://godoc.org/github.com/RaveNoX/go-jsonmerge?status.svg)](https://godoc.org/github.com/RaveNoX/go-jsonmerge) + +GO library for merging JSON objects + +## Original document +```json +{ + "number": 1, + "string": "value", + "object": { + "number": 1, + "string": "value", + "nested object": { + "number": 2 + }, + "array": [1, 2, 3], + "partial_array": [1, 2, 3] + } +} +``` + +## Patch +```json +{ + "number": 2, + "string": "value1", + "nonexitent": "woot", + "object": { + "number": 3, + "string": "value2", + "nested object": { + "number": 4 + }, + "array": [3, 2, 1], + "partial_array": { + "1": 4 + } + } +} +``` + +## Result +```json +{ + "number": 2, + "string": "value1", + "object": { + "number": 3, + "string": "value2", + "nested object": { + "number": 4 + }, + "array": [3, 2, 1], + "partial_array": [1, 4, 3] + } +} +``` + +## Commandline Tool + +```bash +$ go get -u github.com/RaveNoX/go-jsonmerge/cmd/jsonmerge +$ jsonmerge [options] ... +# For help +$ jsonmerge -h +``` + +## Development +``` +# Install depencencies +./init.sh + +# Build +./build.sh +``` + + +## License +[MIT](./LICENSE.MD) diff --git a/vendor/github.com/apapsch/go-jsonmerge/v2/build.cmd b/vendor/github.com/apapsch/go-jsonmerge/v2/build.cmd new file mode 100644 index 00000000..ebd98767 --- /dev/null +++ b/vendor/github.com/apapsch/go-jsonmerge/v2/build.cmd @@ -0,0 +1,25 @@ +@ECHO OFF +setlocal + +set GOARCH=amd64 + +cd %~dp0 +md artifacts + +echo Windows +set GOOS=windows +call go build -o artifacts\jsonmerge.exe .\cmd || goto :error + +echo Linux +set GOOS=linux +call go build -o artifacts\jsonmerge .\cmd || goto :error + +echo Darwin +set GOOS=darwin +call go build -o artifacts\jsonmerge_darwin .\cmd || goto :error + +echo Build done +exit + +:error +exit /b %errorlevel% diff --git a/vendor/github.com/apapsch/go-jsonmerge/v2/build.sh b/vendor/github.com/apapsch/go-jsonmerge/v2/build.sh new file mode 100644 index 00000000..29be8924 --- /dev/null +++ b/vendor/github.com/apapsch/go-jsonmerge/v2/build.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +set -e + +MY_DIR=$(dirname "$0") + +cd "${MY_DIR}" +mkdir -p "artifacts" + +echo "Linux" +GOARCH=amd64 GOOS=linux go build -o "artifacts/jsonmerge" ./cmd + +echo "Windows" +GOARCH=amd64 GOOS=windows go build -o "artifacts/jsonmerge.exe" ./cmd + +echo "Mac(darwin)" +GOARCH=amd64 GOOS=darwin go build -o "artifacts/jsonmerge_darwin" ./cmd + +echo "Build done" diff --git a/vendor/github.com/apapsch/go-jsonmerge/v2/doc.go b/vendor/github.com/apapsch/go-jsonmerge/v2/doc.go new file mode 100644 index 00000000..5bd6e15e --- /dev/null +++ b/vendor/github.com/apapsch/go-jsonmerge/v2/doc.go @@ -0,0 +1,52 @@ +// Package jsonmerge helps mergeing JSON objects +// +// For example you have this documents: +// +// original.json +// { +// "number": 1, +// "string": "value", +// "object": { +// "number": 1, +// "string": "value", +// "nested object": { +// "number": 2 +// }, +// "array": [1, 2, 3], +// "partial_array": [1, 2, 3] +// } +// } +// +// patch.json +// { +// "number": 2, +// "string": "value1", +// "nonexitent": "woot", +// "object": { +// "number": 3, +// "string": "value2", +// "nested object": { +// "number": 4 +// }, +// "array": [3, 2, 1], +// "partial_array": { +// "1": 4 +// } +// } +// } +// +// After merge you will have this result: +// { +// "number": 2, +// "string": "value1", +// "object": { +// "number": 3, +// "string": "value2", +// "nested object": { +// "number": 4 +// }, +// "array": [3, 2, 1], +// "partial_array": [1, 4, 3] +// } +// } +package jsonmerge diff --git a/vendor/github.com/apapsch/go-jsonmerge/v2/merge.go b/vendor/github.com/apapsch/go-jsonmerge/v2/merge.go new file mode 100644 index 00000000..8ba052f1 --- /dev/null +++ b/vendor/github.com/apapsch/go-jsonmerge/v2/merge.go @@ -0,0 +1,167 @@ +package jsonmerge + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strconv" + "strings" +) + +// Merger describes result of merge operation and provides +// configuration. +type Merger struct { + // Errors is slice of non-critical errors of merge operations + Errors []error + // Replaced is describe replacements + // Key is path in document like + // "prop1.prop2.prop3" for object properties or + // "arr1.1.prop" for arrays + // Value is value of replacemet + Replaced map[string]interface{} + // CopyNonexistent enables setting fields into the result + // which only exist in the patch. + CopyNonexistent bool +} + +func (m *Merger) mergeValue(path []string, patch map[string]interface{}, key string, value interface{}) interface{} { + patchValue, patchHasValue := patch[key] + + if !patchHasValue { + return value + } + + _, patchValueIsObject := patchValue.(map[string]interface{}) + + path = append(path, key) + pathStr := strings.Join(path, ".") + + if _, ok := value.(map[string]interface{}); ok { + if !patchValueIsObject { + err := fmt.Errorf("patch value must be object for key \"%v\"", pathStr) + m.Errors = append(m.Errors, err) + return value + } + + return m.mergeObjects(value, patchValue, path) + } + + if _, ok := value.([]interface{}); ok && patchValueIsObject { + return m.mergeObjects(value, patchValue, path) + } + + if !reflect.DeepEqual(value, patchValue) { + m.Replaced[pathStr] = patchValue + } + + return patchValue +} + +func (m *Merger) mergeObjects(data, patch interface{}, path []string) interface{} { + if patchObject, ok := patch.(map[string]interface{}); ok { + if dataArray, ok := data.([]interface{}); ok { + ret := make([]interface{}, len(dataArray)) + + for i, val := range dataArray { + ret[i] = m.mergeValue(path, patchObject, strconv.Itoa(i), val) + } + + return ret + } else if dataObject, ok := data.(map[string]interface{}); ok { + ret := make(map[string]interface{}) + + for k, v := range dataObject { + ret[k] = m.mergeValue(path, patchObject, k, v) + } + if m.CopyNonexistent { + for k, v := range patchObject { + if _, ok := dataObject[k]; !ok { + ret[k] = v + } + } + } + + return ret + } + } + + return data +} + +// Merge merges patch document to data document +// +// Returning merged document. Result of merge operation can be +// obtained from the Merger. Result information is discarded before +// merging. +func (m *Merger) Merge(data, patch interface{}) interface{} { + m.Replaced = make(map[string]interface{}) + m.Errors = make([]error, 0) + return m.mergeObjects(data, patch, nil) +} + +// MergeBytesIndent merges patch document buffer to data document buffer +// +// Use prefix and indent for set indentation like in json.MarshalIndent +// +// Returning merged document buffer and error if any. +func (m *Merger) MergeBytesIndent(dataBuff, patchBuff []byte, prefix, indent string) (mergedBuff []byte, err error) { + var data, patch, merged interface{} + + err = unmarshalJSON(dataBuff, &data) + if err != nil { + err = fmt.Errorf("error in data JSON: %v", err) + return + } + + err = unmarshalJSON(patchBuff, &patch) + if err != nil { + err = fmt.Errorf("error in patch JSON: %v", err) + return + } + + merged = m.Merge(data, patch) + + mergedBuff, err = json.MarshalIndent(merged, prefix, indent) + if err != nil { + err = fmt.Errorf("error writing merged JSON: %v", err) + } + + return +} + +// MergeBytes merges patch document buffer to data document buffer +// +// Returning merged document buffer, merge info and +// error if any +func (m *Merger) MergeBytes(dataBuff, patchBuff []byte) (mergedBuff []byte, err error) { + var data, patch, merged interface{} + + err = unmarshalJSON(dataBuff, &data) + if err != nil { + err = fmt.Errorf("error in data JSON: %v", err) + return + } + + err = unmarshalJSON(patchBuff, &patch) + if err != nil { + err = fmt.Errorf("error in patch JSON: %v", err) + return + } + + merged = m.Merge(data, patch) + + mergedBuff, err = json.Marshal(merged) + if err != nil { + err = fmt.Errorf("error writing merged JSON: %v", err) + } + + return +} + +func unmarshalJSON(buff []byte, data interface{}) error { + decoder := json.NewDecoder(bytes.NewReader(buff)) + decoder.UseNumber() + + return decoder.Decode(data) +} diff --git a/vendor/github.com/getkin/kin-openapi/LICENSE b/vendor/github.com/getkin/kin-openapi/LICENSE new file mode 100644 index 00000000..992b9831 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017-2018 the project authors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/callback.go b/vendor/github.com/getkin/kin-openapi/openapi3/callback.go new file mode 100644 index 00000000..34a6bea3 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/callback.go @@ -0,0 +1,54 @@ +package openapi3 + +import ( + "context" + "sort" +) + +// Callback is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callback-object +type Callback struct { + Extensions map[string]any `json:"-" yaml:"-"` + + m map[string]*PathItem +} + +// NewCallback builds a Callback object with path items in insertion order. +func NewCallback(opts ...NewCallbackOption) *Callback { + Callback := NewCallbackWithCapacity(len(opts)) + for _, opt := range opts { + opt(Callback) + } + return Callback +} + +// NewCallbackOption describes options to NewCallback func +type NewCallbackOption func(*Callback) + +// WithCallback adds Callback as an option to NewCallback +func WithCallback(cb string, pathItem *PathItem) NewCallbackOption { + return func(callback *Callback) { + if p := pathItem; p != nil && cb != "" { + callback.Set(cb, p) + } + } +} + +// Validate returns an error if Callback does not comply with the OpenAPI spec. +func (callback *Callback) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + keys := make([]string, 0, callback.Len()) + for key := range callback.Map() { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + v := callback.Value(key) + if err := v.Validate(ctx); err != nil { + return err + } + } + + return validateExtensions(ctx, callback.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/components.go b/vendor/github.com/getkin/kin-openapi/openapi3/components.go new file mode 100644 index 00000000..98c4b96c --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/components.go @@ -0,0 +1,370 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "fmt" + "sort" + + "github.com/go-openapi/jsonpointer" +) + +type ( + Callbacks map[string]*CallbackRef + Examples map[string]*ExampleRef + Headers map[string]*HeaderRef + Links map[string]*LinkRef + ParametersMap map[string]*ParameterRef + RequestBodies map[string]*RequestBodyRef + ResponseBodies map[string]*ResponseRef + Schemas map[string]*SchemaRef + SecuritySchemes map[string]*SecuritySchemeRef +) + +// Components is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#components-object +type Components struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Schemas Schemas `json:"schemas,omitempty" yaml:"schemas,omitempty"` + Parameters ParametersMap `json:"parameters,omitempty" yaml:"parameters,omitempty"` + Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"` + RequestBodies RequestBodies `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"` + Responses ResponseBodies `json:"responses,omitempty" yaml:"responses,omitempty"` + SecuritySchemes SecuritySchemes `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"` + Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"` + Links Links `json:"links,omitempty" yaml:"links,omitempty"` + Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` +} + +func NewComponents() Components { + return Components{} +} + +// MarshalJSON returns the JSON encoding of Components. +func (components Components) MarshalJSON() ([]byte, error) { + x, err := components.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Components. +func (components Components) MarshalYAML() (any, error) { + m := make(map[string]any, 9+len(components.Extensions)) + for k, v := range components.Extensions { + m[k] = v + } + if x := components.Schemas; len(x) != 0 { + m["schemas"] = x + } + if x := components.Parameters; len(x) != 0 { + m["parameters"] = x + } + if x := components.Headers; len(x) != 0 { + m["headers"] = x + } + if x := components.RequestBodies; len(x) != 0 { + m["requestBodies"] = x + } + if x := components.Responses; len(x) != 0 { + m["responses"] = x + } + if x := components.SecuritySchemes; len(x) != 0 { + m["securitySchemes"] = x + } + if x := components.Examples; len(x) != 0 { + m["examples"] = x + } + if x := components.Links; len(x) != 0 { + m["links"] = x + } + if x := components.Callbacks; len(x) != 0 { + m["callbacks"] = x + } + return m, nil +} + +// UnmarshalJSON sets Components to a copy of data. +func (components *Components) UnmarshalJSON(data []byte) error { + type ComponentsBis Components + var x ComponentsBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "schemas") + delete(x.Extensions, "parameters") + delete(x.Extensions, "headers") + delete(x.Extensions, "requestBodies") + delete(x.Extensions, "responses") + delete(x.Extensions, "securitySchemes") + delete(x.Extensions, "examples") + delete(x.Extensions, "links") + delete(x.Extensions, "callbacks") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *components = Components(x) + return nil +} + +// Validate returns an error if Components does not comply with the OpenAPI spec. +func (components *Components) Validate(ctx context.Context, opts ...ValidationOption) (err error) { + ctx = WithValidationOptions(ctx, opts...) + + schemas := make([]string, 0, len(components.Schemas)) + for name := range components.Schemas { + schemas = append(schemas, name) + } + sort.Strings(schemas) + for _, k := range schemas { + v := components.Schemas[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("schema %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("schema %q: %w", k, err) + } + } + + parameters := make([]string, 0, len(components.Parameters)) + for name := range components.Parameters { + parameters = append(parameters, name) + } + sort.Strings(parameters) + for _, k := range parameters { + v := components.Parameters[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("parameter %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("parameter %q: %w", k, err) + } + } + + requestBodies := make([]string, 0, len(components.RequestBodies)) + for name := range components.RequestBodies { + requestBodies = append(requestBodies, name) + } + sort.Strings(requestBodies) + for _, k := range requestBodies { + v := components.RequestBodies[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("request body %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("request body %q: %w", k, err) + } + } + + responses := make([]string, 0, len(components.Responses)) + for name := range components.Responses { + responses = append(responses, name) + } + sort.Strings(responses) + for _, k := range responses { + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("response %q: %w", k, err) + } + v := components.Responses[k] + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("response %q: %w", k, err) + } + } + + headers := make([]string, 0, len(components.Headers)) + for name := range components.Headers { + headers = append(headers, name) + } + sort.Strings(headers) + for _, k := range headers { + v := components.Headers[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("header %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("header %q: %w", k, err) + } + } + + securitySchemes := make([]string, 0, len(components.SecuritySchemes)) + for name := range components.SecuritySchemes { + securitySchemes = append(securitySchemes, name) + } + sort.Strings(securitySchemes) + for _, k := range securitySchemes { + v := components.SecuritySchemes[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("security scheme %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("security scheme %q: %w", k, err) + } + } + + examples := make([]string, 0, len(components.Examples)) + for name := range components.Examples { + examples = append(examples, name) + } + sort.Strings(examples) + for _, k := range examples { + v := components.Examples[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("example %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("example %q: %w", k, err) + } + } + + links := make([]string, 0, len(components.Links)) + for name := range components.Links { + links = append(links, name) + } + sort.Strings(links) + for _, k := range links { + v := components.Links[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("link %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("link %q: %w", k, err) + } + } + + callbacks := make([]string, 0, len(components.Callbacks)) + for name := range components.Callbacks { + callbacks = append(callbacks, name) + } + sort.Strings(callbacks) + for _, k := range callbacks { + v := components.Callbacks[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("callback %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("callback %q: %w", k, err) + } + } + + return validateExtensions(ctx, components.Extensions) +} + +var _ jsonpointer.JSONPointable = (*Schemas)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m Schemas) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no schema %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*ParametersMap)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m ParametersMap) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no parameter %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*Headers)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m Headers) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no header %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m RequestBodies) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no request body %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*ResponseRef)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m ResponseBodies) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no response body %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*SecuritySchemes)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m SecuritySchemes) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no security scheme body %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*Examples)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m Examples) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no example body %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*Links)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m Links) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no link body %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*Callbacks)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m Callbacks) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no callback body %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/contact.go b/vendor/github.com/getkin/kin-openapi/openapi3/contact.go new file mode 100644 index 00000000..6c76a6fb --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/contact.go @@ -0,0 +1,68 @@ +package openapi3 + +import ( + "context" + "encoding/json" +) + +// Contact is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#contact-object +type Contact struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Name string `json:"name,omitempty" yaml:"name,omitempty"` + URL string `json:"url,omitempty" yaml:"url,omitempty"` + Email string `json:"email,omitempty" yaml:"email,omitempty"` +} + +// MarshalJSON returns the JSON encoding of Contact. +func (contact Contact) MarshalJSON() ([]byte, error) { + x, err := contact.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Contact. +func (contact Contact) MarshalYAML() (any, error) { + m := make(map[string]any, 3+len(contact.Extensions)) + for k, v := range contact.Extensions { + m[k] = v + } + if x := contact.Name; x != "" { + m["name"] = x + } + if x := contact.URL; x != "" { + m["url"] = x + } + if x := contact.Email; x != "" { + m["email"] = x + } + return m, nil +} + +// UnmarshalJSON sets Contact to a copy of data. +func (contact *Contact) UnmarshalJSON(data []byte) error { + type ContactBis Contact + var x ContactBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "name") + delete(x.Extensions, "url") + delete(x.Extensions, "email") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *contact = Contact(x) + return nil +} + +// Validate returns an error if Contact does not comply with the OpenAPI spec. +func (contact *Contact) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + return validateExtensions(ctx, contact.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/content.go b/vendor/github.com/getkin/kin-openapi/openapi3/content.go new file mode 100644 index 00000000..81b070ee --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/content.go @@ -0,0 +1,124 @@ +package openapi3 + +import ( + "context" + "sort" + "strings" +) + +// Content is specified by OpenAPI/Swagger 3.0 standard. +type Content map[string]*MediaType + +func NewContent() Content { + return make(map[string]*MediaType) +} + +func NewContentWithSchema(schema *Schema, consumes []string) Content { + if len(consumes) == 0 { + return Content{ + "*/*": NewMediaType().WithSchema(schema), + } + } + content := make(map[string]*MediaType, len(consumes)) + for _, mediaType := range consumes { + content[mediaType] = NewMediaType().WithSchema(schema) + } + return content +} + +func NewContentWithSchemaRef(schema *SchemaRef, consumes []string) Content { + if len(consumes) == 0 { + return Content{ + "*/*": NewMediaType().WithSchemaRef(schema), + } + } + content := make(map[string]*MediaType, len(consumes)) + for _, mediaType := range consumes { + content[mediaType] = NewMediaType().WithSchemaRef(schema) + } + return content +} + +func NewContentWithJSONSchema(schema *Schema) Content { + return Content{ + "application/json": NewMediaType().WithSchema(schema), + } +} +func NewContentWithJSONSchemaRef(schema *SchemaRef) Content { + return Content{ + "application/json": NewMediaType().WithSchemaRef(schema), + } +} + +func NewContentWithFormDataSchema(schema *Schema) Content { + return Content{ + "multipart/form-data": NewMediaType().WithSchema(schema), + } +} + +func NewContentWithFormDataSchemaRef(schema *SchemaRef) Content { + return Content{ + "multipart/form-data": NewMediaType().WithSchemaRef(schema), + } +} + +func (content Content) Get(mime string) *MediaType { + // If the mime is empty then short-circuit to the wildcard. + // We do this here so that we catch only the specific case of + // and empty mime rather than a present, but invalid, mime type. + if mime == "" { + return content["*/*"] + } + // Start by making the most specific match possible + // by using the mime type in full. + if v := content[mime]; v != nil { + return v + } + // If an exact match is not found then we strip all + // metadata from the mime type and only use the x/y + // portion. + i := strings.IndexByte(mime, ';') + if i < 0 { + // If there is no metadata then preserve the full mime type + // string for later wildcard searches. + i = len(mime) + } + mime = mime[:i] + if v := content[mime]; v != nil { + return v + } + // If the x/y pattern has no specific match then we + // try the x/* pattern. + i = strings.IndexByte(mime, '/') + if i < 0 { + // In the case that the given mime type is not valid because it is + // missing the subtype we return nil so that this does not accidentally + // resolve with the wildcard. + return nil + } + mime = mime[:i] + "/*" + if v := content[mime]; v != nil { + return v + } + // Finally, the most generic match of */* is returned + // as a catch-all. + return content["*/*"] +} + +// Validate returns an error if Content does not comply with the OpenAPI spec. +func (content Content) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + keys := make([]string, 0, len(content)) + for key := range content { + keys = append(keys, key) + } + sort.Strings(keys) + for _, k := range keys { + v := content[k] + if err := v.Validate(ctx); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go b/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go new file mode 100644 index 00000000..e8193bd9 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go @@ -0,0 +1,61 @@ +package openapi3 + +import ( + "context" + "encoding/json" +) + +// Discriminator is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminator-object +type Discriminator struct { + Extensions map[string]any `json:"-" yaml:"-"` + + PropertyName string `json:"propertyName" yaml:"propertyName"` // required + Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"` +} + +// MarshalJSON returns the JSON encoding of Discriminator. +func (discriminator Discriminator) MarshalJSON() ([]byte, error) { + x, err := discriminator.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Discriminator. +func (discriminator Discriminator) MarshalYAML() (any, error) { + m := make(map[string]any, 2+len(discriminator.Extensions)) + for k, v := range discriminator.Extensions { + m[k] = v + } + m["propertyName"] = discriminator.PropertyName + if x := discriminator.Mapping; len(x) != 0 { + m["mapping"] = x + } + return m, nil +} + +// UnmarshalJSON sets Discriminator to a copy of data. +func (discriminator *Discriminator) UnmarshalJSON(data []byte) error { + type DiscriminatorBis Discriminator + var x DiscriminatorBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "propertyName") + delete(x.Extensions, "mapping") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *discriminator = Discriminator(x) + return nil +} + +// Validate returns an error if Discriminator does not comply with the OpenAPI spec. +func (discriminator *Discriminator) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + return validateExtensions(ctx, discriminator.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/doc.go b/vendor/github.com/getkin/kin-openapi/openapi3/doc.go new file mode 100644 index 00000000..41c9965c --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/doc.go @@ -0,0 +1,4 @@ +// Package openapi3 parses and writes OpenAPI 3 specification documents. +// +// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md +package openapi3 diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go b/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go new file mode 100644 index 00000000..1bcdaea5 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go @@ -0,0 +1,148 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "fmt" + "sort" +) + +// Encoding is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#encoding-object +type Encoding struct { + Extensions map[string]any `json:"-" yaml:"-"` + + ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"` + Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"` + Style string `json:"style,omitempty" yaml:"style,omitempty"` + Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"` + AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` +} + +func NewEncoding() *Encoding { + return &Encoding{} +} + +func (encoding *Encoding) WithHeader(name string, header *Header) *Encoding { + return encoding.WithHeaderRef(name, &HeaderRef{ + Value: header, + }) +} + +func (encoding *Encoding) WithHeaderRef(name string, ref *HeaderRef) *Encoding { + headers := encoding.Headers + if headers == nil { + headers = make(map[string]*HeaderRef) + encoding.Headers = headers + } + headers[name] = ref + return encoding +} + +// MarshalJSON returns the JSON encoding of Encoding. +func (encoding Encoding) MarshalJSON() ([]byte, error) { + x, err := encoding.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Encoding. +func (encoding Encoding) MarshalYAML() (any, error) { + m := make(map[string]any, 5+len(encoding.Extensions)) + for k, v := range encoding.Extensions { + m[k] = v + } + if x := encoding.ContentType; x != "" { + m["contentType"] = x + } + if x := encoding.Headers; len(x) != 0 { + m["headers"] = x + } + if x := encoding.Style; x != "" { + m["style"] = x + } + if x := encoding.Explode; x != nil { + m["explode"] = x + } + if x := encoding.AllowReserved; x { + m["allowReserved"] = x + } + return m, nil +} + +// UnmarshalJSON sets Encoding to a copy of data. +func (encoding *Encoding) UnmarshalJSON(data []byte) error { + type EncodingBis Encoding + var x EncodingBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "contentType") + delete(x.Extensions, "headers") + delete(x.Extensions, "style") + delete(x.Extensions, "explode") + delete(x.Extensions, "allowReserved") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *encoding = Encoding(x) + return nil +} + +// SerializationMethod returns a serialization method of request body. +// When serialization method is not defined the method returns the default serialization method. +func (encoding *Encoding) SerializationMethod() *SerializationMethod { + sm := &SerializationMethod{Style: SerializationForm, Explode: true} + if encoding != nil { + if encoding.Style != "" { + sm.Style = encoding.Style + } + if encoding.Explode != nil { + sm.Explode = *encoding.Explode + } + } + return sm +} + +// Validate returns an error if Encoding does not comply with the OpenAPI spec. +func (encoding *Encoding) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if encoding == nil { + return nil + } + + headers := make([]string, 0, len(encoding.Headers)) + for k := range encoding.Headers { + headers = append(headers, k) + } + sort.Strings(headers) + for _, k := range headers { + v := encoding.Headers[k] + if err := ValidateIdentifier(k); err != nil { + return nil + } + if err := v.Validate(ctx); err != nil { + return nil + } + } + + // Validate a media types's serialization method. + sm := encoding.SerializationMethod() + switch { + case sm.Style == SerializationForm && sm.Explode, + sm.Style == SerializationForm && !sm.Explode, + sm.Style == SerializationSpaceDelimited && sm.Explode, + sm.Style == SerializationSpaceDelimited && !sm.Explode, + sm.Style == SerializationPipeDelimited && sm.Explode, + sm.Style == SerializationPipeDelimited && !sm.Explode, + sm.Style == SerializationDeepObject && sm.Explode: + default: + return fmt.Errorf("serialization method with style=%q and explode=%v is not supported by media type", sm.Style, sm.Explode) + } + + return validateExtensions(ctx, encoding.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/errors.go b/vendor/github.com/getkin/kin-openapi/openapi3/errors.go new file mode 100644 index 00000000..010dc889 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/errors.go @@ -0,0 +1,59 @@ +package openapi3 + +import ( + "bytes" + "errors" +) + +// MultiError is a collection of errors, intended for when +// multiple issues need to be reported upstream +type MultiError []error + +func (me MultiError) Error() string { + return spliceErr(" | ", me) +} + +func spliceErr(sep string, errs []error) string { + buff := &bytes.Buffer{} + for i, e := range errs { + buff.WriteString(e.Error()) + if i != len(errs)-1 { + buff.WriteString(sep) + } + } + return buff.String() +} + +// Is allows you to determine if a generic error is in fact a MultiError using `errors.Is()` +// It will also return true if any of the contained errors match target +func (me MultiError) Is(target error) bool { + if _, ok := target.(MultiError); ok { + return true + } + for _, e := range me { + if errors.Is(e, target) { + return true + } + } + return false +} + +// As allows you to use `errors.As()` to set target to the first error within the multi error that matches the target type +func (me MultiError) As(target any) bool { + for _, e := range me { + if errors.As(e, target) { + return true + } + } + return false +} + +type multiErrorForOneOf MultiError + +func (meo multiErrorForOneOf) Error() string { + return spliceErr(" Or ", meo) +} + +func (meo multiErrorForOneOf) Unwrap() error { + return MultiError(meo) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/example.go b/vendor/github.com/getkin/kin-openapi/openapi3/example.go new file mode 100644 index 00000000..f9a7a6b0 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/example.go @@ -0,0 +1,85 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" +) + +// Example is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#example-object +type Example struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Value any `json:"value,omitempty" yaml:"value,omitempty"` + ExternalValue string `json:"externalValue,omitempty" yaml:"externalValue,omitempty"` +} + +func NewExample(value any) *Example { + return &Example{Value: value} +} + +// MarshalJSON returns the JSON encoding of Example. +func (example Example) MarshalJSON() ([]byte, error) { + x, err := example.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Example. +func (example Example) MarshalYAML() (any, error) { + m := make(map[string]any, 4+len(example.Extensions)) + for k, v := range example.Extensions { + m[k] = v + } + if x := example.Summary; x != "" { + m["summary"] = x + } + if x := example.Description; x != "" { + m["description"] = x + } + if x := example.Value; x != nil { + m["value"] = x + } + if x := example.ExternalValue; x != "" { + m["externalValue"] = x + } + return m, nil +} + +// UnmarshalJSON sets Example to a copy of data. +func (example *Example) UnmarshalJSON(data []byte) error { + type ExampleBis Example + var x ExampleBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "summary") + delete(x.Extensions, "description") + delete(x.Extensions, "value") + delete(x.Extensions, "externalValue") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *example = Example(x) + return nil +} + +// Validate returns an error if Example does not comply with the OpenAPI spec. +func (example *Example) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if example.Value != nil && example.ExternalValue != "" { + return errors.New("value and externalValue are mutually exclusive") + } + if example.Value == nil && example.ExternalValue == "" { + return errors.New("no value or externalValue field") + } + + return validateExtensions(ctx, example.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/example_validation.go b/vendor/github.com/getkin/kin-openapi/openapi3/example_validation.go new file mode 100644 index 00000000..0d105c92 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/example_validation.go @@ -0,0 +1,16 @@ +package openapi3 + +import "context" + +func validateExampleValue(ctx context.Context, input any, schema *Schema) error { + opts := make([]SchemaValidationOption, 0, 2) + + if vo := getValidationOptions(ctx); vo.examplesValidationAsReq { + opts = append(opts, VisitAsRequest()) + } else if vo.examplesValidationAsRes { + opts = append(opts, VisitAsResponse()) + } + opts = append(opts, MultiErrors()) + + return schema.VisitJSON(input, opts...) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/extension.go b/vendor/github.com/getkin/kin-openapi/openapi3/extension.go new file mode 100644 index 00000000..ca86078f --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/extension.go @@ -0,0 +1,32 @@ +package openapi3 + +import ( + "context" + "fmt" + "sort" + "strings" +) + +func validateExtensions(ctx context.Context, extensions map[string]any) error { // FIXME: newtype + Validate(...) + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + + var unknowns []string + for k := range extensions { + if strings.HasPrefix(k, "x-") { + continue + } + if allowed != nil { + if _, ok := allowed[k]; ok { + continue + } + } + unknowns = append(unknowns, k) + } + + if len(unknowns) != 0 { + sort.Strings(unknowns) + return fmt.Errorf("extra sibling fields: %+v", unknowns) + } + + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go b/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go new file mode 100644 index 00000000..bd99511a --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go @@ -0,0 +1,73 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/url" +) + +// ExternalDocs is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#external-documentation-object +type ExternalDocs struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Description string `json:"description,omitempty" yaml:"description,omitempty"` + URL string `json:"url,omitempty" yaml:"url,omitempty"` +} + +// MarshalJSON returns the JSON encoding of ExternalDocs. +func (e ExternalDocs) MarshalJSON() ([]byte, error) { + x, err := e.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of ExternalDocs. +func (e ExternalDocs) MarshalYAML() (any, error) { + m := make(map[string]any, 2+len(e.Extensions)) + for k, v := range e.Extensions { + m[k] = v + } + if x := e.Description; x != "" { + m["description"] = x + } + if x := e.URL; x != "" { + m["url"] = x + } + return m, nil +} + +// UnmarshalJSON sets ExternalDocs to a copy of data. +func (e *ExternalDocs) UnmarshalJSON(data []byte) error { + type ExternalDocsBis ExternalDocs + var x ExternalDocsBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "description") + delete(x.Extensions, "url") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *e = ExternalDocs(x) + return nil +} + +// Validate returns an error if ExternalDocs does not comply with the OpenAPI spec. +func (e *ExternalDocs) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if e.URL == "" { + return errors.New("url is required") + } + if _, err := url.Parse(e.URL); err != nil { + return fmt.Errorf("url is incorrect: %w", err) + } + + return validateExtensions(ctx, e.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/header.go b/vendor/github.com/getkin/kin-openapi/openapi3/header.go new file mode 100644 index 00000000..dc542874 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/header.go @@ -0,0 +1,96 @@ +package openapi3 + +import ( + "context" + "errors" + "fmt" + + "github.com/go-openapi/jsonpointer" +) + +// Header is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#header-object +type Header struct { + Parameter +} + +var _ jsonpointer.JSONPointable = (*Header)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (header Header) JSONLookup(token string) (any, error) { + return header.Parameter.JSONLookup(token) +} + +// MarshalJSON returns the JSON encoding of Header. +func (header Header) MarshalJSON() ([]byte, error) { + return header.Parameter.MarshalJSON() +} + +// UnmarshalJSON sets Header to a copy of data. +func (header *Header) UnmarshalJSON(data []byte) error { + return header.Parameter.UnmarshalJSON(data) +} + +// MarshalYAML returns the JSON encoding of Header. +func (header Header) MarshalYAML() (any, error) { + return header.Parameter, nil +} + +// SerializationMethod returns a header's serialization method. +func (header *Header) SerializationMethod() (*SerializationMethod, error) { + style := header.Style + if style == "" { + style = SerializationSimple + } + explode := false + if header.Explode != nil { + explode = *header.Explode + } + return &SerializationMethod{Style: style, Explode: explode}, nil +} + +// Validate returns an error if Header does not comply with the OpenAPI spec. +func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if header.Name != "" { + return errors.New("header 'name' MUST NOT be specified, it is given in the corresponding headers map") + } + if header.In != "" { + return errors.New("header 'in' MUST NOT be specified, it is implicitly in header") + } + + // Validate a parameter's serialization method. + sm, err := header.SerializationMethod() + if err != nil { + return err + } + if smSupported := false || + sm.Style == SerializationSimple && !sm.Explode || + sm.Style == SerializationSimple && sm.Explode; !smSupported { + e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a header parameter", sm.Style, sm.Explode) + return fmt.Errorf("header schema is invalid: %w", e) + } + + if (header.Schema == nil) == (len(header.Content) == 0) { + e := fmt.Errorf("parameter must contain exactly one of content and schema: %v", header) + return fmt.Errorf("header schema is invalid: %w", e) + } + if schema := header.Schema; schema != nil { + if err := schema.Validate(ctx); err != nil { + return fmt.Errorf("header schema is invalid: %w", err) + } + } + + if content := header.Content; content != nil { + e := errors.New("parameter content must only contain one entry") + if len(content) > 1 { + return fmt.Errorf("header content is invalid: %w", e) + } + + if err := content.Validate(ctx); err != nil { + return fmt.Errorf("header content is invalid: %w", err) + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/helpers.go b/vendor/github.com/getkin/kin-openapi/openapi3/helpers.go new file mode 100644 index 00000000..d50b3d84 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/helpers.go @@ -0,0 +1,261 @@ +package openapi3 + +import ( + "fmt" + "net/url" + "path" + "reflect" + "regexp" + "sort" + "strings" + + "github.com/go-openapi/jsonpointer" +) + +const identifierChars = `a-zA-Z0-9._-` + +// IdentifierRegExp verifies whether Component object key matches contains just 'identifierChars', according to OpenAPI v3.x. +// InvalidIdentifierCharRegExp matches all characters not contained in 'identifierChars'. +// However, to be able supporting legacy OpenAPI v2.x, there is a need to customize above pattern in order not to fail +// converted v2-v3 validation +var ( + IdentifierRegExp = regexp.MustCompile(`^[` + identifierChars + `]+$`) + InvalidIdentifierCharRegExp = regexp.MustCompile(`[^` + identifierChars + `]`) +) + +// ValidateIdentifier returns an error if the given component name does not match [IdentifierRegExp]. +func ValidateIdentifier(value string) error { + if IdentifierRegExp.MatchString(value) { + return nil + } + return fmt.Errorf("identifier %q is not supported by OpenAPIv3 standard (charset: [%q])", value, identifierChars) +} + +// Float64Ptr is a helper for defining OpenAPI schemas. +func Float64Ptr(value float64) *float64 { + return &value +} + +// BoolPtr is a helper for defining OpenAPI schemas. +func BoolPtr(value bool) *bool { + return &value +} + +// Int64Ptr is a helper for defining OpenAPI schemas. +func Int64Ptr(value int64) *int64 { + return &value +} + +// Uint64Ptr is a helper for defining OpenAPI schemas. +func Uint64Ptr(value uint64) *uint64 { + return &value +} + +// componentNames returns the map keys in a sorted slice. +func componentNames[E any](s map[string]E) []string { + out := make([]string, 0, len(s)) + for i := range s { + out = append(out, i) + } + sort.Strings(out) + return out +} + +// copyURI makes a copy of the pointer. +func copyURI(u *url.URL) *url.URL { + if u == nil { + return nil + } + + c := *u // shallow-copy + return &c +} + +type ComponentRef interface { + RefString() string + RefPath() *url.URL + CollectionName() string +} + +// refersToSameDocument returns if the $ref refers to the same document. +// +// Documents in different directories will have distinct $ref values that resolve to +// the same document. +// For example, consider the 3 files: +// +// /records.yaml +// /root.yaml $ref: records.yaml +// /schema/other.yaml $ref: ../records.yaml +// +// The records.yaml reference in the 2 latter refers to the same document. +func refersToSameDocument(o1 ComponentRef, o2 ComponentRef) bool { + if o1 == nil || o2 == nil { + return false + } + + r1 := o1.RefPath() + r2 := o2.RefPath() + + if r1 == nil || r2 == nil { + return false + } + + // refURL is relative to the working directory & base spec file. + return referenceURIMatch(r1, r2) +} + +// referencesRootDocument returns if the $ref points to the root document of the OpenAPI spec. +// +// If the document has no location, perhaps loaded from data in memory, it always returns false. +func referencesRootDocument(doc *T, ref ComponentRef) bool { + if doc.url == nil || ref == nil || ref.RefPath() == nil { + return false + } + + refURL := *ref.RefPath() + refURL.Fragment = "" + + // Check referenced element was in the root document. + return referenceURIMatch(doc.url, &refURL) +} + +func referenceURIMatch(u1 *url.URL, u2 *url.URL) bool { + s1, s2 := *u1, *u2 + if s1.Scheme == "" { + s1.Scheme = "file" + } + if s2.Scheme == "" { + s2.Scheme = "file" + } + + return s1.String() == s2.String() +} + +// ReferencesComponentInRootDocument returns if the given component reference references +// the same document or element as another component reference in the root document's +// '#/components/'. If it does, it returns the name of it in the form +// '#/components//NameXXX' +// +// Of course given a component from the root document will always match itself. +// +// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object +// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#relative-references-in-urls +// +// Example. Take the spec with directory structure: +// +// openapi.yaml +// schemas/ +// ├─ record.yaml +// ├─ records.yaml +// +// In openapi.yaml we have: +// +// components: +// schemas: +// Record: +// $ref: schemas/record.yaml +// +// Case 1: records.yml references a component in the root document +// +// $ref: ../openapi.yaml#/components/schemas/Record +// +// This would return... +// +// #/components/schemas/Record +// +// Case 2: records.yml indirectly refers to the same schema +// as a schema the root document's '#/components/schemas'. +// +// $ref: ./record.yaml +// +// This would also return... +// +// #/components/schemas/Record +func ReferencesComponentInRootDocument(doc *T, ref ComponentRef) (string, bool) { + if ref == nil || ref.RefString() == "" { + return "", false + } + + // Case 1: + // Something like: ../another-folder/document.json#/myElement + if isRemoteReference(ref.RefString()) && isRootComponentReference(ref.RefString(), ref.CollectionName()) { + // Determine if it is *this* root doc. + if referencesRootDocument(doc, ref) { + _, name, _ := strings.Cut(ref.RefString(), path.Join("#/components/", ref.CollectionName())) + + return path.Join("#/components/", ref.CollectionName(), name), true + } + } + + // If there are no schemas defined in the root document return early. + if doc.Components == nil { + return "", false + } + + collection, _, err := jsonpointer.GetForToken(doc.Components, ref.CollectionName()) + if err != nil { + panic(err) // unreachable + } + + var components map[string]ComponentRef + + componentRefType := reflect.TypeOf(new(ComponentRef)).Elem() + if t := reflect.TypeOf(collection); t.Kind() == reflect.Map && + t.Key().Kind() == reflect.String && + t.Elem().AssignableTo(componentRefType) { + v := reflect.ValueOf(collection) + + components = make(map[string]ComponentRef, v.Len()) + for _, key := range v.MapKeys() { + strct := v.MapIndex(key) + // Type assertion safe, already checked via reflection above. + components[key.Interface().(string)] = strct.Interface().(ComponentRef) + } + } else { + return "", false + } + + // Case 2: + // Something like: ../openapi.yaml#/components/schemas/myElement + for name, s := range components { + // Must be a reference to a YAML file. + if !isWholeDocumentReference(s.RefString()) { + continue + } + + // Is the schema a ref to the same resource. + if !refersToSameDocument(s, ref) { + continue + } + + // Transform the remote ref to the equivalent schema in the root document. + return path.Join("#/components/", ref.CollectionName(), name), true + } + + return "", false +} + +// isElementReference takes a $ref value and checks if it references a specific element. +func isElementReference(ref string) bool { + return ref != "" && !isWholeDocumentReference(ref) +} + +// isSchemaReference takes a $ref value and checks if it references a schema element. +func isRootComponentReference(ref string, compType string) bool { + return isElementReference(ref) && strings.Contains(ref, path.Join("#/components/", compType)) +} + +// isWholeDocumentReference takes a $ref value and checks if it is whole document reference. +func isWholeDocumentReference(ref string) bool { + return ref != "" && !strings.ContainsAny(ref, "#") +} + +// isRemoteReference takes a $ref value and checks if it is remote reference. +func isRemoteReference(ref string) bool { + return ref != "" && !strings.HasPrefix(ref, "#") && !isURLReference(ref) +} + +// isURLReference takes a $ref value and checks if it is URL reference. +func isURLReference(ref string) bool { + return strings.HasPrefix(ref, "http://") || strings.HasPrefix(ref, "https://") || strings.HasPrefix(ref, "//") +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/info.go b/vendor/github.com/getkin/kin-openapi/openapi3/info.go new file mode 100644 index 00000000..e2468285 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/info.go @@ -0,0 +1,103 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" +) + +// Info is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#info-object +type Info struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Title string `json:"title" yaml:"title"` // Required + Description string `json:"description,omitempty" yaml:"description,omitempty"` + TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"` + Contact *Contact `json:"contact,omitempty" yaml:"contact,omitempty"` + License *License `json:"license,omitempty" yaml:"license,omitempty"` + Version string `json:"version" yaml:"version"` // Required +} + +// MarshalJSON returns the JSON encoding of Info. +func (info Info) MarshalJSON() ([]byte, error) { + x, err := info.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Info. +func (info *Info) MarshalYAML() (any, error) { + if info == nil { + return nil, nil + } + m := make(map[string]any, 6+len(info.Extensions)) + for k, v := range info.Extensions { + m[k] = v + } + m["title"] = info.Title + if x := info.Description; x != "" { + m["description"] = x + } + if x := info.TermsOfService; x != "" { + m["termsOfService"] = x + } + if x := info.Contact; x != nil { + m["contact"] = x + } + if x := info.License; x != nil { + m["license"] = x + } + m["version"] = info.Version + return m, nil +} + +// UnmarshalJSON sets Info to a copy of data. +func (info *Info) UnmarshalJSON(data []byte) error { + type InfoBis Info + var x InfoBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "title") + delete(x.Extensions, "description") + delete(x.Extensions, "termsOfService") + delete(x.Extensions, "contact") + delete(x.Extensions, "license") + delete(x.Extensions, "version") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *info = Info(x) + return nil +} + +// Validate returns an error if Info does not comply with the OpenAPI spec. +func (info *Info) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if contact := info.Contact; contact != nil { + if err := contact.Validate(ctx); err != nil { + return err + } + } + + if license := info.License; license != nil { + if err := license.Validate(ctx); err != nil { + return err + } + } + + if info.Version == "" { + return errors.New("value of version must be a non-empty string") + } + + if info.Title == "" { + return errors.New("value of title must be a non-empty string") + } + + return validateExtensions(ctx, info.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go b/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go new file mode 100644 index 00000000..01f5dad8 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go @@ -0,0 +1,546 @@ +package openapi3 + +import ( + "context" + "path" + "strings" +) + +// RefNameResolver maps a component to an name that is used as it's internalized name. +// +// The function should avoid name collisions (i.e. be a injective mapping). +// It must only contain characters valid for fixed field names: [IdentifierRegExp]. +type RefNameResolver func(*T, ComponentRef) string + +// DefaultRefResolver is a default implementation of refNameResolver for the +// InternalizeRefs function. +// +// The external reference is internalized to (hopefully) a unique name. If +// the external reference matches (by path) to another reference in the root +// document then the name of that component is used. +// +// The transformation involves: +// - Cutting the "#/components/" part. +// - Cutting the file extensions (.yaml/.json) from documents. +// - Trimming the common directory with the root spec. +// - Replace invalid characters with with underscores. +// +// This is an injective mapping over a "reasonable" amount of the possible openapi +// spec domain space but is not perfect. There might be edge cases. +func DefaultRefNameResolver(doc *T, ref ComponentRef) string { + if ref.RefString() == "" || ref.RefPath() == nil { + panic("unable to resolve reference to name") + } + + name := ref.RefPath() + + // If refering to a component in the root spec, no need to internalize just use + // the existing component. + // XXX(percivalalb): since this function call is iterating over components behind the + // scenes during an internalization call it actually starts interating over + // new & replaced internalized components. This might caused some edge cases, + // haven't found one yet but this might need to actually be used on a frozen copy + // of doc. + if nameInRoot, found := ReferencesComponentInRootDocument(doc, ref); found { + nameInRoot = strings.TrimPrefix(nameInRoot, "#") + + rootCompURI := copyURI(doc.url) + rootCompURI.Fragment = nameInRoot + name = rootCompURI + } + + filePath, componentPath := name.Path, name.Fragment + + // Cut out the "#/components/" to make the names shorter. + // XXX(percivalalb): This might cause collisions but is worth the brevity. + if b, a, ok := strings.Cut(componentPath, path.Join("components", ref.CollectionName(), "")); ok { + componentPath = path.Join(b, a) + } + + if filePath != "" { + // If the path is the same as the root doc, just remove. + if doc.url != nil && filePath == doc.url.Path { + filePath = "" + } + + // Remove the path extentions to make this JSON/YAML agnostic. + for ext := path.Ext(filePath); len(ext) > 0; ext = path.Ext(filePath) { + filePath = strings.TrimSuffix(filePath, ext) + } + + // Trim the common prefix with the root doc path. + if doc.url != nil { + commonDir := path.Dir(doc.url.Path) + for { + if commonDir == "." { // no common prefix + break + } + + if p, found := cutDirectories(filePath, commonDir); found { + filePath = p + break + } + + commonDir = path.Dir(commonDir) + } + } + } + + var internalizedName string + + // Trim .'s & slashes from start e.g. otherwise ./doc.yaml would end up as __doc + if filePath != "" { + internalizedName = strings.TrimLeft(filePath, "./") + } + + if componentPath != "" { + if internalizedName != "" { + internalizedName += "_" + } + + internalizedName += strings.TrimLeft(componentPath, "./") + } + + // Replace invalid characters in component fixed field names. + internalizedName = InvalidIdentifierCharRegExp.ReplaceAllString(internalizedName, "_") + + return internalizedName +} + +// cutDirectories removes the given directories from the start of the path if +// the path is a child. +func cutDirectories(p, dirs string) (string, bool) { + if dirs == "" || p == "" { + return p, false + } + + p = strings.TrimRight(p, "/") + dirs = strings.TrimRight(dirs, "/") + + var sb strings.Builder + sb.Grow(len(ParameterInHeader)) + for _, segments := range strings.Split(p, "/") { + sb.WriteString(segments) + + if sb.String() == p { + return strings.TrimPrefix(p, dirs), true + } + + sb.WriteRune('/') + } + + return p, false +} + +func isExternalRef(ref string, parentIsExternal bool) bool { + return ref != "" && (!strings.HasPrefix(ref, "#/components/") || parentIsExternal) +} + +func (doc *T) addSchemaToSpec(s *SchemaRef, refNameResolver RefNameResolver, parentIsExternal bool) bool { + if s == nil || !isExternalRef(s.Ref, parentIsExternal) { + return false + } + + name := refNameResolver(doc, s) + if doc.Components != nil { + if _, ok := doc.Components.Schemas[name]; ok { + s.Ref = "#/components/schemas/" + name + return true + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + if doc.Components.Schemas == nil { + doc.Components.Schemas = make(Schemas) + } + doc.Components.Schemas[name] = s.Value.NewRef() + s.Ref = "#/components/schemas/" + name + return true +} + +func (doc *T) addParameterToSpec(p *ParameterRef, refNameResolver RefNameResolver, parentIsExternal bool) bool { + if p == nil || !isExternalRef(p.Ref, parentIsExternal) { + return false + } + name := refNameResolver(doc, p) + if doc.Components != nil { + if _, ok := doc.Components.Parameters[name]; ok { + p.Ref = "#/components/parameters/" + name + return true + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + if doc.Components.Parameters == nil { + doc.Components.Parameters = make(ParametersMap) + } + doc.Components.Parameters[name] = &ParameterRef{Value: p.Value} + p.Ref = "#/components/parameters/" + name + return true +} + +func (doc *T) addHeaderToSpec(h *HeaderRef, refNameResolver RefNameResolver, parentIsExternal bool) bool { + if h == nil || !isExternalRef(h.Ref, parentIsExternal) { + return false + } + name := refNameResolver(doc, h) + if doc.Components != nil { + if _, ok := doc.Components.Headers[name]; ok { + h.Ref = "#/components/headers/" + name + return true + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + if doc.Components.Headers == nil { + doc.Components.Headers = make(Headers) + } + doc.Components.Headers[name] = &HeaderRef{Value: h.Value} + h.Ref = "#/components/headers/" + name + return true +} + +func (doc *T) addRequestBodyToSpec(r *RequestBodyRef, refNameResolver RefNameResolver, parentIsExternal bool) bool { + if r == nil || !isExternalRef(r.Ref, parentIsExternal) { + return false + } + name := refNameResolver(doc, r) + if doc.Components != nil { + if _, ok := doc.Components.RequestBodies[name]; ok { + r.Ref = "#/components/requestBodies/" + name + return true + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + if doc.Components.RequestBodies == nil { + doc.Components.RequestBodies = make(RequestBodies) + } + doc.Components.RequestBodies[name] = &RequestBodyRef{Value: r.Value} + r.Ref = "#/components/requestBodies/" + name + return true +} + +func (doc *T) addResponseToSpec(r *ResponseRef, refNameResolver RefNameResolver, parentIsExternal bool) bool { + if r == nil || !isExternalRef(r.Ref, parentIsExternal) { + return false + } + name := refNameResolver(doc, r) + if doc.Components != nil { + if _, ok := doc.Components.Responses[name]; ok { + r.Ref = "#/components/responses/" + name + return true + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + if doc.Components.Responses == nil { + doc.Components.Responses = make(ResponseBodies) + } + doc.Components.Responses[name] = &ResponseRef{Value: r.Value} + r.Ref = "#/components/responses/" + name + return true +} + +func (doc *T) addSecuritySchemeToSpec(ss *SecuritySchemeRef, refNameResolver RefNameResolver, parentIsExternal bool) { + if ss == nil || !isExternalRef(ss.Ref, parentIsExternal) { + return + } + name := refNameResolver(doc, ss) + if doc.Components != nil { + if _, ok := doc.Components.SecuritySchemes[name]; ok { + ss.Ref = "#/components/securitySchemes/" + name + return + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + if doc.Components.SecuritySchemes == nil { + doc.Components.SecuritySchemes = make(SecuritySchemes) + } + doc.Components.SecuritySchemes[name] = &SecuritySchemeRef{Value: ss.Value} + ss.Ref = "#/components/securitySchemes/" + name + +} + +func (doc *T) addExampleToSpec(e *ExampleRef, refNameResolver RefNameResolver, parentIsExternal bool) { + if e == nil || !isExternalRef(e.Ref, parentIsExternal) { + return + } + name := refNameResolver(doc, e) + if doc.Components != nil { + if _, ok := doc.Components.Examples[name]; ok { + e.Ref = "#/components/examples/" + name + return + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + if doc.Components.Examples == nil { + doc.Components.Examples = make(Examples) + } + doc.Components.Examples[name] = &ExampleRef{Value: e.Value} + e.Ref = "#/components/examples/" + name + +} + +func (doc *T) addLinkToSpec(l *LinkRef, refNameResolver RefNameResolver, parentIsExternal bool) { + if l == nil || !isExternalRef(l.Ref, parentIsExternal) { + return + } + name := refNameResolver(doc, l) + if doc.Components != nil { + if _, ok := doc.Components.Links[name]; ok { + l.Ref = "#/components/links/" + name + return + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + if doc.Components.Links == nil { + doc.Components.Links = make(Links) + } + doc.Components.Links[name] = &LinkRef{Value: l.Value} + l.Ref = "#/components/links/" + name + +} + +func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver, parentIsExternal bool) bool { + if c == nil || !isExternalRef(c.Ref, parentIsExternal) { + return false + } + name := refNameResolver(doc, c) + + if doc.Components == nil { + doc.Components = &Components{} + } + if doc.Components.Callbacks == nil { + doc.Components.Callbacks = make(Callbacks) + } + c.Ref = "#/components/callbacks/" + name + doc.Components.Callbacks[name] = &CallbackRef{Value: c.Value} + return true +} + +func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver, parentIsExternal bool) { + if s == nil || doc.isVisitedSchema(s) { + return + } + + for _, list := range []SchemaRefs{s.AllOf, s.AnyOf, s.OneOf} { + for _, s2 := range list { + isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal) + if s2 != nil { + doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal) + } + } + } + + for _, name := range componentNames(s.Properties) { + s2 := s.Properties[name] + isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal) + if s2 != nil { + doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal) + } + } + for _, ref := range []*SchemaRef{s.Not, s.AdditionalProperties.Schema, s.Items} { + isExternal := doc.addSchemaToSpec(ref, refNameResolver, parentIsExternal) + if ref != nil { + doc.derefSchema(ref.Value, refNameResolver, isExternal || parentIsExternal) + } + } +} + +func (doc *T) derefHeaders(hs Headers, refNameResolver RefNameResolver, parentIsExternal bool) { + for _, name := range componentNames(hs) { + h := hs[name] + isExternal := doc.addHeaderToSpec(h, refNameResolver, parentIsExternal) + if doc.isVisitedHeader(h.Value) { + continue + } + doc.derefParameter(h.Value.Parameter, refNameResolver, parentIsExternal || isExternal) + } +} + +func (doc *T) derefExamples(es Examples, refNameResolver RefNameResolver, parentIsExternal bool) { + for _, name := range componentNames(es) { + e := es[name] + doc.addExampleToSpec(e, refNameResolver, parentIsExternal) + } +} + +func (doc *T) derefContent(c Content, refNameResolver RefNameResolver, parentIsExternal bool) { + for _, name := range componentNames(c) { + mediatype := c[name] + isExternal := doc.addSchemaToSpec(mediatype.Schema, refNameResolver, parentIsExternal) + if mediatype.Schema != nil { + doc.derefSchema(mediatype.Schema.Value, refNameResolver, isExternal || parentIsExternal) + } + doc.derefExamples(mediatype.Examples, refNameResolver, parentIsExternal) + for _, name := range componentNames(mediatype.Encoding) { + e := mediatype.Encoding[name] + doc.derefHeaders(e.Headers, refNameResolver, parentIsExternal) + } + } +} + +func (doc *T) derefLinks(ls Links, refNameResolver RefNameResolver, parentIsExternal bool) { + for _, name := range componentNames(ls) { + l := ls[name] + doc.addLinkToSpec(l, refNameResolver, parentIsExternal) + } +} + +func (doc *T) derefResponse(r *ResponseRef, refNameResolver RefNameResolver, parentIsExternal bool) { + isExternal := doc.addResponseToSpec(r, refNameResolver, parentIsExternal) + if v := r.Value; v != nil { + doc.derefHeaders(v.Headers, refNameResolver, isExternal || parentIsExternal) + doc.derefContent(v.Content, refNameResolver, isExternal || parentIsExternal) + doc.derefLinks(v.Links, refNameResolver, isExternal || parentIsExternal) + } +} + +func (doc *T) derefResponses(rs *Responses, refNameResolver RefNameResolver, parentIsExternal bool) { + doc.derefResponseBodies(rs.Map(), refNameResolver, parentIsExternal) +} + +func (doc *T) derefResponseBodies(es ResponseBodies, refNameResolver RefNameResolver, parentIsExternal bool) { + for _, name := range componentNames(es) { + e := es[name] + doc.derefResponse(e, refNameResolver, parentIsExternal) + } +} + +func (doc *T) derefParameter(p Parameter, refNameResolver RefNameResolver, parentIsExternal bool) { + isExternal := doc.addSchemaToSpec(p.Schema, refNameResolver, parentIsExternal) + doc.derefContent(p.Content, refNameResolver, parentIsExternal) + if p.Schema != nil { + doc.derefSchema(p.Schema.Value, refNameResolver, isExternal || parentIsExternal) + } +} + +func (doc *T) derefRequestBody(r RequestBody, refNameResolver RefNameResolver, parentIsExternal bool) { + doc.derefContent(r.Content, refNameResolver, parentIsExternal) +} + +func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameResolver, parentIsExternal bool) { + for _, name := range componentNames(paths) { + ops := paths[name] + pathIsExternal := isExternalRef(ops.Ref, parentIsExternal) + // inline full operations + ops.Ref = "" + + for _, param := range ops.Parameters { + isExternal := doc.addParameterToSpec(param, refNameResolver, pathIsExternal) + if param.Value != nil { + doc.derefParameter(*param.Value, refNameResolver, pathIsExternal || isExternal) + } + } + + opsWithMethod := ops.Operations() + for _, name := range componentNames(opsWithMethod) { + op := opsWithMethod[name] + isExternal := doc.addRequestBodyToSpec(op.RequestBody, refNameResolver, pathIsExternal) + if op.RequestBody != nil && op.RequestBody.Value != nil { + doc.derefRequestBody(*op.RequestBody.Value, refNameResolver, pathIsExternal || isExternal) + } + for _, name := range componentNames(op.Callbacks) { + cb := op.Callbacks[name] + isExternal := doc.addCallbackToSpec(cb, refNameResolver, pathIsExternal) + if cb.Value != nil { + cbValue := (*cb.Value).Map() + doc.derefPaths(cbValue, refNameResolver, pathIsExternal || isExternal) + } + } + doc.derefResponses(op.Responses, refNameResolver, pathIsExternal) + for _, param := range op.Parameters { + isExternal := doc.addParameterToSpec(param, refNameResolver, pathIsExternal) + if param.Value != nil { + doc.derefParameter(*param.Value, refNameResolver, pathIsExternal || isExternal) + } + } + } + } +} + +// InternalizeRefs removes all references to external files from the spec and moves them +// to the components section. +// +// refNameResolver takes in references to returns a name to store the reference under locally. +// It MUST return a unique name for each reference type. +// A default implementation is provided that will suffice for most use cases. See the function +// documentation for more details. +// +// Example: +// +// doc.InternalizeRefs(context.Background(), nil) +func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(*T, ComponentRef) string) { + doc.resetVisited() + + if refNameResolver == nil { + refNameResolver = DefaultRefNameResolver + } + + if components := doc.Components; components != nil { + for _, name := range componentNames(components.Schemas) { + schema := components.Schemas[name] + isExternal := doc.addSchemaToSpec(schema, refNameResolver, false) + if schema != nil { + schema.Ref = "" // always dereference the top level + doc.derefSchema(schema.Value, refNameResolver, isExternal) + } + } + for _, name := range componentNames(components.Parameters) { + p := components.Parameters[name] + isExternal := doc.addParameterToSpec(p, refNameResolver, false) + if p != nil && p.Value != nil { + p.Ref = "" // always dereference the top level + doc.derefParameter(*p.Value, refNameResolver, isExternal) + } + } + doc.derefHeaders(components.Headers, refNameResolver, false) + for _, name := range componentNames(components.RequestBodies) { + req := components.RequestBodies[name] + isExternal := doc.addRequestBodyToSpec(req, refNameResolver, false) + if req != nil && req.Value != nil { + req.Ref = "" // always dereference the top level + doc.derefRequestBody(*req.Value, refNameResolver, isExternal) + } + } + doc.derefResponseBodies(components.Responses, refNameResolver, false) + for _, name := range componentNames(components.SecuritySchemes) { + ss := components.SecuritySchemes[name] + doc.addSecuritySchemeToSpec(ss, refNameResolver, false) + } + doc.derefExamples(components.Examples, refNameResolver, false) + doc.derefLinks(components.Links, refNameResolver, false) + + for _, name := range componentNames(components.Callbacks) { + cb := components.Callbacks[name] + isExternal := doc.addCallbackToSpec(cb, refNameResolver, false) + if cb != nil && cb.Value != nil { + cb.Ref = "" // always dereference the top level + cbValue := (*cb.Value).Map() + doc.derefPaths(cbValue, refNameResolver, isExternal) + } + } + } + + doc.derefPaths(doc.Paths.Map(), refNameResolver, false) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/license.go b/vendor/github.com/getkin/kin-openapi/openapi3/license.go new file mode 100644 index 00000000..c4f6c8dc --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/license.go @@ -0,0 +1,66 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" +) + +// License is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#license-object +type License struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Name string `json:"name" yaml:"name"` // Required + URL string `json:"url,omitempty" yaml:"url,omitempty"` +} + +// MarshalJSON returns the JSON encoding of License. +func (license License) MarshalJSON() ([]byte, error) { + x, err := license.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of License. +func (license License) MarshalYAML() (any, error) { + m := make(map[string]any, 2+len(license.Extensions)) + for k, v := range license.Extensions { + m[k] = v + } + m["name"] = license.Name + if x := license.URL; x != "" { + m["url"] = x + } + return m, nil +} + +// UnmarshalJSON sets License to a copy of data. +func (license *License) UnmarshalJSON(data []byte) error { + type LicenseBis License + var x LicenseBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "name") + delete(x.Extensions, "url") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *license = License(x) + return nil +} + +// Validate returns an error if License does not comply with the OpenAPI spec. +func (license *License) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if license.Name == "" { + return errors.New("value of license name must be a non-empty string") + } + + return validateExtensions(ctx, license.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/link.go b/vendor/github.com/getkin/kin-openapi/openapi3/link.go new file mode 100644 index 00000000..132f6780 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/link.go @@ -0,0 +1,94 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" +) + +// Link is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#link-object +type Link struct { + Extensions map[string]any `json:"-" yaml:"-"` + + OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"` + OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Parameters map[string]any `json:"parameters,omitempty" yaml:"parameters,omitempty"` + Server *Server `json:"server,omitempty" yaml:"server,omitempty"` + RequestBody any `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` +} + +// MarshalJSON returns the JSON encoding of Link. +func (link Link) MarshalJSON() ([]byte, error) { + x, err := link.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Link. +func (link Link) MarshalYAML() (any, error) { + m := make(map[string]any, 6+len(link.Extensions)) + for k, v := range link.Extensions { + m[k] = v + } + + if x := link.OperationRef; x != "" { + m["operationRef"] = x + } + if x := link.OperationID; x != "" { + m["operationId"] = x + } + if x := link.Description; x != "" { + m["description"] = x + } + if x := link.Parameters; len(x) != 0 { + m["parameters"] = x + } + if x := link.Server; x != nil { + m["server"] = x + } + if x := link.RequestBody; x != nil { + m["requestBody"] = x + } + + return m, nil +} + +// UnmarshalJSON sets Link to a copy of data. +func (link *Link) UnmarshalJSON(data []byte) error { + type LinkBis Link + var x LinkBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "operationRef") + delete(x.Extensions, "operationId") + delete(x.Extensions, "description") + delete(x.Extensions, "parameters") + delete(x.Extensions, "server") + delete(x.Extensions, "requestBody") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *link = Link(x) + return nil +} + +// Validate returns an error if Link does not comply with the OpenAPI spec. +func (link *Link) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if link.OperationID == "" && link.OperationRef == "" { + return errors.New("missing operationId or operationRef on link") + } + if link.OperationID != "" && link.OperationRef != "" { + return fmt.Errorf("operationId %q and operationRef %q are mutually exclusive", link.OperationID, link.OperationRef) + } + + return validateExtensions(ctx, link.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/loader.go b/vendor/github.com/getkin/kin-openapi/openapi3/loader.go new file mode 100644 index 00000000..31c34076 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/loader.go @@ -0,0 +1,1213 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/url" + "os" + "path" + "path/filepath" + "reflect" + "strconv" + "strings" +) + +func foundUnresolvedRef(ref string) error { + return fmt.Errorf("found unresolved ref: %q", ref) +} + +func failedToResolveRefFragmentPart(value, what string) error { + return fmt.Errorf("failed to resolve %q in fragment in URI: %q", what, value) +} + +// Loader helps deserialize an OpenAPIv3 document +type Loader struct { + // IsExternalRefsAllowed enables visiting other files + IsExternalRefsAllowed bool + + // ReadFromURIFunc allows overriding the any file/URL reading func + ReadFromURIFunc ReadFromURIFunc + + Context context.Context + + rootDir string + rootLocation string + + visitedPathItemRefs map[string]struct{} + + visitedDocuments map[string]*T + + visitedRefs map[string]struct{} + visitedPath []string + backtrack map[string][]func(value any) +} + +// NewLoader returns an empty Loader +func NewLoader() *Loader { + return &Loader{ + Context: context.Background(), + } +} + +func (loader *Loader) resetVisitedPathItemRefs() { + loader.visitedPathItemRefs = make(map[string]struct{}) + loader.visitedRefs = make(map[string]struct{}) + loader.visitedPath = nil + loader.backtrack = make(map[string][]func(value any)) +} + +// LoadFromURI loads a spec from a remote URL +func (loader *Loader) LoadFromURI(location *url.URL) (*T, error) { + loader.resetVisitedPathItemRefs() + return loader.loadFromURIInternal(location) +} + +// LoadFromFile loads a spec from a local file path +func (loader *Loader) LoadFromFile(location string) (*T, error) { + loader.rootDir = path.Dir(location) + return loader.LoadFromURI(&url.URL{Path: filepath.ToSlash(location)}) +} + +func (loader *Loader) loadFromURIInternal(location *url.URL) (*T, error) { + data, err := loader.readURL(location) + if err != nil { + return nil, err + } + return loader.loadFromDataWithPathInternal(data, location) +} + +func (loader *Loader) allowsExternalRefs(ref string) (err error) { + if !loader.IsExternalRefsAllowed { + err = fmt.Errorf("encountered disallowed external reference: %q", ref) + } + return +} + +func (loader *Loader) loadSingleElementFromURI(ref string, rootPath *url.URL, element any) (*url.URL, error) { + if err := loader.allowsExternalRefs(ref); err != nil { + return nil, err + } + + resolvedPath, err := resolvePathWithRef(ref, rootPath) + if err != nil { + return nil, err + } + if frag := resolvedPath.Fragment; frag != "" { + return nil, fmt.Errorf("unexpected ref fragment %q", frag) + } + + data, err := loader.readURL(resolvedPath) + if err != nil { + return nil, err + } + if err := unmarshal(data, element); err != nil { + return nil, err + } + + return resolvedPath, nil +} + +func (loader *Loader) readURL(location *url.URL) ([]byte, error) { + if f := loader.ReadFromURIFunc; f != nil { + return f(loader, location) + } + return DefaultReadFromURI(loader, location) +} + +// LoadFromStdin loads a spec from stdin +func (loader *Loader) LoadFromStdin() (*T, error) { + return loader.LoadFromIoReader(os.Stdin) +} + +// LoadFromStdin loads a spec from io.Reader +func (loader *Loader) LoadFromIoReader(reader io.Reader) (*T, error) { + if reader == nil { + return nil, fmt.Errorf("invalid reader: %v", reader) + } + + data, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + return loader.LoadFromData(data) +} + +// LoadFromData loads a spec from a byte array +func (loader *Loader) LoadFromData(data []byte) (*T, error) { + loader.resetVisitedPathItemRefs() + doc := &T{} + if err := unmarshal(data, doc); err != nil { + return nil, err + } + if err := loader.ResolveRefsIn(doc, nil); err != nil { + return nil, err + } + return doc, nil +} + +// LoadFromDataWithPath takes the OpenAPI document data in bytes and a path where the resolver can find referred +// elements and returns a *T with all resolved data or an error if unable to load data or resolve refs. +func (loader *Loader) LoadFromDataWithPath(data []byte, location *url.URL) (*T, error) { + loader.resetVisitedPathItemRefs() + return loader.loadFromDataWithPathInternal(data, location) +} + +func (loader *Loader) loadFromDataWithPathInternal(data []byte, location *url.URL) (*T, error) { + if loader.visitedDocuments == nil { + loader.visitedDocuments = make(map[string]*T) + loader.rootLocation = location.Path + } + uri := location.String() + if doc, ok := loader.visitedDocuments[uri]; ok { + return doc, nil + } + + doc := &T{} + loader.visitedDocuments[uri] = doc + + if err := unmarshal(data, doc); err != nil { + return nil, err + } + + doc.url = copyURI(location) + + if err := loader.ResolveRefsIn(doc, location); err != nil { + return nil, err + } + + return doc, nil +} + +// ResolveRefsIn expands references if for instance spec was just unmarshaled +func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error) { + if loader.Context == nil { + loader.Context = context.Background() + } + + if loader.visitedPathItemRefs == nil { + loader.resetVisitedPathItemRefs() + } + + if components := doc.Components; components != nil { + for _, name := range componentNames(components.Headers) { + component := components.Headers[name] + if err = loader.resolveHeaderRef(doc, component, location); err != nil { + return + } + } + for _, name := range componentNames(components.Parameters) { + component := components.Parameters[name] + if err = loader.resolveParameterRef(doc, component, location); err != nil { + return + } + } + for _, name := range componentNames(components.RequestBodies) { + component := components.RequestBodies[name] + if err = loader.resolveRequestBodyRef(doc, component, location); err != nil { + return + } + } + for _, name := range componentNames(components.Responses) { + component := components.Responses[name] + if err = loader.resolveResponseRef(doc, component, location); err != nil { + return + } + } + for _, name := range componentNames(components.Schemas) { + component := components.Schemas[name] + if err = loader.resolveSchemaRef(doc, component, location, []string{}); err != nil { + return + } + } + for _, name := range componentNames(components.SecuritySchemes) { + component := components.SecuritySchemes[name] + if err = loader.resolveSecuritySchemeRef(doc, component, location); err != nil { + return + } + } + for _, name := range componentNames(components.Examples) { + component := components.Examples[name] + if err = loader.resolveExampleRef(doc, component, location); err != nil { + return + } + } + for _, name := range componentNames(components.Callbacks) { + component := components.Callbacks[name] + if err = loader.resolveCallbackRef(doc, component, location); err != nil { + return + } + } + } + + // Visit all operations + pathItems := doc.Paths.Map() + for _, name := range componentNames(pathItems) { + pathItem := pathItems[name] + if pathItem == nil { + continue + } + if err = loader.resolvePathItemRef(doc, pathItem, location); err != nil { + return + } + } + + return +} + +func join(basePath *url.URL, relativePath *url.URL) *url.URL { + if basePath == nil { + return relativePath + } + newPath := *basePath + newPath.Path = path.Join(path.Dir(newPath.Path), relativePath.Path) + return &newPath +} + +func resolvePath(basePath *url.URL, componentPath *url.URL) *url.URL { + if is_file(componentPath) { + // support absolute paths + if filepath.IsAbs(componentPath.Path) { + return componentPath + } + return join(basePath, componentPath) + } + return componentPath +} + +func resolvePathWithRef(ref string, rootPath *url.URL) (*url.URL, error) { + parsedURL, err := url.Parse(ref) + if err != nil { + return nil, fmt.Errorf("cannot parse reference: %q: %w", ref, err) + } + + resolvedPath := resolvePath(rootPath, parsedURL) + resolvedPath.Fragment = parsedURL.Fragment + return resolvedPath, nil +} + +func (loader *Loader) resolveRefPath(ref string, path *url.URL) (*url.URL, error) { + if ref != "" && ref[0] == '#' { + path = copyURI(path) + // Resolving internal refs of a doc loaded from memory + // has no path, so just set the Fragment. + if path == nil { + path = new(url.URL) + } + + path.Fragment = ref + return path, nil + } + + if err := loader.allowsExternalRefs(ref); err != nil { + return nil, err + } + + resolvedPath, err := resolvePathWithRef(ref, path) + if err != nil { + return nil, err + } + + return resolvedPath, nil +} + +func isSingleRefElement(ref string) bool { + return !strings.Contains(ref, "#") +} + +func (loader *Loader) visitRef(ref string) { + if loader.visitedRefs == nil { + loader.visitedRefs = make(map[string]struct{}) + loader.backtrack = make(map[string][]func(value any)) + } + loader.visitedPath = append(loader.visitedPath, ref) + loader.visitedRefs[ref] = struct{}{} +} + +func (loader *Loader) unvisitRef(ref string, value any) { + if value != nil { + for _, fn := range loader.backtrack[ref] { + fn(value) + } + } + delete(loader.visitedRefs, ref) + delete(loader.backtrack, ref) + loader.visitedPath = loader.visitedPath[:len(loader.visitedPath)-1] +} + +func (loader *Loader) shouldVisitRef(ref string, fn func(value any)) bool { + if _, ok := loader.visitedRefs[ref]; ok { + loader.backtrack[ref] = append(loader.backtrack[ref], fn) + return false + } + return true +} + +func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolved any) ( + componentDoc *T, + componentPath *url.URL, + err error, +) { + if componentDoc, ref, componentPath, err = loader.resolveRefAndDocument(doc, ref, path); err != nil { + return nil, nil, err + } + + parsedURL, err := url.Parse(ref) + if err != nil { + return nil, nil, fmt.Errorf("cannot parse reference: %q: %v", ref, parsedURL) + } + fragment := parsedURL.Fragment + if fragment == "" { + fragment = "/" + } + if fragment[0] != '/' { + return nil, nil, fmt.Errorf("expected fragment prefix '#/' in URI %q", ref) + } + + drill := func(cursor any) (any, error) { + for _, pathPart := range strings.Split(fragment[1:], "/") { + pathPart = unescapeRefString(pathPart) + attempted := false + + switch c := cursor.(type) { + // Special case of T + // See issue856: a ref to doc => we assume that doc is a T => things live in T.Extensions + case *T: + if pathPart == "" { + cursor = c.Extensions + attempted = true + } + + // Special case due to multijson + case *SchemaRef: + if pathPart == "additionalProperties" { + if ap := c.Value.AdditionalProperties.Has; ap != nil { + cursor = *ap + } else { + cursor = c.Value.AdditionalProperties.Schema + } + attempted = true + } + + case *Responses: + cursor = c.m // m map[string]*ResponseRef + case *Callback: + cursor = c.m // m map[string]*PathItem + case *Paths: + cursor = c.m // m map[string]*PathItem + } + + if !attempted { + if cursor, err = drillIntoField(cursor, pathPart); err != nil { + e := failedToResolveRefFragmentPart(ref, pathPart) + return nil, fmt.Errorf("%s: %w", e, err) + } + } + + if cursor == nil { + return nil, failedToResolveRefFragmentPart(ref, pathPart) + } + } + return cursor, nil + } + var cursor any + if cursor, err = drill(componentDoc); err != nil { + if path == nil { + return nil, nil, err + } + var err2 error + data, err2 := loader.readURL(path) + if err2 != nil { + return nil, nil, err + } + if err2 = unmarshal(data, &cursor); err2 != nil { + return nil, nil, err + } + if cursor, err2 = drill(cursor); err2 != nil || cursor == nil { + return nil, nil, err + } + err = nil + } + + setPathRef := func(target any) { + if i, ok := target.(interface { + setRefPath(*url.URL) + }); ok { + pathRef := copyURI(componentPath) + // Resolving internal refs of a doc loaded from memory + // has no path, so just set the Fragment. + if pathRef == nil { + pathRef = new(url.URL) + } + pathRef.Fragment = fragment + + i.setRefPath(pathRef) + } + } + + switch { + case reflect.TypeOf(cursor) == reflect.TypeOf(resolved): + setPathRef(cursor) + + reflect.ValueOf(resolved).Elem().Set(reflect.ValueOf(cursor).Elem()) + return componentDoc, componentPath, nil + + case reflect.TypeOf(cursor) == reflect.TypeOf(map[string]any{}): + codec := func(got, expect any) error { + enc, err := json.Marshal(got) + if err != nil { + return err + } + if err = json.Unmarshal(enc, expect); err != nil { + return err + } + + setPathRef(expect) + return nil + } + if err := codec(cursor, resolved); err != nil { + return nil, nil, fmt.Errorf("bad data in %q (expecting %s)", ref, readableType(resolved)) + } + return componentDoc, componentPath, nil + + default: + return nil, nil, fmt.Errorf("bad data in %q (expecting %s)", ref, readableType(resolved)) + } +} + +func readableType(x any) string { + switch x.(type) { + case *Callback: + return "callback object" + case *CallbackRef: + return "ref to callback object" + case *ExampleRef: + return "ref to example object" + case *HeaderRef: + return "ref to header object" + case *LinkRef: + return "ref to link object" + case *ParameterRef: + return "ref to parameter object" + case *PathItem: + return "pathItem object" + case *RequestBodyRef: + return "ref to requestBody object" + case *ResponseRef: + return "ref to response object" + case *SchemaRef: + return "ref to schema object" + case *SecuritySchemeRef: + return "ref to securityScheme object" + default: + panic(fmt.Sprintf("unreachable %T", x)) + } +} + +func drillIntoField(cursor any, fieldName string) (any, error) { + switch val := reflect.Indirect(reflect.ValueOf(cursor)); val.Kind() { + + case reflect.Map: + elementValue := val.MapIndex(reflect.ValueOf(fieldName)) + if !elementValue.IsValid() { + return nil, fmt.Errorf("map key %q not found", fieldName) + } + return elementValue.Interface(), nil + + case reflect.Slice: + i, err := strconv.ParseUint(fieldName, 10, 32) + if err != nil { + return nil, err + } + index := int(i) + if 0 > index || index >= val.Len() { + return nil, errors.New("slice index out of bounds") + } + return val.Index(index).Interface(), nil + + case reflect.Struct: + hasFields := false + for i := 0; i < val.NumField(); i++ { + hasFields = true + if yamlTag := val.Type().Field(i).Tag.Get("yaml"); yamlTag != "-" { + if tagName := strings.Split(yamlTag, ",")[0]; tagName != "" { + if fieldName == tagName { + return val.Field(i).Interface(), nil + } + } + } + } + + // if cursor is a "ref wrapper" struct (e.g. RequestBodyRef), + if _, ok := val.Type().FieldByName("Value"); ok { + // try digging into its Value field + return drillIntoField(val.FieldByName("Value").Interface(), fieldName) + } + if hasFields { + if ff := val.Type().Field(0); ff.PkgPath == "" && ff.Name == "Extensions" { + extensions := val.Field(0).Interface().(map[string]any) + if enc, ok := extensions[fieldName]; ok { + return enc, nil + } + } + } + return nil, fmt.Errorf("struct field %q not found", fieldName) + + default: + return nil, errors.New("not a map, slice nor struct") + } +} + +func (loader *Loader) resolveRefAndDocument(doc *T, ref string, path *url.URL) (*T, string, *url.URL, error) { + if ref != "" && ref[0] == '#' { + return doc, ref, path, nil + } + + fragment, resolvedPath, err := loader.resolveRef(ref, path) + if err != nil { + return nil, "", nil, err + } + + if doc, err = loader.loadFromURIInternal(resolvedPath); err != nil { + return nil, "", nil, fmt.Errorf("error resolving reference %q: %w", ref, err) + } + + return doc, fragment, resolvedPath, nil +} + +func (loader *Loader) resolveRef(ref string, path *url.URL) (string, *url.URL, error) { + resolvedPathRef, err := loader.resolveRefPath(ref, path) + if err != nil { + return "", nil, err + } + + fragment := "#" + resolvedPathRef.Fragment + resolvedPathRef.Fragment = "" + return fragment, resolvedPathRef, nil +} + +var ( + errMUSTCallback = errors.New("invalid callback: value MUST be an object") + errMUSTExample = errors.New("invalid example: value MUST be an object") + errMUSTHeader = errors.New("invalid header: value MUST be an object") + errMUSTLink = errors.New("invalid link: value MUST be an object") + errMUSTParameter = errors.New("invalid parameter: value MUST be an object") + errMUSTPathItem = errors.New("invalid path item: value MUST be an object") + errMUSTRequestBody = errors.New("invalid requestBody: value MUST be an object") + errMUSTResponse = errors.New("invalid response: value MUST be an object") + errMUSTSchema = errors.New("invalid schema: value MUST be an object") + errMUSTSecurityScheme = errors.New("invalid securityScheme: value MUST be an object") +) + +func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPath *url.URL) (err error) { + if component.isEmpty() { + return errMUSTHeader + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Header) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var header Header + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &header); err != nil { + return err + } + component.Value = &header + component.setRefPath(documentPath) + } else { + var resolved HeaderRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveHeaderRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTHeader { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + value := component.Value + if value == nil { + return nil + } + + if schema := value.Schema; schema != nil { + if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil { + return err + } + } + return nil +} + +func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, documentPath *url.URL) (err error) { + if component.isEmpty() { + return errMUSTParameter + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Parameter) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var param Parameter + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, ¶m); err != nil { + return err + } + component.Value = ¶m + component.setRefPath(documentPath) + } else { + var resolved ParameterRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveParameterRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTParameter { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + value := component.Value + if value == nil { + return nil + } + + if value.Content != nil && value.Schema != nil { + return errors.New("cannot contain both schema and content in a parameter") + } + for _, name := range componentNames(value.Content) { + contentType := value.Content[name] + if schema := contentType.Schema; schema != nil { + if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil { + return err + } + } + } + if schema := value.Schema; schema != nil { + if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil { + return err + } + } + return nil +} + +func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, documentPath *url.URL) (err error) { + if component.isEmpty() { + return errMUSTRequestBody + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*RequestBody) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var requestBody RequestBody + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &requestBody); err != nil { + return err + } + component.Value = &requestBody + component.setRefPath(documentPath) + } else { + var resolved RequestBodyRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err = loader.resolveRequestBodyRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTRequestBody { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + value := component.Value + if value == nil { + return nil + } + + for _, name := range componentNames(value.Content) { + contentType := value.Content[name] + if contentType == nil { + continue + } + for _, name := range componentNames(contentType.Examples) { + example := contentType.Examples[name] + if err := loader.resolveExampleRef(doc, example, documentPath); err != nil { + return err + } + contentType.Examples[name] = example + } + if schema := contentType.Schema; schema != nil { + if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil { + return err + } + } + } + return nil +} + +func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documentPath *url.URL) (err error) { + if component.isEmpty() { + return errMUSTResponse + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Response) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var resp Response + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &resp); err != nil { + return err + } + component.Value = &resp + component.setRefPath(documentPath) + } else { + var resolved ResponseRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveResponseRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTResponse { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + value := component.Value + if value == nil { + return nil + } + + for _, name := range componentNames(value.Headers) { + header := value.Headers[name] + if err := loader.resolveHeaderRef(doc, header, documentPath); err != nil { + return err + } + } + for _, name := range componentNames(value.Content) { + contentType := value.Content[name] + if contentType == nil { + continue + } + for _, name := range componentNames(contentType.Examples) { + example := contentType.Examples[name] + if err := loader.resolveExampleRef(doc, example, documentPath); err != nil { + return err + } + contentType.Examples[name] = example + } + if schema := contentType.Schema; schema != nil { + if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil { + return err + } + contentType.Schema = schema + } + } + for _, name := range componentNames(value.Links) { + link := value.Links[name] + if err := loader.resolveLinkRef(doc, link, documentPath); err != nil { + return err + } + } + return nil +} + +func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPath *url.URL, visited []string) (err error) { + if component.isEmpty() { + return errMUSTSchema + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Schema) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var schema Schema + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &schema); err != nil { + return err + } + component.Value = &schema + component.setRefPath(documentPath) + } else { + var resolved SchemaRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveSchemaRef(doc, &resolved, componentPath, visited); err != nil { + if err == errMUSTSchema { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + value := component.Value + if value == nil { + return nil + } + + // ResolveRefs referred schemas + if v := value.Items; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + for _, name := range componentNames(value.Properties) { + v := value.Properties[name] + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + if v := value.AdditionalProperties.Schema; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + if v := value.Not; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + for _, v := range value.AllOf { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + for _, v := range value.AnyOf { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + for _, v := range value.OneOf { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + return nil +} + +func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecuritySchemeRef, documentPath *url.URL) (err error) { + if component.isEmpty() { + return errMUSTSecurityScheme + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*SecurityScheme) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var scheme SecurityScheme + if _, err = loader.loadSingleElementFromURI(ref, documentPath, &scheme); err != nil { + return err + } + component.Value = &scheme + component.setRefPath(documentPath) + } else { + var resolved SecuritySchemeRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveSecuritySchemeRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTSecurityScheme { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + return nil +} + +func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentPath *url.URL) (err error) { + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Example) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var example Example + if _, err = loader.loadSingleElementFromURI(ref, documentPath, &example); err != nil { + return err + } + component.Value = &example + component.setRefPath(documentPath) + } else { + var resolved ExampleRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveExampleRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTExample { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + return nil +} + +func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documentPath *url.URL) (err error) { + if component.isEmpty() { + return errMUSTCallback + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Callback) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var resolved Callback + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &resolved); err != nil { + return err + } + component.Value = &resolved + component.setRefPath(documentPath) + } else { + var resolved CallbackRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err = loader.resolveCallbackRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTCallback { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + value := component.Value + if value == nil { + return nil + } + + pathItems := value.Map() + for _, name := range componentNames(pathItems) { + pathItem := pathItems[name] + if err = loader.resolvePathItemRef(doc, pathItem, documentPath); err != nil { + return err + } + } + return nil +} + +func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *url.URL) (err error) { + if component.isEmpty() { + return errMUSTLink + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Link) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var link Link + if _, err = loader.loadSingleElementFromURI(ref, documentPath, &link); err != nil { + return err + } + component.Value = &link + component.setRefPath(documentPath) + } else { + var resolved LinkRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveLinkRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTLink { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + return nil +} + +func (loader *Loader) resolvePathItemRef(doc *T, pathItem *PathItem, documentPath *url.URL) (err error) { + if pathItem == nil { + err = errMUSTPathItem + return + } + + if ref := pathItem.Ref; ref != "" { + if !pathItem.isEmpty() { + return + } + if !loader.shouldVisitRef(ref, func(value any) { + *pathItem = *value.(*PathItem) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var p PathItem + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &p); err != nil { + return + } + *pathItem = p + } else { + var resolved PathItem + if doc, documentPath, err = loader.resolveComponent(doc, ref, documentPath, &resolved); err != nil { + if err == errMUSTPathItem { + return nil + } + return + } + *pathItem = resolved + } + pathItem.Ref = ref + defer loader.unvisitRef(ref, pathItem) + } + + for _, parameter := range pathItem.Parameters { + if err = loader.resolveParameterRef(doc, parameter, documentPath); err != nil { + return + } + } + operations := pathItem.Operations() + for _, name := range componentNames(operations) { + operation := operations[name] + for _, parameter := range operation.Parameters { + if err = loader.resolveParameterRef(doc, parameter, documentPath); err != nil { + return + } + } + if requestBody := operation.RequestBody; requestBody != nil { + if err = loader.resolveRequestBodyRef(doc, requestBody, documentPath); err != nil { + return + } + } + responses := operation.Responses.Map() + for _, name := range componentNames(responses) { + response := responses[name] + if err = loader.resolveResponseRef(doc, response, documentPath); err != nil { + return + } + } + for _, name := range componentNames(operation.Callbacks) { + callback := operation.Callbacks[name] + if err = loader.resolveCallbackRef(doc, callback, documentPath); err != nil { + return + } + } + } + return +} + +func unescapeRefString(ref string) string { + return strings.Replace(strings.Replace(ref, "~1", "/", -1), "~0", "~", -1) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go b/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go new file mode 100644 index 00000000..b023dfb2 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go @@ -0,0 +1,116 @@ +package openapi3 + +import ( + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + "sync" +) + +// ReadFromURIFunc defines a function which reads the contents of a resource +// located at a URI. +type ReadFromURIFunc func(loader *Loader, url *url.URL) ([]byte, error) + +var uriMu = &sync.RWMutex{} + +// ErrURINotSupported indicates the ReadFromURIFunc does not know how to handle a +// given URI. +var ErrURINotSupported = errors.New("unsupported URI") + +// ReadFromURIs returns a ReadFromURIFunc which tries to read a URI using the +// given reader functions, in the same order. If a reader function does not +// support the URI and returns ErrURINotSupported, the next function is checked +// until a match is found, or the URI is not supported by any. +func ReadFromURIs(readers ...ReadFromURIFunc) ReadFromURIFunc { + return func(loader *Loader, url *url.URL) ([]byte, error) { + for i := range readers { + buf, err := readers[i](loader, url) + if err == ErrURINotSupported { + continue + } else if err != nil { + return nil, err + } + return buf, nil + } + return nil, ErrURINotSupported + } +} + +// DefaultReadFromURI returns a caching ReadFromURIFunc which can read remote +// HTTP URIs and local file URIs. +var DefaultReadFromURI = URIMapCache(ReadFromURIs(ReadFromHTTP(http.DefaultClient), ReadFromFile)) + +// ReadFromHTTP returns a ReadFromURIFunc which uses the given http.Client to +// read the contents from a remote HTTP URI. This client may be customized to +// implement timeouts, RFC 7234 caching, etc. +func ReadFromHTTP(cl *http.Client) ReadFromURIFunc { + return func(loader *Loader, location *url.URL) ([]byte, error) { + if location.Scheme == "" || location.Host == "" { + return nil, ErrURINotSupported + } + req, err := http.NewRequest("GET", location.String(), nil) + if err != nil { + return nil, err + } + resp, err := cl.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode > 399 { + return nil, fmt.Errorf("error loading %q: request returned status code %d", location.String(), resp.StatusCode) + } + return io.ReadAll(resp.Body) + } +} + +func is_file(location *url.URL) bool { + return location.Path != "" && + location.Host == "" && + (location.Scheme == "" || location.Scheme == "file") +} + +// ReadFromFile is a ReadFromURIFunc which reads local file URIs. +func ReadFromFile(loader *Loader, location *url.URL) ([]byte, error) { + if !is_file(location) { + return nil, ErrURINotSupported + } + return os.ReadFile(filepath.FromSlash(location.Path)) +} + +// URIMapCache returns a ReadFromURIFunc that caches the contents read from URI +// locations in a simple map. This cache implementation is suitable for +// short-lived processes such as command-line tools which process OpenAPI +// documents. +func URIMapCache(reader ReadFromURIFunc) ReadFromURIFunc { + cache := map[string][]byte{} + return func(loader *Loader, location *url.URL) (buf []byte, err error) { + if location.Scheme == "" || location.Scheme == "file" { + if !filepath.IsAbs(location.Path) { + // Do not cache relative file paths; this can cause trouble if + // the current working directory changes when processing + // multiple top-level documents. + return reader(loader, location) + } + } + uri := location.String() + var ok bool + uriMu.RLock() + if buf, ok = cache[uri]; ok { + uriMu.RUnlock() + return + } + uriMu.RUnlock() + if buf, err = reader(loader, location); err != nil { + return + } + uriMu.Lock() + defer uriMu.Unlock() + cache[uri] = buf + return + } +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go b/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go new file mode 100644 index 00000000..7b8045c6 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go @@ -0,0 +1,402 @@ +package openapi3 + +import ( + "encoding/json" + "sort" + "strings" + + "github.com/go-openapi/jsonpointer" +) + +// NewResponsesWithCapacity builds a responses object of the given capacity. +func NewResponsesWithCapacity(cap int) *Responses { + if cap == 0 { + return &Responses{m: make(map[string]*ResponseRef)} + } + return &Responses{m: make(map[string]*ResponseRef, cap)} +} + +// Value returns the responses for key or nil +func (responses *Responses) Value(key string) *ResponseRef { + if responses.Len() == 0 { + return nil + } + return responses.m[key] +} + +// Set adds or replaces key 'key' of 'responses' with 'value'. +// Note: 'responses' MUST be non-nil +func (responses *Responses) Set(key string, value *ResponseRef) { + if responses.m == nil { + responses.m = make(map[string]*ResponseRef) + } + responses.m[key] = value +} + +// Len returns the amount of keys in responses excluding responses.Extensions. +func (responses *Responses) Len() int { + if responses == nil || responses.m == nil { + return 0 + } + return len(responses.m) +} + +// Delete removes the entry associated with key 'key' from 'responses'. +func (responses *Responses) Delete(key string) { + if responses != nil && responses.m != nil { + delete(responses.m, key) + } +} + +// Map returns responses as a 'map'. +// Note: iteration on Go maps is not ordered. +func (responses *Responses) Map() (m map[string]*ResponseRef) { + if responses == nil || len(responses.m) == 0 { + return make(map[string]*ResponseRef) + } + m = make(map[string]*ResponseRef, len(responses.m)) + for k, v := range responses.m { + m[k] = v + } + return +} + +var _ jsonpointer.JSONPointable = (*Responses)(nil) + +// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable +func (responses Responses) JSONLookup(token string) (any, error) { + if v := responses.Value(token); v == nil { + vv, _, err := jsonpointer.GetForToken(responses.Extensions, token) + return vv, err + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + var vv *Response = v.Value + return vv, nil + } +} + +// MarshalYAML returns the YAML encoding of Responses. +func (responses *Responses) MarshalYAML() (any, error) { + if responses == nil { + return nil, nil + } + m := make(map[string]any, responses.Len()+len(responses.Extensions)) + for k, v := range responses.Extensions { + m[k] = v + } + for k, v := range responses.Map() { + m[k] = v + } + return m, nil +} + +// MarshalJSON returns the JSON encoding of Responses. +func (responses *Responses) MarshalJSON() ([]byte, error) { + responsesYaml, err := responses.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(responsesYaml) +} + +// UnmarshalJSON sets Responses to a copy of data. +func (responses *Responses) UnmarshalJSON(data []byte) (err error) { + var m map[string]any + if err = json.Unmarshal(data, &m); err != nil { + return + } + + ks := make([]string, 0, len(m)) + for k := range m { + ks = append(ks, k) + } + sort.Strings(ks) + + x := Responses{ + Extensions: make(map[string]any), + m: make(map[string]*ResponseRef, len(m)), + } + + for _, k := range ks { + v := m[k] + if strings.HasPrefix(k, "x-") { + x.Extensions[k] = v + continue + } + + var data []byte + if data, err = json.Marshal(v); err != nil { + return + } + var vv ResponseRef + if err = vv.UnmarshalJSON(data); err != nil { + return + } + x.m[k] = &vv + } + *responses = x + return +} + +// NewCallbackWithCapacity builds a callback object of the given capacity. +func NewCallbackWithCapacity(cap int) *Callback { + if cap == 0 { + return &Callback{m: make(map[string]*PathItem)} + } + return &Callback{m: make(map[string]*PathItem, cap)} +} + +// Value returns the callback for key or nil +func (callback *Callback) Value(key string) *PathItem { + if callback.Len() == 0 { + return nil + } + return callback.m[key] +} + +// Set adds or replaces key 'key' of 'callback' with 'value'. +// Note: 'callback' MUST be non-nil +func (callback *Callback) Set(key string, value *PathItem) { + if callback.m == nil { + callback.m = make(map[string]*PathItem) + } + callback.m[key] = value +} + +// Len returns the amount of keys in callback excluding callback.Extensions. +func (callback *Callback) Len() int { + if callback == nil || callback.m == nil { + return 0 + } + return len(callback.m) +} + +// Delete removes the entry associated with key 'key' from 'callback'. +func (callback *Callback) Delete(key string) { + if callback != nil && callback.m != nil { + delete(callback.m, key) + } +} + +// Map returns callback as a 'map'. +// Note: iteration on Go maps is not ordered. +func (callback *Callback) Map() (m map[string]*PathItem) { + if callback == nil || len(callback.m) == 0 { + return make(map[string]*PathItem) + } + m = make(map[string]*PathItem, len(callback.m)) + for k, v := range callback.m { + m[k] = v + } + return +} + +var _ jsonpointer.JSONPointable = (*Callback)(nil) + +// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable +func (callback Callback) JSONLookup(token string) (any, error) { + if v := callback.Value(token); v == nil { + vv, _, err := jsonpointer.GetForToken(callback.Extensions, token) + return vv, err + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + var vv *PathItem = v + return vv, nil + } +} + +// MarshalYAML returns the YAML encoding of Callback. +func (callback *Callback) MarshalYAML() (any, error) { + if callback == nil { + return nil, nil + } + m := make(map[string]any, callback.Len()+len(callback.Extensions)) + for k, v := range callback.Extensions { + m[k] = v + } + for k, v := range callback.Map() { + m[k] = v + } + return m, nil +} + +// MarshalJSON returns the JSON encoding of Callback. +func (callback *Callback) MarshalJSON() ([]byte, error) { + callbackYaml, err := callback.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(callbackYaml) +} + +// UnmarshalJSON sets Callback to a copy of data. +func (callback *Callback) UnmarshalJSON(data []byte) (err error) { + var m map[string]any + if err = json.Unmarshal(data, &m); err != nil { + return + } + + ks := make([]string, 0, len(m)) + for k := range m { + ks = append(ks, k) + } + sort.Strings(ks) + + x := Callback{ + Extensions: make(map[string]any), + m: make(map[string]*PathItem, len(m)), + } + + for _, k := range ks { + v := m[k] + if strings.HasPrefix(k, "x-") { + x.Extensions[k] = v + continue + } + + var data []byte + if data, err = json.Marshal(v); err != nil { + return + } + var vv PathItem + if err = vv.UnmarshalJSON(data); err != nil { + return + } + x.m[k] = &vv + } + *callback = x + return +} + +// NewPathsWithCapacity builds a paths object of the given capacity. +func NewPathsWithCapacity(cap int) *Paths { + if cap == 0 { + return &Paths{m: make(map[string]*PathItem)} + } + return &Paths{m: make(map[string]*PathItem, cap)} +} + +// Value returns the paths for key or nil +func (paths *Paths) Value(key string) *PathItem { + if paths.Len() == 0 { + return nil + } + return paths.m[key] +} + +// Set adds or replaces key 'key' of 'paths' with 'value'. +// Note: 'paths' MUST be non-nil +func (paths *Paths) Set(key string, value *PathItem) { + if paths.m == nil { + paths.m = make(map[string]*PathItem) + } + paths.m[key] = value +} + +// Len returns the amount of keys in paths excluding paths.Extensions. +func (paths *Paths) Len() int { + if paths == nil || paths.m == nil { + return 0 + } + return len(paths.m) +} + +// Delete removes the entry associated with key 'key' from 'paths'. +func (paths *Paths) Delete(key string) { + if paths != nil && paths.m != nil { + delete(paths.m, key) + } +} + +// Map returns paths as a 'map'. +// Note: iteration on Go maps is not ordered. +func (paths *Paths) Map() (m map[string]*PathItem) { + if paths == nil || len(paths.m) == 0 { + return make(map[string]*PathItem) + } + m = make(map[string]*PathItem, len(paths.m)) + for k, v := range paths.m { + m[k] = v + } + return +} + +var _ jsonpointer.JSONPointable = (*Paths)(nil) + +// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable +func (paths Paths) JSONLookup(token string) (any, error) { + if v := paths.Value(token); v == nil { + vv, _, err := jsonpointer.GetForToken(paths.Extensions, token) + return vv, err + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + var vv *PathItem = v + return vv, nil + } +} + +// MarshalYAML returns the YAML encoding of Paths. +func (paths *Paths) MarshalYAML() (any, error) { + if paths == nil { + return nil, nil + } + m := make(map[string]any, paths.Len()+len(paths.Extensions)) + for k, v := range paths.Extensions { + m[k] = v + } + for k, v := range paths.Map() { + m[k] = v + } + return m, nil +} + +// MarshalJSON returns the JSON encoding of Paths. +func (paths *Paths) MarshalJSON() ([]byte, error) { + pathsYaml, err := paths.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(pathsYaml) +} + +// UnmarshalJSON sets Paths to a copy of data. +func (paths *Paths) UnmarshalJSON(data []byte) (err error) { + var m map[string]any + if err = json.Unmarshal(data, &m); err != nil { + return + } + + ks := make([]string, 0, len(m)) + for k := range m { + ks = append(ks, k) + } + sort.Strings(ks) + + x := Paths{ + Extensions: make(map[string]any), + m: make(map[string]*PathItem, len(m)), + } + + for _, k := range ks { + v := m[k] + if strings.HasPrefix(k, "x-") { + x.Extensions[k] = v + continue + } + + var data []byte + if data, err = json.Marshal(v); err != nil { + return + } + var vv PathItem + if err = vv.UnmarshalJSON(data); err != nil { + return + } + x.m[k] = &vv + } + *paths = x + return +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/marsh.go b/vendor/github.com/getkin/kin-openapi/openapi3/marsh.go new file mode 100644 index 00000000..daa93755 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/marsh.go @@ -0,0 +1,34 @@ +package openapi3 + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/invopop/yaml" +) + +func unmarshalError(jsonUnmarshalErr error) error { + if before, after, found := strings.Cut(jsonUnmarshalErr.Error(), "Bis"); found && before != "" && after != "" { + before = strings.ReplaceAll(before, " Go struct ", " ") + return fmt.Errorf("%s%s", before, strings.ReplaceAll(after, "Bis", "")) + } + return jsonUnmarshalErr +} + +func unmarshal(data []byte, v any) error { + var jsonErr, yamlErr error + + // See https://github.com/getkin/kin-openapi/issues/680 + if jsonErr = json.Unmarshal(data, v); jsonErr == nil { + return nil + } + + // UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys + if yamlErr = yaml.Unmarshal(data, v); yamlErr == nil { + return nil + } + + // If both unmarshaling attempts fail, return a new error that includes both errors + return fmt.Errorf("failed to unmarshal data: json error: %v, yaml error: %v", jsonErr, yamlErr) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go b/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go new file mode 100644 index 00000000..d4466bcf --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go @@ -0,0 +1,179 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "sort" + + "github.com/go-openapi/jsonpointer" +) + +// MediaType is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#media-type-object +type MediaType struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` + Example any `json:"example,omitempty" yaml:"example,omitempty"` + Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"` + Encoding map[string]*Encoding `json:"encoding,omitempty" yaml:"encoding,omitempty"` +} + +var _ jsonpointer.JSONPointable = (*MediaType)(nil) + +func NewMediaType() *MediaType { + return &MediaType{} +} + +func (mediaType *MediaType) WithSchema(schema *Schema) *MediaType { + if schema == nil { + mediaType.Schema = nil + } else { + mediaType.Schema = &SchemaRef{Value: schema} + } + return mediaType +} + +func (mediaType *MediaType) WithSchemaRef(schema *SchemaRef) *MediaType { + mediaType.Schema = schema + return mediaType +} + +func (mediaType *MediaType) WithExample(name string, value any) *MediaType { + example := mediaType.Examples + if example == nil { + example = make(map[string]*ExampleRef) + mediaType.Examples = example + } + example[name] = &ExampleRef{ + Value: NewExample(value), + } + return mediaType +} + +func (mediaType *MediaType) WithEncoding(name string, enc *Encoding) *MediaType { + encoding := mediaType.Encoding + if encoding == nil { + encoding = make(map[string]*Encoding) + mediaType.Encoding = encoding + } + encoding[name] = enc + return mediaType +} + +// MarshalJSON returns the JSON encoding of MediaType. +func (mediaType MediaType) MarshalJSON() ([]byte, error) { + x, err := mediaType.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of MediaType. +func (mediaType MediaType) MarshalYAML() (any, error) { + m := make(map[string]any, 4+len(mediaType.Extensions)) + for k, v := range mediaType.Extensions { + m[k] = v + } + if x := mediaType.Schema; x != nil { + m["schema"] = x + } + if x := mediaType.Example; x != nil { + m["example"] = x + } + if x := mediaType.Examples; len(x) != 0 { + m["examples"] = x + } + if x := mediaType.Encoding; len(x) != 0 { + m["encoding"] = x + } + return m, nil +} + +// UnmarshalJSON sets MediaType to a copy of data. +func (mediaType *MediaType) UnmarshalJSON(data []byte) error { + type MediaTypeBis MediaType + var x MediaTypeBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "schema") + delete(x.Extensions, "example") + delete(x.Extensions, "examples") + delete(x.Extensions, "encoding") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *mediaType = MediaType(x) + return nil +} + +// Validate returns an error if MediaType does not comply with the OpenAPI spec. +func (mediaType *MediaType) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if mediaType == nil { + return nil + } + if schema := mediaType.Schema; schema != nil { + if err := schema.Validate(ctx); err != nil { + return err + } + + if mediaType.Example != nil && mediaType.Examples != nil { + return errors.New("example and examples are mutually exclusive") + } + + if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled { + if example := mediaType.Example; example != nil { + if err := validateExampleValue(ctx, example, schema.Value); err != nil { + return fmt.Errorf("invalid example: %w", err) + } + } + + if examples := mediaType.Examples; examples != nil { + names := make([]string, 0, len(examples)) + for name := range examples { + names = append(names, name) + } + sort.Strings(names) + for _, k := range names { + v := examples[k] + if err := v.Validate(ctx); err != nil { + return fmt.Errorf("example %s: %w", k, err) + } + if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil { + return fmt.Errorf("example %s: %w", k, err) + } + } + } + } + } + + return validateExtensions(ctx, mediaType.Extensions) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (mediaType MediaType) JSONLookup(token string) (any, error) { + switch token { + case "schema": + if mediaType.Schema != nil { + if mediaType.Schema.Ref != "" { + return &Ref{Ref: mediaType.Schema.Ref}, nil + } + return mediaType.Schema.Value, nil + } + case "example": + return mediaType.Example, nil + case "examples": + return mediaType.Examples, nil + case "encoding": + return mediaType.Encoding, nil + } + v, _, err := jsonpointer.GetForToken(mediaType.Extensions, token) + return v, err +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go b/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go new file mode 100644 index 00000000..ef1592e8 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go @@ -0,0 +1,205 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/url" + + "github.com/go-openapi/jsonpointer" +) + +// T is the root of an OpenAPI v3 document +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#openapi-object +type T struct { + Extensions map[string]any `json:"-" yaml:"-"` + + OpenAPI string `json:"openapi" yaml:"openapi"` // Required + Components *Components `json:"components,omitempty" yaml:"components,omitempty"` + Info *Info `json:"info" yaml:"info"` // Required + Paths *Paths `json:"paths" yaml:"paths"` // Required + Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"` + Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"` + Tags Tags `json:"tags,omitempty" yaml:"tags,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + + visited visitedComponent + url *url.URL +} + +var _ jsonpointer.JSONPointable = (*T)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (doc *T) JSONLookup(token string) (any, error) { + switch token { + case "openapi": + return doc.OpenAPI, nil + case "components": + return doc.Components, nil + case "info": + return doc.Info, nil + case "paths": + return doc.Paths, nil + case "security": + return doc.Security, nil + case "servers": + return doc.Servers, nil + case "tags": + return doc.Tags, nil + case "externalDocs": + return doc.ExternalDocs, nil + } + + v, _, err := jsonpointer.GetForToken(doc.Extensions, token) + return v, err +} + +// MarshalJSON returns the JSON encoding of T. +func (doc *T) MarshalJSON() ([]byte, error) { + x, err := doc.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of T. +func (doc *T) MarshalYAML() (any, error) { + if doc == nil { + return nil, nil + } + m := make(map[string]any, 4+len(doc.Extensions)) + for k, v := range doc.Extensions { + m[k] = v + } + m["openapi"] = doc.OpenAPI + if x := doc.Components; x != nil { + m["components"] = x + } + m["info"] = doc.Info + m["paths"] = doc.Paths + if x := doc.Security; len(x) != 0 { + m["security"] = x + } + if x := doc.Servers; len(x) != 0 { + m["servers"] = x + } + if x := doc.Tags; len(x) != 0 { + m["tags"] = x + } + if x := doc.ExternalDocs; x != nil { + m["externalDocs"] = x + } + return m, nil +} + +// UnmarshalJSON sets T to a copy of data. +func (doc *T) UnmarshalJSON(data []byte) error { + type TBis T + var x TBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "openapi") + delete(x.Extensions, "components") + delete(x.Extensions, "info") + delete(x.Extensions, "paths") + delete(x.Extensions, "security") + delete(x.Extensions, "servers") + delete(x.Extensions, "tags") + delete(x.Extensions, "externalDocs") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *doc = T(x) + return nil +} + +func (doc *T) AddOperation(path string, method string, operation *Operation) { + if doc.Paths == nil { + doc.Paths = NewPaths() + } + pathItem := doc.Paths.Value(path) + if pathItem == nil { + pathItem = &PathItem{} + doc.Paths.Set(path, pathItem) + } + pathItem.SetOperation(method, operation) +} + +func (doc *T) AddServer(server *Server) { + doc.Servers = append(doc.Servers, server) +} + +func (doc *T) AddServers(servers ...*Server) { + doc.Servers = append(doc.Servers, servers...) +} + +// Validate returns an error if T does not comply with the OpenAPI spec. +// Validations Options can be provided to modify the validation behavior. +func (doc *T) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if doc.OpenAPI == "" { + return errors.New("value of openapi must be a non-empty string") + } + + var wrap func(error) error + + wrap = func(e error) error { return fmt.Errorf("invalid components: %w", e) } + if v := doc.Components; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } + + wrap = func(e error) error { return fmt.Errorf("invalid info: %w", e) } + if v := doc.Info; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } else { + return wrap(errors.New("must be an object")) + } + + wrap = func(e error) error { return fmt.Errorf("invalid paths: %w", e) } + if v := doc.Paths; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } else { + return wrap(errors.New("must be an object")) + } + + wrap = func(e error) error { return fmt.Errorf("invalid security: %w", e) } + if v := doc.Security; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } + + wrap = func(e error) error { return fmt.Errorf("invalid servers: %w", e) } + if v := doc.Servers; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } + + wrap = func(e error) error { return fmt.Errorf("invalid tags: %w", e) } + if v := doc.Tags; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } + + wrap = func(e error) error { return fmt.Errorf("invalid external docs: %w", e) } + if v := doc.ExternalDocs; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } + + return validateExtensions(ctx, doc.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/operation.go b/vendor/github.com/getkin/kin-openapi/openapi3/operation.go new file mode 100644 index 00000000..40abf73c --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/operation.go @@ -0,0 +1,222 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strconv" + + "github.com/go-openapi/jsonpointer" +) + +// Operation represents "operation" specified by" OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object +type Operation struct { + Extensions map[string]any `json:"-" yaml:"-"` + + // Optional tags for documentation. + Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` + + // Optional short summary. + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + + // Optional description. Should use CommonMark syntax. + Description string `json:"description,omitempty" yaml:"description,omitempty"` + + // Optional operation ID. + OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"` + + // Optional parameters. + Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"` + + // Optional body parameter. + RequestBody *RequestBodyRef `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` + + // Responses. + Responses *Responses `json:"responses" yaml:"responses"` // Required + + // Optional callbacks + Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` + + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + + // Optional security requirements that overrides top-level security. + Security *SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"` + + // Optional servers that overrides top-level servers. + Servers *Servers `json:"servers,omitempty" yaml:"servers,omitempty"` + + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` +} + +var _ jsonpointer.JSONPointable = (*Operation)(nil) + +func NewOperation() *Operation { + return &Operation{} +} + +// MarshalJSON returns the JSON encoding of Operation. +func (operation Operation) MarshalJSON() ([]byte, error) { + x, err := operation.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Operation. +func (operation Operation) MarshalYAML() (any, error) { + m := make(map[string]any, 12+len(operation.Extensions)) + for k, v := range operation.Extensions { + m[k] = v + } + if x := operation.Tags; len(x) != 0 { + m["tags"] = x + } + if x := operation.Summary; x != "" { + m["summary"] = x + } + if x := operation.Description; x != "" { + m["description"] = x + } + if x := operation.OperationID; x != "" { + m["operationId"] = x + } + if x := operation.Parameters; len(x) != 0 { + m["parameters"] = x + } + if x := operation.RequestBody; x != nil { + m["requestBody"] = x + } + m["responses"] = operation.Responses + if x := operation.Callbacks; len(x) != 0 { + m["callbacks"] = x + } + if x := operation.Deprecated; x { + m["deprecated"] = x + } + if x := operation.Security; x != nil { + m["security"] = x + } + if x := operation.Servers; x != nil { + m["servers"] = x + } + if x := operation.ExternalDocs; x != nil { + m["externalDocs"] = x + } + return m, nil +} + +// UnmarshalJSON sets Operation to a copy of data. +func (operation *Operation) UnmarshalJSON(data []byte) error { + type OperationBis Operation + var x OperationBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "tags") + delete(x.Extensions, "summary") + delete(x.Extensions, "description") + delete(x.Extensions, "operationId") + delete(x.Extensions, "parameters") + delete(x.Extensions, "requestBody") + delete(x.Extensions, "responses") + delete(x.Extensions, "callbacks") + delete(x.Extensions, "deprecated") + delete(x.Extensions, "security") + delete(x.Extensions, "servers") + delete(x.Extensions, "externalDocs") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *operation = Operation(x) + return nil +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (operation Operation) JSONLookup(token string) (any, error) { + switch token { + case "requestBody": + if operation.RequestBody != nil { + if operation.RequestBody.Ref != "" { + return &Ref{Ref: operation.RequestBody.Ref}, nil + } + return operation.RequestBody.Value, nil + } + case "tags": + return operation.Tags, nil + case "summary": + return operation.Summary, nil + case "description": + return operation.Description, nil + case "operationID": + return operation.OperationID, nil + case "parameters": + return operation.Parameters, nil + case "responses": + return operation.Responses, nil + case "callbacks": + return operation.Callbacks, nil + case "deprecated": + return operation.Deprecated, nil + case "security": + return operation.Security, nil + case "servers": + return operation.Servers, nil + case "externalDocs": + return operation.ExternalDocs, nil + } + + v, _, err := jsonpointer.GetForToken(operation.Extensions, token) + return v, err +} + +func (operation *Operation) AddParameter(p *Parameter) { + operation.Parameters = append(operation.Parameters, &ParameterRef{Value: p}) +} + +func (operation *Operation) AddResponse(status int, response *Response) { + code := "default" + if 0 < status && status < 1000 { + code = strconv.FormatInt(int64(status), 10) + } + if operation.Responses == nil { + operation.Responses = NewResponses() + } + operation.Responses.Set(code, &ResponseRef{Value: response}) +} + +// Validate returns an error if Operation does not comply with the OpenAPI spec. +func (operation *Operation) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if v := operation.Parameters; v != nil { + if err := v.Validate(ctx); err != nil { + return err + } + } + + if v := operation.RequestBody; v != nil { + if err := v.Validate(ctx); err != nil { + return err + } + } + + if v := operation.Responses; v != nil { + if err := v.Validate(ctx); err != nil { + return err + } + } else { + return errors.New("value of responses must be an object") + } + + if v := operation.ExternalDocs; v != nil { + if err := v.Validate(ctx); err != nil { + return fmt.Errorf("invalid external docs: %w", err) + } + } + + return validateExtensions(ctx, operation.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go b/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go new file mode 100644 index 00000000..34fe2911 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go @@ -0,0 +1,416 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "sort" + "strconv" + + "github.com/go-openapi/jsonpointer" +) + +// Parameters is specified by OpenAPI/Swagger 3.0 standard. +type Parameters []*ParameterRef + +var _ jsonpointer.JSONPointable = (*Parameters)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (p Parameters) JSONLookup(token string) (any, error) { + index, err := strconv.Atoi(token) + if err != nil { + return nil, err + } + if index < 0 || index >= len(p) { + return nil, fmt.Errorf("index %d out of bounds of array of length %d", index, len(p)) + } + + ref := p[index] + if ref != nil && ref.Ref != "" { + return &Ref{Ref: ref.Ref}, nil + } + return ref.Value, nil +} + +func NewParameters() Parameters { + return make(Parameters, 0, 4) +} + +func (parameters Parameters) GetByInAndName(in string, name string) *Parameter { + for _, item := range parameters { + if v := item.Value; v != nil { + if v.Name == name && v.In == in { + return v + } + } + } + return nil +} + +// Validate returns an error if Parameters does not comply with the OpenAPI spec. +func (parameters Parameters) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + dupes := make(map[string]struct{}) + for _, parameterRef := range parameters { + if v := parameterRef.Value; v != nil { + key := v.In + ":" + v.Name + if _, ok := dupes[key]; ok { + return fmt.Errorf("more than one %q parameter has name %q", v.In, v.Name) + } + dupes[key] = struct{}{} + } + + if err := parameterRef.Validate(ctx); err != nil { + return err + } + } + return nil +} + +// Parameter is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object +type Parameter struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Name string `json:"name,omitempty" yaml:"name,omitempty"` + In string `json:"in,omitempty" yaml:"in,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Style string `json:"style,omitempty" yaml:"style,omitempty"` + Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"` + AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` + AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + Required bool `json:"required,omitempty" yaml:"required,omitempty"` + Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` + Example any `json:"example,omitempty" yaml:"example,omitempty"` + Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"` + Content Content `json:"content,omitempty" yaml:"content,omitempty"` +} + +var _ jsonpointer.JSONPointable = (*Parameter)(nil) + +const ( + ParameterInPath = "path" + ParameterInQuery = "query" + ParameterInHeader = "header" + ParameterInCookie = "cookie" +) + +func NewPathParameter(name string) *Parameter { + return &Parameter{ + Name: name, + In: ParameterInPath, + Required: true, + } +} + +func NewQueryParameter(name string) *Parameter { + return &Parameter{ + Name: name, + In: ParameterInQuery, + } +} + +func NewHeaderParameter(name string) *Parameter { + return &Parameter{ + Name: name, + In: ParameterInHeader, + } +} + +func NewCookieParameter(name string) *Parameter { + return &Parameter{ + Name: name, + In: ParameterInCookie, + } +} + +func (parameter *Parameter) WithDescription(value string) *Parameter { + parameter.Description = value + return parameter +} + +func (parameter *Parameter) WithRequired(value bool) *Parameter { + parameter.Required = value + return parameter +} + +func (parameter *Parameter) WithSchema(value *Schema) *Parameter { + if value == nil { + parameter.Schema = nil + } else { + parameter.Schema = &SchemaRef{ + Value: value, + } + } + return parameter +} + +// MarshalJSON returns the JSON encoding of Parameter. +func (parameter Parameter) MarshalJSON() ([]byte, error) { + x, err := parameter.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Parameter. +func (parameter Parameter) MarshalYAML() (any, error) { + m := make(map[string]any, 13+len(parameter.Extensions)) + for k, v := range parameter.Extensions { + m[k] = v + } + + if x := parameter.Name; x != "" { + m["name"] = x + } + if x := parameter.In; x != "" { + m["in"] = x + } + if x := parameter.Description; x != "" { + m["description"] = x + } + if x := parameter.Style; x != "" { + m["style"] = x + } + if x := parameter.Explode; x != nil { + m["explode"] = x + } + if x := parameter.AllowEmptyValue; x { + m["allowEmptyValue"] = x + } + if x := parameter.AllowReserved; x { + m["allowReserved"] = x + } + if x := parameter.Deprecated; x { + m["deprecated"] = x + } + if x := parameter.Required; x { + m["required"] = x + } + if x := parameter.Schema; x != nil { + m["schema"] = x + } + if x := parameter.Example; x != nil { + m["example"] = x + } + if x := parameter.Examples; len(x) != 0 { + m["examples"] = x + } + if x := parameter.Content; len(x) != 0 { + m["content"] = x + } + + return m, nil +} + +// UnmarshalJSON sets Parameter to a copy of data. +func (parameter *Parameter) UnmarshalJSON(data []byte) error { + type ParameterBis Parameter + var x ParameterBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + + delete(x.Extensions, "name") + delete(x.Extensions, "in") + delete(x.Extensions, "description") + delete(x.Extensions, "style") + delete(x.Extensions, "explode") + delete(x.Extensions, "allowEmptyValue") + delete(x.Extensions, "allowReserved") + delete(x.Extensions, "deprecated") + delete(x.Extensions, "required") + delete(x.Extensions, "schema") + delete(x.Extensions, "example") + delete(x.Extensions, "examples") + delete(x.Extensions, "content") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + + *parameter = Parameter(x) + return nil +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (parameter Parameter) JSONLookup(token string) (any, error) { + switch token { + case "schema": + if parameter.Schema != nil { + if parameter.Schema.Ref != "" { + return &Ref{Ref: parameter.Schema.Ref}, nil + } + return parameter.Schema.Value, nil + } + case "name": + return parameter.Name, nil + case "in": + return parameter.In, nil + case "description": + return parameter.Description, nil + case "style": + return parameter.Style, nil + case "explode": + return parameter.Explode, nil + case "allowEmptyValue": + return parameter.AllowEmptyValue, nil + case "allowReserved": + return parameter.AllowReserved, nil + case "deprecated": + return parameter.Deprecated, nil + case "required": + return parameter.Required, nil + case "example": + return parameter.Example, nil + case "examples": + return parameter.Examples, nil + case "content": + return parameter.Content, nil + } + + v, _, err := jsonpointer.GetForToken(parameter.Extensions, token) + return v, err +} + +// SerializationMethod returns a parameter's serialization method. +// When a parameter's serialization method is not defined the method returns +// the default serialization method corresponding to a parameter's location. +func (parameter *Parameter) SerializationMethod() (*SerializationMethod, error) { + switch parameter.In { + case ParameterInPath, ParameterInHeader: + style := parameter.Style + if style == "" { + style = SerializationSimple + } + explode := false + if parameter.Explode != nil { + explode = *parameter.Explode + } + return &SerializationMethod{Style: style, Explode: explode}, nil + case ParameterInQuery, ParameterInCookie: + style := parameter.Style + if style == "" { + style = SerializationForm + } + explode := true + if parameter.Explode != nil { + explode = *parameter.Explode + } + return &SerializationMethod{Style: style, Explode: explode}, nil + default: + return nil, fmt.Errorf("unexpected parameter's 'in': %q", parameter.In) + } +} + +// Validate returns an error if Parameter does not comply with the OpenAPI spec. +func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if parameter.Name == "" { + return errors.New("parameter name can't be blank") + } + in := parameter.In + switch in { + case + ParameterInPath, + ParameterInQuery, + ParameterInHeader, + ParameterInCookie: + default: + return fmt.Errorf("parameter can't have 'in' value %q", parameter.In) + } + + if in == ParameterInPath && !parameter.Required { + return fmt.Errorf("path parameter %q must be required", parameter.Name) + } + + // Validate a parameter's serialization method. + sm, err := parameter.SerializationMethod() + if err != nil { + return err + } + var smSupported bool + switch { + case parameter.In == ParameterInPath && sm.Style == SerializationSimple && !sm.Explode, + parameter.In == ParameterInPath && sm.Style == SerializationSimple && sm.Explode, + parameter.In == ParameterInPath && sm.Style == SerializationLabel && !sm.Explode, + parameter.In == ParameterInPath && sm.Style == SerializationLabel && sm.Explode, + parameter.In == ParameterInPath && sm.Style == SerializationMatrix && !sm.Explode, + parameter.In == ParameterInPath && sm.Style == SerializationMatrix && sm.Explode, + + parameter.In == ParameterInQuery && sm.Style == SerializationForm && sm.Explode, + parameter.In == ParameterInQuery && sm.Style == SerializationForm && !sm.Explode, + parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && sm.Explode, + parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && !sm.Explode, + parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && sm.Explode, + parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && !sm.Explode, + parameter.In == ParameterInQuery && sm.Style == SerializationDeepObject && sm.Explode, + + parameter.In == ParameterInHeader && sm.Style == SerializationSimple && !sm.Explode, + parameter.In == ParameterInHeader && sm.Style == SerializationSimple && sm.Explode, + + parameter.In == ParameterInCookie && sm.Style == SerializationForm && !sm.Explode, + parameter.In == ParameterInCookie && sm.Style == SerializationForm && sm.Explode: + smSupported = true + } + if !smSupported { + e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a %s parameter", sm.Style, sm.Explode, in) + return fmt.Errorf("parameter %q schema is invalid: %w", parameter.Name, e) + } + + if (parameter.Schema == nil) == (len(parameter.Content) == 0) { + e := errors.New("parameter must contain exactly one of content and schema") + return fmt.Errorf("parameter %q schema is invalid: %w", parameter.Name, e) + } + + if content := parameter.Content; content != nil { + e := errors.New("parameter content must only contain one entry") + if len(content) > 1 { + return fmt.Errorf("parameter %q content is invalid: %w", parameter.Name, e) + } + + if err := content.Validate(ctx); err != nil { + return fmt.Errorf("parameter %q content is invalid: %w", parameter.Name, err) + } + } + + if schema := parameter.Schema; schema != nil { + if err := schema.Validate(ctx); err != nil { + return fmt.Errorf("parameter %q schema is invalid: %w", parameter.Name, err) + } + if parameter.Example != nil && parameter.Examples != nil { + return fmt.Errorf("parameter %q example and examples are mutually exclusive", parameter.Name) + } + + if vo := getValidationOptions(ctx); vo.examplesValidationDisabled { + return nil + } + if example := parameter.Example; example != nil { + if err := validateExampleValue(ctx, example, schema.Value); err != nil { + return fmt.Errorf("invalid example: %w", err) + } + } else if examples := parameter.Examples; examples != nil { + names := make([]string, 0, len(examples)) + for name := range examples { + names = append(names, name) + } + sort.Strings(names) + for _, k := range names { + v := examples[k] + if err := v.Validate(ctx); err != nil { + return fmt.Errorf("%s: %w", k, err) + } + if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil { + return fmt.Errorf("%s: %w", k, err) + } + } + } + } + + return validateExtensions(ctx, parameter.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go b/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go new file mode 100644 index 00000000..859634fe --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go @@ -0,0 +1,248 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "sort" +) + +// PathItem is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#path-item-object +type PathItem struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Connect *Operation `json:"connect,omitempty" yaml:"connect,omitempty"` + Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"` + Get *Operation `json:"get,omitempty" yaml:"get,omitempty"` + Head *Operation `json:"head,omitempty" yaml:"head,omitempty"` + Options *Operation `json:"options,omitempty" yaml:"options,omitempty"` + Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"` + Post *Operation `json:"post,omitempty" yaml:"post,omitempty"` + Put *Operation `json:"put,omitempty" yaml:"put,omitempty"` + Trace *Operation `json:"trace,omitempty" yaml:"trace,omitempty"` + Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"` + Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"` +} + +// MarshalJSON returns the JSON encoding of PathItem. +func (pathItem PathItem) MarshalJSON() ([]byte, error) { + x, err := pathItem.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of PathItem. +func (pathItem PathItem) MarshalYAML() (any, error) { + if ref := pathItem.Ref; ref != "" { + return Ref{Ref: ref}, nil + } + + m := make(map[string]any, 13+len(pathItem.Extensions)) + for k, v := range pathItem.Extensions { + m[k] = v + } + if x := pathItem.Summary; x != "" { + m["summary"] = x + } + if x := pathItem.Description; x != "" { + m["description"] = x + } + if x := pathItem.Connect; x != nil { + m["connect"] = x + } + if x := pathItem.Delete; x != nil { + m["delete"] = x + } + if x := pathItem.Get; x != nil { + m["get"] = x + } + if x := pathItem.Head; x != nil { + m["head"] = x + } + if x := pathItem.Options; x != nil { + m["options"] = x + } + if x := pathItem.Patch; x != nil { + m["patch"] = x + } + if x := pathItem.Post; x != nil { + m["post"] = x + } + if x := pathItem.Put; x != nil { + m["put"] = x + } + if x := pathItem.Trace; x != nil { + m["trace"] = x + } + if x := pathItem.Servers; len(x) != 0 { + m["servers"] = x + } + if x := pathItem.Parameters; len(x) != 0 { + m["parameters"] = x + } + return m, nil +} + +// UnmarshalJSON sets PathItem to a copy of data. +func (pathItem *PathItem) UnmarshalJSON(data []byte) error { + type PathItemBis PathItem + var x PathItemBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "$ref") + delete(x.Extensions, "summary") + delete(x.Extensions, "description") + delete(x.Extensions, "connect") + delete(x.Extensions, "delete") + delete(x.Extensions, "get") + delete(x.Extensions, "head") + delete(x.Extensions, "options") + delete(x.Extensions, "patch") + delete(x.Extensions, "post") + delete(x.Extensions, "put") + delete(x.Extensions, "trace") + delete(x.Extensions, "servers") + delete(x.Extensions, "parameters") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *pathItem = PathItem(x) + return nil +} + +func (pathItem *PathItem) Operations() map[string]*Operation { + operations := make(map[string]*Operation) + if v := pathItem.Connect; v != nil { + operations[http.MethodConnect] = v + } + if v := pathItem.Delete; v != nil { + operations[http.MethodDelete] = v + } + if v := pathItem.Get; v != nil { + operations[http.MethodGet] = v + } + if v := pathItem.Head; v != nil { + operations[http.MethodHead] = v + } + if v := pathItem.Options; v != nil { + operations[http.MethodOptions] = v + } + if v := pathItem.Patch; v != nil { + operations[http.MethodPatch] = v + } + if v := pathItem.Post; v != nil { + operations[http.MethodPost] = v + } + if v := pathItem.Put; v != nil { + operations[http.MethodPut] = v + } + if v := pathItem.Trace; v != nil { + operations[http.MethodTrace] = v + } + return operations +} + +func (pathItem *PathItem) GetOperation(method string) *Operation { + switch method { + case http.MethodConnect: + return pathItem.Connect + case http.MethodDelete: + return pathItem.Delete + case http.MethodGet: + return pathItem.Get + case http.MethodHead: + return pathItem.Head + case http.MethodOptions: + return pathItem.Options + case http.MethodPatch: + return pathItem.Patch + case http.MethodPost: + return pathItem.Post + case http.MethodPut: + return pathItem.Put + case http.MethodTrace: + return pathItem.Trace + default: + panic(fmt.Errorf("unsupported HTTP method %q", method)) + } +} + +func (pathItem *PathItem) SetOperation(method string, operation *Operation) { + switch method { + case http.MethodConnect: + pathItem.Connect = operation + case http.MethodDelete: + pathItem.Delete = operation + case http.MethodGet: + pathItem.Get = operation + case http.MethodHead: + pathItem.Head = operation + case http.MethodOptions: + pathItem.Options = operation + case http.MethodPatch: + pathItem.Patch = operation + case http.MethodPost: + pathItem.Post = operation + case http.MethodPut: + pathItem.Put = operation + case http.MethodTrace: + pathItem.Trace = operation + default: + panic(fmt.Errorf("unsupported HTTP method %q", method)) + } +} + +// Validate returns an error if PathItem does not comply with the OpenAPI spec. +func (pathItem *PathItem) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + operations := pathItem.Operations() + + methods := make([]string, 0, len(operations)) + for method := range operations { + methods = append(methods, method) + } + sort.Strings(methods) + for _, method := range methods { + operation := operations[method] + if err := operation.Validate(ctx); err != nil { + return fmt.Errorf("invalid operation %s: %v", method, err) + } + } + + if v := pathItem.Parameters; v != nil { + if err := v.Validate(ctx); err != nil { + return err + } + } + + return validateExtensions(ctx, pathItem.Extensions) +} + +// isEmpty's introduced in 546590b1 +func (pathItem *PathItem) isEmpty() bool { + // NOTE: ignores pathItem.Extensions + // NOTE: ignores pathItem.Ref + return pathItem.Summary == "" && + pathItem.Description == "" && + pathItem.Connect == nil && + pathItem.Delete == nil && + pathItem.Get == nil && + pathItem.Head == nil && + pathItem.Options == nil && + pathItem.Patch == nil && + pathItem.Post == nil && + pathItem.Put == nil && + pathItem.Trace == nil && + len(pathItem.Servers) == 0 && + len(pathItem.Parameters) == 0 +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/paths.go b/vendor/github.com/getkin/kin-openapi/openapi3/paths.go new file mode 100644 index 00000000..76747412 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/paths.go @@ -0,0 +1,268 @@ +package openapi3 + +import ( + "context" + "fmt" + "sort" + "strings" +) + +// Paths is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object +type Paths struct { + Extensions map[string]any `json:"-" yaml:"-"` + + m map[string]*PathItem +} + +// NewPaths builds a paths object with path items in insertion order. +func NewPaths(opts ...NewPathsOption) *Paths { + paths := NewPathsWithCapacity(len(opts)) + for _, opt := range opts { + opt(paths) + } + return paths +} + +// NewPathsOption describes options to NewPaths func +type NewPathsOption func(*Paths) + +// WithPath adds a named path item +func WithPath(path string, pathItem *PathItem) NewPathsOption { + return func(paths *Paths) { + if p := pathItem; p != nil && path != "" { + paths.Set(path, p) + } + } +} + +// Validate returns an error if Paths does not comply with the OpenAPI spec. +func (paths *Paths) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + normalizedPaths := make(map[string]string, paths.Len()) + + keys := make([]string, 0, paths.Len()) + for key := range paths.Map() { + keys = append(keys, key) + } + sort.Strings(keys) + for _, path := range keys { + pathItem := paths.Value(path) + if path == "" || path[0] != '/' { + return fmt.Errorf("path %q does not start with a forward slash (/)", path) + } + + if pathItem == nil { + pathItem = &PathItem{} + paths.Set(path, pathItem) + } + + normalizedPath, _, varsInPath := normalizeTemplatedPath(path) + if oldPath, ok := normalizedPaths[normalizedPath]; ok { + return fmt.Errorf("conflicting paths %q and %q", path, oldPath) + } + normalizedPaths[path] = path + + var commonParams []string + for _, parameterRef := range pathItem.Parameters { + if parameterRef != nil { + if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath { + commonParams = append(commonParams, parameter.Name) + } + } + } + operations := pathItem.Operations() + methods := make([]string, 0, len(operations)) + for method := range operations { + methods = append(methods, method) + } + sort.Strings(methods) + for _, method := range methods { + operation := operations[method] + var setParams []string + for _, parameterRef := range operation.Parameters { + if parameterRef != nil { + if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath { + setParams = append(setParams, parameter.Name) + } + } + } + if expected := len(setParams) + len(commonParams); expected != len(varsInPath) { + expected -= len(varsInPath) + if expected < 0 { + expected *= -1 + } + missing := make(map[string]struct{}, expected) + definedParams := append(setParams, commonParams...) + for _, name := range definedParams { + if _, ok := varsInPath[name]; !ok { + missing[name] = struct{}{} + } + } + for name := range varsInPath { + got := false + for _, othername := range definedParams { + if othername == name { + got = true + break + } + } + if !got { + missing[name] = struct{}{} + } + } + if len(missing) != 0 { + missings := make([]string, 0, len(missing)) + for name := range missing { + missings = append(missings, name) + } + return fmt.Errorf("operation %s %s must define exactly all path parameters (missing: %v)", method, path, missings) + } + } + } + + if err := pathItem.Validate(ctx); err != nil { + return fmt.Errorf("invalid path %s: %v", path, err) + } + } + + if err := paths.validateUniqueOperationIDs(); err != nil { + return err + } + + return validateExtensions(ctx, paths.Extensions) +} + +// InMatchingOrder returns paths in the order they are matched against URLs. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object +// When matching URLs, concrete (non-templated) paths would be matched +// before their templated counterparts. +func (paths *Paths) InMatchingOrder() []string { + // NOTE: sorting by number of variables ASC then by descending lexicographical + // order seems to be a good heuristic. + if paths.Len() == 0 { + return nil + } + + vars := make(map[int][]string) + max := 0 + for path := range paths.Map() { + count := strings.Count(path, "}") + vars[count] = append(vars[count], path) + if count > max { + max = count + } + } + + ordered := make([]string, 0, paths.Len()) + for c := 0; c <= max; c++ { + if ps, ok := vars[c]; ok { + sort.Sort(sort.Reverse(sort.StringSlice(ps))) + ordered = append(ordered, ps...) + } + } + return ordered +} + +// Find returns a path that matches the key. +// +// The method ignores differences in template variable names (except possible "*" suffix). +// +// For example: +// +// paths := openapi3.Paths { +// "/person/{personName}": &openapi3.PathItem{}, +// } +// pathItem := path.Find("/person/{name}") +// +// would return the correct path item. +func (paths *Paths) Find(key string) *PathItem { + // Try directly access the map + pathItem := paths.Value(key) + if pathItem != nil { + return pathItem + } + + normalizedPath, expected, _ := normalizeTemplatedPath(key) + for path, pathItem := range paths.Map() { + pathNormalized, got, _ := normalizeTemplatedPath(path) + if got == expected && pathNormalized == normalizedPath { + return pathItem + } + } + return nil +} + +func (paths *Paths) validateUniqueOperationIDs() error { + operationIDs := make(map[string]string) + for urlPath, pathItem := range paths.Map() { + if pathItem == nil { + continue + } + for httpMethod, operation := range pathItem.Operations() { + if operation == nil || operation.OperationID == "" { + continue + } + endpoint := httpMethod + " " + urlPath + if endpointDup, ok := operationIDs[operation.OperationID]; ok { + if endpoint > endpointDup { // For make error message a bit more deterministic. May be useful for tests. + endpoint, endpointDup = endpointDup, endpoint + } + return fmt.Errorf("operations %q and %q have the same operation id %q", + endpoint, endpointDup, operation.OperationID) + } + operationIDs[operation.OperationID] = endpoint + } + } + return nil +} + +func normalizeTemplatedPath(path string) (string, uint, map[string]struct{}) { + if strings.IndexByte(path, '{') < 0 { + return path, 0, nil + } + + var buffTpl strings.Builder + buffTpl.Grow(len(path)) + + var ( + cc rune + count uint + isVariable bool + vars = make(map[string]struct{}) + buffVar strings.Builder + ) + for i, c := range path { + if isVariable { + if c == '}' { + // End path variable + isVariable = false + + vars[buffVar.String()] = struct{}{} + buffVar = strings.Builder{} + + // First append possible '*' before this character + // The character '}' will be appended + if i > 0 && cc == '*' { + buffTpl.WriteRune(cc) + } + } else { + buffVar.WriteRune(c) + continue + } + + } else if c == '{' { + // Begin path variable + isVariable = true + + // The character '{' will be appended + count++ + } + + // Append the character + buffTpl.WriteRune(c) + cc = c + } + return buffTpl.String(), count, vars +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/ref.go b/vendor/github.com/getkin/kin-openapi/openapi3/ref.go new file mode 100644 index 00000000..07060731 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/ref.go @@ -0,0 +1,9 @@ +package openapi3 + +//go:generate go run refsgenerator.go + +// Ref is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object +type Ref struct { + Ref string `json:"$ref" yaml:"$ref"` +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/refs.go b/vendor/github.com/getkin/kin-openapi/openapi3/refs.go new file mode 100644 index 00000000..d337b0e3 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/refs.go @@ -0,0 +1,1238 @@ +// Code generated by go generate; DO NOT EDIT. +package openapi3 + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "sort" + "strings" + + "github.com/go-openapi/jsonpointer" + "github.com/perimeterx/marshmallow" +) + +// CallbackRef represents either a Callback or a $ref to a Callback. +// When serializing and both fields are set, Ref is preferred over Value. +type CallbackRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Callback + extra []string + + refPath *url.URL +} + +var _ jsonpointer.JSONPointable = (*CallbackRef)(nil) + +func (x *CallbackRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *CallbackRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *CallbackRef) CollectionName() string { return "callbacks" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *CallbackRef) RefPath() *url.URL { return copyURI(x.refPath) } + +func (x *CallbackRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + +// MarshalYAML returns the YAML encoding of CallbackRef. +func (x CallbackRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of CallbackRef. +func (x CallbackRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets CallbackRef to a copy of data. +func (x *CallbackRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if CallbackRef does not comply with the OpenAPI spec. +func (x *CallbackRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *CallbackRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// ExampleRef represents either a Example or a $ref to a Example. +// When serializing and both fields are set, Ref is preferred over Value. +type ExampleRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Example + extra []string + + refPath *url.URL +} + +var _ jsonpointer.JSONPointable = (*ExampleRef)(nil) + +func (x *ExampleRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *ExampleRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *ExampleRef) CollectionName() string { return "examples" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *ExampleRef) RefPath() *url.URL { return copyURI(x.refPath) } + +func (x *ExampleRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + +// MarshalYAML returns the YAML encoding of ExampleRef. +func (x ExampleRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of ExampleRef. +func (x ExampleRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets ExampleRef to a copy of data. +func (x *ExampleRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if ExampleRef does not comply with the OpenAPI spec. +func (x *ExampleRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *ExampleRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// HeaderRef represents either a Header or a $ref to a Header. +// When serializing and both fields are set, Ref is preferred over Value. +type HeaderRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Header + extra []string + + refPath *url.URL +} + +var _ jsonpointer.JSONPointable = (*HeaderRef)(nil) + +func (x *HeaderRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *HeaderRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *HeaderRef) CollectionName() string { return "headers" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *HeaderRef) RefPath() *url.URL { return copyURI(x.refPath) } + +func (x *HeaderRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + +// MarshalYAML returns the YAML encoding of HeaderRef. +func (x HeaderRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of HeaderRef. +func (x HeaderRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets HeaderRef to a copy of data. +func (x *HeaderRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if HeaderRef does not comply with the OpenAPI spec. +func (x *HeaderRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *HeaderRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// LinkRef represents either a Link or a $ref to a Link. +// When serializing and both fields are set, Ref is preferred over Value. +type LinkRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Link + extra []string + + refPath *url.URL +} + +var _ jsonpointer.JSONPointable = (*LinkRef)(nil) + +func (x *LinkRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *LinkRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *LinkRef) CollectionName() string { return "links" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *LinkRef) RefPath() *url.URL { return copyURI(x.refPath) } + +func (x *LinkRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + +// MarshalYAML returns the YAML encoding of LinkRef. +func (x LinkRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of LinkRef. +func (x LinkRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets LinkRef to a copy of data. +func (x *LinkRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if LinkRef does not comply with the OpenAPI spec. +func (x *LinkRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *LinkRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// ParameterRef represents either a Parameter or a $ref to a Parameter. +// When serializing and both fields are set, Ref is preferred over Value. +type ParameterRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Parameter + extra []string + + refPath *url.URL +} + +var _ jsonpointer.JSONPointable = (*ParameterRef)(nil) + +func (x *ParameterRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *ParameterRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *ParameterRef) CollectionName() string { return "parameters" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *ParameterRef) RefPath() *url.URL { return copyURI(x.refPath) } + +func (x *ParameterRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + +// MarshalYAML returns the YAML encoding of ParameterRef. +func (x ParameterRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of ParameterRef. +func (x ParameterRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets ParameterRef to a copy of data. +func (x *ParameterRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if ParameterRef does not comply with the OpenAPI spec. +func (x *ParameterRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *ParameterRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// RequestBodyRef represents either a RequestBody or a $ref to a RequestBody. +// When serializing and both fields are set, Ref is preferred over Value. +type RequestBodyRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *RequestBody + extra []string + + refPath *url.URL +} + +var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil) + +func (x *RequestBodyRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *RequestBodyRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *RequestBodyRef) CollectionName() string { return "requestBodies" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *RequestBodyRef) RefPath() *url.URL { return copyURI(x.refPath) } + +func (x *RequestBodyRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + +// MarshalYAML returns the YAML encoding of RequestBodyRef. +func (x RequestBodyRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of RequestBodyRef. +func (x RequestBodyRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets RequestBodyRef to a copy of data. +func (x *RequestBodyRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if RequestBodyRef does not comply with the OpenAPI spec. +func (x *RequestBodyRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *RequestBodyRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// ResponseRef represents either a Response or a $ref to a Response. +// When serializing and both fields are set, Ref is preferred over Value. +type ResponseRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Response + extra []string + + refPath *url.URL +} + +var _ jsonpointer.JSONPointable = (*ResponseRef)(nil) + +func (x *ResponseRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *ResponseRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *ResponseRef) CollectionName() string { return "responses" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *ResponseRef) RefPath() *url.URL { return copyURI(x.refPath) } + +func (x *ResponseRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + +// MarshalYAML returns the YAML encoding of ResponseRef. +func (x ResponseRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of ResponseRef. +func (x ResponseRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets ResponseRef to a copy of data. +func (x *ResponseRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if ResponseRef does not comply with the OpenAPI spec. +func (x *ResponseRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *ResponseRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// SchemaRef represents either a Schema or a $ref to a Schema. +// When serializing and both fields are set, Ref is preferred over Value. +type SchemaRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Schema + extra []string + + refPath *url.URL +} + +var _ jsonpointer.JSONPointable = (*SchemaRef)(nil) + +func (x *SchemaRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *SchemaRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *SchemaRef) CollectionName() string { return "schemas" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *SchemaRef) RefPath() *url.URL { return copyURI(x.refPath) } + +func (x *SchemaRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + +// MarshalYAML returns the YAML encoding of SchemaRef. +func (x SchemaRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of SchemaRef. +func (x SchemaRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets SchemaRef to a copy of data. +func (x *SchemaRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if SchemaRef does not comply with the OpenAPI spec. +func (x *SchemaRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *SchemaRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// SecuritySchemeRef represents either a SecurityScheme or a $ref to a SecurityScheme. +// When serializing and both fields are set, Ref is preferred over Value. +type SecuritySchemeRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *SecurityScheme + extra []string + + refPath *url.URL +} + +var _ jsonpointer.JSONPointable = (*SecuritySchemeRef)(nil) + +func (x *SecuritySchemeRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *SecuritySchemeRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *SecuritySchemeRef) CollectionName() string { return "securitySchemes" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *SecuritySchemeRef) RefPath() *url.URL { return copyURI(x.refPath) } + +func (x *SecuritySchemeRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + +// MarshalYAML returns the YAML encoding of SecuritySchemeRef. +func (x SecuritySchemeRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of SecuritySchemeRef. +func (x SecuritySchemeRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets SecuritySchemeRef to a copy of data. +func (x *SecuritySchemeRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if SecuritySchemeRef does not comply with the OpenAPI spec. +func (x *SecuritySchemeRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *SecuritySchemeRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl b/vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl new file mode 100644 index 00000000..a3f5bdab --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl @@ -0,0 +1,151 @@ +// Code generated by go generate; DO NOT EDIT. +package {{ .Package }} + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "sort" + "strings" + + "github.com/go-openapi/jsonpointer" + "github.com/perimeterx/marshmallow" +) +{{ range $type := .Types }} +// {{ $type.Name }}Ref represents either a {{ $type.Name }} or a $ref to a {{ $type.Name }}. +// When serializing and both fields are set, Ref is preferred over Value. +type {{ $type.Name }}Ref struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *{{ $type.Name }} + extra []string + + refPath *url.URL +} + +var _ jsonpointer.JSONPointable = (*{{ $type.Name }}Ref)(nil) + +func (x *{{ $type.Name }}Ref) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *{{ $type.Name }}Ref) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *{{ $type.Name }}Ref) CollectionName() string { return "{{ $type.CollectionName }}" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *{{ $type.Name }}Ref) RefPath() *url.URL { return copyURI(x.refPath) } + +func (x *{{ $type.Name }}Ref) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + +// MarshalYAML returns the YAML encoding of {{ $type.Name }}Ref. +func (x {{ $type.Name }}Ref) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of {{ $type.Name }}Ref. +func (x {{ $type.Name }}Ref) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets {{ $type.Name }}Ref to a copy of data. +func (x *{{ $type.Name }}Ref) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if {{ $type.Name }}Ref does not comply with the OpenAPI spec. +func (x *{{ $type.Name }}Ref) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *{{ $type.Name }}Ref) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} +{{ end -}} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/refs_test.tmpl b/vendor/github.com/getkin/kin-openapi/openapi3/refs_test.tmpl new file mode 100644 index 00000000..634fccf6 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/refs_test.tmpl @@ -0,0 +1,54 @@ +// Code generated by go generate; DO NOT EDIT. +package {{ .Package }} + +import ( + "context" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) +{{ range $type := .Types }} +func Test{{ $type.Name }}Ref_Extensions(t *testing.T) { + data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`) + + ref := {{ $type.Name }}Ref{} + err := json.Unmarshal(data, &ref) + assert.NoError(t, err) + + // captures extension + assert.Equal(t, "#/components/schemas/Pet", ref.Ref) + assert.Equal(t, float64(1), ref.Extensions["x-order"]) + + // does not capture non-extensions + assert.Nil(t, ref.Extensions["something"]) + + // validation + err = ref.Validate(context.Background()) + require.EqualError(t, err, "extra sibling fields: [something]") + + err = ref.Validate(context.Background(), ProhibitExtensionsWithRef()) + require.EqualError(t, err, "extra sibling fields: [something x-order]") + + err = ref.Validate(context.Background(), AllowExtraSiblingFields("something")) + assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined + + // non-extension not json lookable + _, err = ref.JSONLookup("something") + assert.Error(t, err) +{{ if ne $type.Name "Header" }} + t.Run("extentions in value", func(t *testing.T) { + ref.Value = &{{ $type.Name }}{Extensions: map[string]any{}} + ref.Value.Extensions["x-order"] = 2.0 + + // prefers the value next to the \$ref over the one in the \$ref. + v, err := ref.JSONLookup("x-order") + assert.NoError(t, err) + assert.Equal(t, float64(1), v) + }) +{{ else }} + // Header does not have its own extensions. +{{ end -}} +} +{{ end -}} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go b/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go new file mode 100644 index 00000000..6d4b8185 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go @@ -0,0 +1,138 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" +) + +// RequestBody is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#request-body-object +type RequestBody struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Required bool `json:"required,omitempty" yaml:"required,omitempty"` + Content Content `json:"content" yaml:"content"` +} + +func NewRequestBody() *RequestBody { + return &RequestBody{} +} + +func (requestBody *RequestBody) WithDescription(value string) *RequestBody { + requestBody.Description = value + return requestBody +} + +func (requestBody *RequestBody) WithRequired(value bool) *RequestBody { + requestBody.Required = value + return requestBody +} + +func (requestBody *RequestBody) WithContent(content Content) *RequestBody { + requestBody.Content = content + return requestBody +} + +func (requestBody *RequestBody) WithSchemaRef(value *SchemaRef, consumes []string) *RequestBody { + requestBody.Content = NewContentWithSchemaRef(value, consumes) + return requestBody +} + +func (requestBody *RequestBody) WithSchema(value *Schema, consumes []string) *RequestBody { + requestBody.Content = NewContentWithSchema(value, consumes) + return requestBody +} + +func (requestBody *RequestBody) WithJSONSchemaRef(value *SchemaRef) *RequestBody { + requestBody.Content = NewContentWithJSONSchemaRef(value) + return requestBody +} + +func (requestBody *RequestBody) WithJSONSchema(value *Schema) *RequestBody { + requestBody.Content = NewContentWithJSONSchema(value) + return requestBody +} + +func (requestBody *RequestBody) WithFormDataSchemaRef(value *SchemaRef) *RequestBody { + requestBody.Content = NewContentWithFormDataSchemaRef(value) + return requestBody +} + +func (requestBody *RequestBody) WithFormDataSchema(value *Schema) *RequestBody { + requestBody.Content = NewContentWithFormDataSchema(value) + return requestBody +} + +func (requestBody *RequestBody) GetMediaType(mediaType string) *MediaType { + m := requestBody.Content + if m == nil { + return nil + } + return m[mediaType] +} + +// MarshalJSON returns the JSON encoding of RequestBody. +func (requestBody RequestBody) MarshalJSON() ([]byte, error) { + x, err := requestBody.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of RequestBody. +func (requestBody RequestBody) MarshalYAML() (any, error) { + m := make(map[string]any, 3+len(requestBody.Extensions)) + for k, v := range requestBody.Extensions { + m[k] = v + } + if x := requestBody.Description; x != "" { + m["description"] = requestBody.Description + } + if x := requestBody.Required; x { + m["required"] = x + } + if x := requestBody.Content; true { + m["content"] = x + } + return m, nil +} + +// UnmarshalJSON sets RequestBody to a copy of data. +func (requestBody *RequestBody) UnmarshalJSON(data []byte) error { + type RequestBodyBis RequestBody + var x RequestBodyBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "description") + delete(x.Extensions, "required") + delete(x.Extensions, "content") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *requestBody = RequestBody(x) + return nil +} + +// Validate returns an error if RequestBody does not comply with the OpenAPI spec. +func (requestBody *RequestBody) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if requestBody.Content == nil { + return errors.New("content of the request body is required") + } + + if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled { + vo.examplesValidationAsReq, vo.examplesValidationAsRes = true, false + } + + if err := requestBody.Content.Validate(ctx); err != nil { + return err + } + + return validateExtensions(ctx, requestBody.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/response.go b/vendor/github.com/getkin/kin-openapi/openapi3/response.go new file mode 100644 index 00000000..af8fda6f --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/response.go @@ -0,0 +1,227 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "sort" + "strconv" +) + +// Responses is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responses-object +type Responses struct { + Extensions map[string]any `json:"-" yaml:"-"` + + m map[string]*ResponseRef +} + +// NewResponses builds a responses object with response objects in insertion order. +// Given no arguments, NewResponses returns a valid responses object containing a default match-all reponse. +func NewResponses(opts ...NewResponsesOption) *Responses { + if len(opts) == 0 { + return NewResponses(WithName("default", NewResponse().WithDescription(""))) + } + responses := NewResponsesWithCapacity(len(opts)) + for _, opt := range opts { + opt(responses) + } + return responses +} + +// NewResponsesOption describes options to NewResponses func +type NewResponsesOption func(*Responses) + +// WithStatus adds a status code keyed ResponseRef +func WithStatus(status int, responseRef *ResponseRef) NewResponsesOption { + return func(responses *Responses) { + if r := responseRef; r != nil { + code := strconv.FormatInt(int64(status), 10) + responses.Set(code, r) + } + } +} + +// WithName adds a name-keyed Response +func WithName(name string, response *Response) NewResponsesOption { + return func(responses *Responses) { + if r := response; r != nil && name != "" { + responses.Set(name, &ResponseRef{Value: r}) + } + } +} + +// Default returns the default response +func (responses *Responses) Default() *ResponseRef { + return responses.Value("default") +} + +// Status returns a ResponseRef for the given status +// If an exact match isn't initially found a patterned field is checked using +// the first digit to determine the range (eg: 201 to 2XX) +// See https://spec.openapis.org/oas/v3.0.3#patterned-fields-0 +func (responses *Responses) Status(status int) *ResponseRef { + st := strconv.FormatInt(int64(status), 10) + if rref := responses.Value(st); rref != nil { + return rref + } + if 99 < status && status < 600 { + st = string(st[0]) + "XX" + switch st { + case "1XX", "2XX", "3XX", "4XX", "5XX": + return responses.Value(st) + } + } + return nil +} + +// Validate returns an error if Responses does not comply with the OpenAPI spec. +func (responses *Responses) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if responses.Len() == 0 { + return errors.New("the responses object MUST contain at least one response code") + } + + keys := make([]string, 0, responses.Len()) + for key := range responses.Map() { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + v := responses.Value(key) + if err := v.Validate(ctx); err != nil { + return err + } + } + + return validateExtensions(ctx, responses.Extensions) +} + +// Response is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#response-object +type Response struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Description *string `json:"description,omitempty" yaml:"description,omitempty"` + Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"` + Content Content `json:"content,omitempty" yaml:"content,omitempty"` + Links Links `json:"links,omitempty" yaml:"links,omitempty"` +} + +func NewResponse() *Response { + return &Response{} +} + +func (response *Response) WithDescription(value string) *Response { + response.Description = &value + return response +} + +func (response *Response) WithContent(content Content) *Response { + response.Content = content + return response +} + +func (response *Response) WithJSONSchema(schema *Schema) *Response { + response.Content = NewContentWithJSONSchema(schema) + return response +} + +func (response *Response) WithJSONSchemaRef(schema *SchemaRef) *Response { + response.Content = NewContentWithJSONSchemaRef(schema) + return response +} + +// MarshalJSON returns the JSON encoding of Response. +func (response Response) MarshalJSON() ([]byte, error) { + x, err := response.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Response. +func (response Response) MarshalYAML() (any, error) { + m := make(map[string]any, 4+len(response.Extensions)) + for k, v := range response.Extensions { + m[k] = v + } + if x := response.Description; x != nil { + m["description"] = x + } + if x := response.Headers; len(x) != 0 { + m["headers"] = x + } + if x := response.Content; len(x) != 0 { + m["content"] = x + } + if x := response.Links; len(x) != 0 { + m["links"] = x + } + return m, nil +} + +// UnmarshalJSON sets Response to a copy of data. +func (response *Response) UnmarshalJSON(data []byte) error { + type ResponseBis Response + var x ResponseBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "description") + delete(x.Extensions, "headers") + delete(x.Extensions, "content") + delete(x.Extensions, "links") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *response = Response(x) + return nil +} + +// Validate returns an error if Response does not comply with the OpenAPI spec. +func (response *Response) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if response.Description == nil { + return errors.New("a short description of the response is required") + } + if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled { + vo.examplesValidationAsReq, vo.examplesValidationAsRes = false, true + } + + if content := response.Content; content != nil { + if err := content.Validate(ctx); err != nil { + return err + } + } + + headers := make([]string, 0, len(response.Headers)) + for name := range response.Headers { + headers = append(headers, name) + } + sort.Strings(headers) + for _, name := range headers { + header := response.Headers[name] + if err := header.Validate(ctx); err != nil { + return err + } + } + + links := make([]string, 0, len(response.Links)) + for name := range response.Links { + links = append(links, name) + } + sort.Strings(links) + for _, name := range links { + link := response.Links[name] + if err := link.Validate(ctx); err != nil { + return err + } + } + + return validateExtensions(ctx, response.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema.go new file mode 100644 index 00000000..f8119606 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema.go @@ -0,0 +1,2246 @@ +package openapi3 + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "math" + "math/big" + "reflect" + "sort" + "strconv" + "strings" + "sync" + "unicode/utf16" + + "github.com/go-openapi/jsonpointer" + "github.com/mohae/deepcopy" +) + +const ( + TypeArray = "array" + TypeBoolean = "boolean" + TypeInteger = "integer" + TypeNumber = "number" + TypeObject = "object" + TypeString = "string" + TypeNull = "null" +) + +var ( + // SchemaErrorDetailsDisabled disables printing of details about schema errors. + SchemaErrorDetailsDisabled = false + + errSchema = errors.New("input does not match the schema") + + // ErrOneOfConflict is the SchemaError Origin when data matches more than one oneOf schema + ErrOneOfConflict = errors.New("input matches more than one oneOf schemas") + + // ErrSchemaInputNaN may be returned when validating a number + ErrSchemaInputNaN = errors.New("floating point NaN is not allowed") + // ErrSchemaInputInf may be returned when validating a number + ErrSchemaInputInf = errors.New("floating point Inf is not allowed") + + compiledPatterns sync.Map +) + +// NewSchemaRef simply builds a SchemaRef +func NewSchemaRef(ref string, value *Schema) *SchemaRef { + return &SchemaRef{ + Ref: ref, + Value: value, + } +} + +type SchemaRefs []*SchemaRef + +var _ jsonpointer.JSONPointable = (*SchemaRefs)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (s SchemaRefs) JSONLookup(token string) (any, error) { + i, err := strconv.ParseUint(token, 10, 64) + if err != nil { + return nil, err + } + + if i >= uint64(len(s)) { + return nil, fmt.Errorf("index out of range: %d", i) + } + + ref := s[i] + + if ref == nil || ref.Ref != "" { + return &Ref{Ref: ref.Ref}, nil + } + return ref.Value, nil +} + +// Schema is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object +type Schema struct { + Extensions map[string]any `json:"-" yaml:"-"` + + OneOf SchemaRefs `json:"oneOf,omitempty" yaml:"oneOf,omitempty"` + AnyOf SchemaRefs `json:"anyOf,omitempty" yaml:"anyOf,omitempty"` + AllOf SchemaRefs `json:"allOf,omitempty" yaml:"allOf,omitempty"` + Not *SchemaRef `json:"not,omitempty" yaml:"not,omitempty"` + Type *Types `json:"type,omitempty" yaml:"type,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Format string `json:"format,omitempty" yaml:"format,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` + Default any `json:"default,omitempty" yaml:"default,omitempty"` + Example any `json:"example,omitempty" yaml:"example,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + + // Array-related, here for struct compactness + UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` + // Number-related, here for struct compactness + ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` + ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` + // Properties + Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"` + ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` + WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` + AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"` + + // Number + Min *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"` + Max *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"` + MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` + + // String + MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"` + MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` + Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` + + // Array + MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"` + MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"` + Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"` + + // Object + Required []string `json:"required,omitempty" yaml:"required,omitempty"` + Properties Schemas `json:"properties,omitempty" yaml:"properties,omitempty"` + MinProps uint64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` + MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` + AdditionalProperties AdditionalProperties `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` + Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"` +} + +type Types []string + +func (types *Types) Is(typ string) bool { + return types != nil && len(*types) == 1 && (*types)[0] == typ +} + +func (types *Types) Slice() []string { + if types == nil { + return nil + } + return *types +} + +func (pTypes *Types) Includes(typ string) bool { + if pTypes == nil { + return false + } + types := *pTypes + for _, candidate := range types { + if candidate == typ { + return true + } + } + return false +} + +func (types *Types) Permits(typ string) bool { + if types == nil { + return true + } + return types.Includes(typ) +} + +func (pTypes *Types) MarshalJSON() ([]byte, error) { + x, err := pTypes.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +func (pTypes *Types) MarshalYAML() (any, error) { + if pTypes == nil { + return nil, nil + } + types := *pTypes + switch len(types) { + case 0: + return nil, nil + case 1: + return types[0], nil + default: + return []string(types), nil + } +} + +func (types *Types) UnmarshalJSON(data []byte) error { + var strings []string + if err := json.Unmarshal(data, &strings); err != nil { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return unmarshalError(err) + } + strings = []string{s} + } + *types = strings + return nil +} + +type AdditionalProperties struct { + Has *bool + Schema *SchemaRef +} + +// MarshalYAML returns the YAML encoding of AdditionalProperties. +func (addProps AdditionalProperties) MarshalYAML() (any, error) { + if x := addProps.Has; x != nil { + if *x { + return true, nil + } + return false, nil + } + if x := addProps.Schema; x != nil { + return x.MarshalYAML() + } + return nil, nil +} + +// MarshalJSON returns the JSON encoding of AdditionalProperties. +func (addProps AdditionalProperties) MarshalJSON() ([]byte, error) { + x, err := addProps.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// UnmarshalJSON sets AdditionalProperties to a copy of data. +func (addProps *AdditionalProperties) UnmarshalJSON(data []byte) error { + var x any + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + switch y := x.(type) { + case nil: + case bool: + addProps.Has = &y + case map[string]any: + if len(y) == 0 { + addProps.Schema = &SchemaRef{Value: &Schema{}} + } else { + buf := new(bytes.Buffer) + json.NewEncoder(buf).Encode(y) + if err := json.NewDecoder(buf).Decode(&addProps.Schema); err != nil { + return err + } + } + default: + return errors.New("cannot unmarshal additionalProperties: value must be either a schema object or a boolean") + } + return nil +} + +var _ jsonpointer.JSONPointable = (*Schema)(nil) + +func NewSchema() *Schema { + return &Schema{} +} + +// MarshalJSON returns the JSON encoding of Schema. +func (schema Schema) MarshalJSON() ([]byte, error) { + m, err := schema.MarshalYAML() + if err != nil { + return nil, err + } + + return json.Marshal(m) +} + +// MarshalYAML returns the YAML encoding of Schema. +func (schema Schema) MarshalYAML() (any, error) { + m := make(map[string]any, 36+len(schema.Extensions)) + for k, v := range schema.Extensions { + m[k] = v + } + + if x := schema.OneOf; len(x) != 0 { + m["oneOf"] = x + } + if x := schema.AnyOf; len(x) != 0 { + m["anyOf"] = x + } + if x := schema.AllOf; len(x) != 0 { + m["allOf"] = x + } + if x := schema.Not; x != nil { + m["not"] = x + } + if x := schema.Type; x != nil { + m["type"] = x + } + if x := schema.Title; len(x) != 0 { + m["title"] = x + } + if x := schema.Format; len(x) != 0 { + m["format"] = x + } + if x := schema.Description; len(x) != 0 { + m["description"] = x + } + if x := schema.Enum; len(x) != 0 { + m["enum"] = x + } + if x := schema.Default; x != nil { + m["default"] = x + } + if x := schema.Example; x != nil { + m["example"] = x + } + if x := schema.ExternalDocs; x != nil { + m["externalDocs"] = x + } + + // Array-related + if x := schema.UniqueItems; x { + m["uniqueItems"] = x + } + // Number-related + if x := schema.ExclusiveMin; x { + m["exclusiveMinimum"] = x + } + if x := schema.ExclusiveMax; x { + m["exclusiveMaximum"] = x + } + // Properties + if x := schema.Nullable; x { + m["nullable"] = x + } + if x := schema.ReadOnly; x { + m["readOnly"] = x + } + if x := schema.WriteOnly; x { + m["writeOnly"] = x + } + if x := schema.AllowEmptyValue; x { + m["allowEmptyValue"] = x + } + if x := schema.Deprecated; x { + m["deprecated"] = x + } + if x := schema.XML; x != nil { + m["xml"] = x + } + + // Number + if x := schema.Min; x != nil { + m["minimum"] = x + } + if x := schema.Max; x != nil { + m["maximum"] = x + } + if x := schema.MultipleOf; x != nil { + m["multipleOf"] = x + } + + // String + if x := schema.MinLength; x != 0 { + m["minLength"] = x + } + if x := schema.MaxLength; x != nil { + m["maxLength"] = x + } + if x := schema.Pattern; x != "" { + m["pattern"] = x + } + + // Array + if x := schema.MinItems; x != 0 { + m["minItems"] = x + } + if x := schema.MaxItems; x != nil { + m["maxItems"] = x + } + if x := schema.Items; x != nil { + m["items"] = x + } + + // Object + if x := schema.Required; len(x) != 0 { + m["required"] = x + } + if x := schema.Properties; len(x) != 0 { + m["properties"] = x + } + if x := schema.MinProps; x != 0 { + m["minProperties"] = x + } + if x := schema.MaxProps; x != nil { + m["maxProperties"] = x + } + if x := schema.AdditionalProperties; x.Has != nil || x.Schema != nil { + m["additionalProperties"] = &x + } + if x := schema.Discriminator; x != nil { + m["discriminator"] = x + } + + return m, nil +} + +// UnmarshalJSON sets Schema to a copy of data. +func (schema *Schema) UnmarshalJSON(data []byte) error { + type SchemaBis Schema + var x SchemaBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + + delete(x.Extensions, "oneOf") + delete(x.Extensions, "anyOf") + delete(x.Extensions, "allOf") + delete(x.Extensions, "not") + delete(x.Extensions, "type") + delete(x.Extensions, "title") + delete(x.Extensions, "format") + delete(x.Extensions, "description") + delete(x.Extensions, "enum") + delete(x.Extensions, "default") + delete(x.Extensions, "example") + delete(x.Extensions, "externalDocs") + + // Array-related + delete(x.Extensions, "uniqueItems") + // Number-related + delete(x.Extensions, "exclusiveMinimum") + delete(x.Extensions, "exclusiveMaximum") + // Properties + delete(x.Extensions, "nullable") + delete(x.Extensions, "readOnly") + delete(x.Extensions, "writeOnly") + delete(x.Extensions, "allowEmptyValue") + delete(x.Extensions, "deprecated") + delete(x.Extensions, "xml") + + // Number + delete(x.Extensions, "minimum") + delete(x.Extensions, "maximum") + delete(x.Extensions, "multipleOf") + + // String + delete(x.Extensions, "minLength") + delete(x.Extensions, "maxLength") + delete(x.Extensions, "pattern") + + // Array + delete(x.Extensions, "minItems") + delete(x.Extensions, "maxItems") + delete(x.Extensions, "items") + + // Object + delete(x.Extensions, "required") + delete(x.Extensions, "properties") + delete(x.Extensions, "minProperties") + delete(x.Extensions, "maxProperties") + delete(x.Extensions, "additionalProperties") + delete(x.Extensions, "discriminator") + + if len(x.Extensions) == 0 { + x.Extensions = nil + } + + *schema = Schema(x) + + if schema.Format == "date" { + // This is a fix for: https://github.com/getkin/kin-openapi/issues/697 + if eg, ok := schema.Example.(string); ok { + schema.Example = strings.TrimSuffix(eg, "T00:00:00Z") + } + } + return nil +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (schema Schema) JSONLookup(token string) (any, error) { + switch token { + case "additionalProperties": + if addProps := schema.AdditionalProperties.Has; addProps != nil { + return *addProps, nil + } + if addProps := schema.AdditionalProperties.Schema; addProps != nil { + if addProps.Ref != "" { + return &Ref{Ref: addProps.Ref}, nil + } + return addProps.Value, nil + } + case "not": + if schema.Not != nil { + if schema.Not.Ref != "" { + return &Ref{Ref: schema.Not.Ref}, nil + } + return schema.Not.Value, nil + } + case "items": + if schema.Items != nil { + if schema.Items.Ref != "" { + return &Ref{Ref: schema.Items.Ref}, nil + } + return schema.Items.Value, nil + } + case "oneOf": + return schema.OneOf, nil + case "anyOf": + return schema.AnyOf, nil + case "allOf": + return schema.AllOf, nil + case "type": + return schema.Type, nil + case "title": + return schema.Title, nil + case "format": + return schema.Format, nil + case "description": + return schema.Description, nil + case "enum": + return schema.Enum, nil + case "default": + return schema.Default, nil + case "example": + return schema.Example, nil + case "externalDocs": + return schema.ExternalDocs, nil + case "uniqueItems": + return schema.UniqueItems, nil + case "exclusiveMin": + return schema.ExclusiveMin, nil + case "exclusiveMax": + return schema.ExclusiveMax, nil + case "nullable": + return schema.Nullable, nil + case "readOnly": + return schema.ReadOnly, nil + case "writeOnly": + return schema.WriteOnly, nil + case "allowEmptyValue": + return schema.AllowEmptyValue, nil + case "xml": + return schema.XML, nil + case "deprecated": + return schema.Deprecated, nil + case "min": + return schema.Min, nil + case "max": + return schema.Max, nil + case "multipleOf": + return schema.MultipleOf, nil + case "minLength": + return schema.MinLength, nil + case "maxLength": + return schema.MaxLength, nil + case "pattern": + return schema.Pattern, nil + case "minItems": + return schema.MinItems, nil + case "maxItems": + return schema.MaxItems, nil + case "required": + return schema.Required, nil + case "properties": + return schema.Properties, nil + case "minProps": + return schema.MinProps, nil + case "maxProps": + return schema.MaxProps, nil + case "discriminator": + return schema.Discriminator, nil + } + + v, _, err := jsonpointer.GetForToken(schema.Extensions, token) + return v, err +} + +func (schema *Schema) NewRef() *SchemaRef { + return &SchemaRef{ + Value: schema, + } +} + +func NewOneOfSchema(schemas ...*Schema) *Schema { + refs := make([]*SchemaRef, 0, len(schemas)) + for _, schema := range schemas { + refs = append(refs, &SchemaRef{Value: schema}) + } + return &Schema{ + OneOf: refs, + } +} + +func NewAnyOfSchema(schemas ...*Schema) *Schema { + refs := make([]*SchemaRef, 0, len(schemas)) + for _, schema := range schemas { + refs = append(refs, &SchemaRef{Value: schema}) + } + return &Schema{ + AnyOf: refs, + } +} + +func NewAllOfSchema(schemas ...*Schema) *Schema { + refs := make([]*SchemaRef, 0, len(schemas)) + for _, schema := range schemas { + refs = append(refs, &SchemaRef{Value: schema}) + } + return &Schema{ + AllOf: refs, + } +} + +func NewBoolSchema() *Schema { + return &Schema{ + Type: &Types{TypeBoolean}, + } +} + +func NewFloat64Schema() *Schema { + return &Schema{ + Type: &Types{TypeNumber}, + } +} + +func NewIntegerSchema() *Schema { + return &Schema{ + Type: &Types{TypeInteger}, + } +} + +func NewInt32Schema() *Schema { + return &Schema{ + Type: &Types{TypeInteger}, + Format: "int32", + } +} + +func NewInt64Schema() *Schema { + return &Schema{ + Type: &Types{TypeInteger}, + Format: "int64", + } +} + +func NewStringSchema() *Schema { + return &Schema{ + Type: &Types{TypeString}, + } +} + +func NewDateTimeSchema() *Schema { + return &Schema{ + Type: &Types{TypeString}, + Format: "date-time", + } +} + +func NewUUIDSchema() *Schema { + return &Schema{ + Type: &Types{TypeString}, + Format: "uuid", + } +} + +func NewBytesSchema() *Schema { + return &Schema{ + Type: &Types{TypeString}, + Format: "byte", + } +} + +func NewArraySchema() *Schema { + return &Schema{ + Type: &Types{TypeArray}, + } +} + +func NewObjectSchema() *Schema { + return &Schema{ + Type: &Types{TypeObject}, + Properties: make(Schemas), + } +} + +func (schema *Schema) WithNullable() *Schema { + schema.Nullable = true + return schema +} + +func (schema *Schema) WithMin(value float64) *Schema { + schema.Min = &value + return schema +} + +func (schema *Schema) WithMax(value float64) *Schema { + schema.Max = &value + return schema +} + +func (schema *Schema) WithExclusiveMin(value bool) *Schema { + schema.ExclusiveMin = value + return schema +} + +func (schema *Schema) WithExclusiveMax(value bool) *Schema { + schema.ExclusiveMax = value + return schema +} + +func (schema *Schema) WithEnum(values ...any) *Schema { + schema.Enum = values + return schema +} + +func (schema *Schema) WithDefault(defaultValue any) *Schema { + schema.Default = defaultValue + return schema +} + +func (schema *Schema) WithFormat(value string) *Schema { + schema.Format = value + return schema +} + +func (schema *Schema) WithLength(i int64) *Schema { + n := uint64(i) + schema.MinLength = n + schema.MaxLength = &n + return schema +} + +func (schema *Schema) WithMinLength(i int64) *Schema { + n := uint64(i) + schema.MinLength = n + return schema +} + +func (schema *Schema) WithMaxLength(i int64) *Schema { + n := uint64(i) + schema.MaxLength = &n + return schema +} + +func (schema *Schema) WithLengthDecodedBase64(i int64) *Schema { + n := uint64(i) + v := (n*8 + 5) / 6 + schema.MinLength = v + schema.MaxLength = &v + return schema +} + +func (schema *Schema) WithMinLengthDecodedBase64(i int64) *Schema { + n := uint64(i) + schema.MinLength = (n*8 + 5) / 6 + return schema +} + +func (schema *Schema) WithMaxLengthDecodedBase64(i int64) *Schema { + n := uint64(i) + schema.MinLength = (n*8 + 5) / 6 + return schema +} + +func (schema *Schema) WithPattern(pattern string) *Schema { + schema.Pattern = pattern + return schema +} + +func (schema *Schema) WithItems(value *Schema) *Schema { + schema.Items = &SchemaRef{ + Value: value, + } + return schema +} + +func (schema *Schema) WithMinItems(i int64) *Schema { + n := uint64(i) + schema.MinItems = n + return schema +} + +func (schema *Schema) WithMaxItems(i int64) *Schema { + n := uint64(i) + schema.MaxItems = &n + return schema +} + +func (schema *Schema) WithUniqueItems(unique bool) *Schema { + schema.UniqueItems = unique + return schema +} + +func (schema *Schema) WithProperty(name string, propertySchema *Schema) *Schema { + return schema.WithPropertyRef(name, &SchemaRef{ + Value: propertySchema, + }) +} + +func (schema *Schema) WithPropertyRef(name string, ref *SchemaRef) *Schema { + properties := schema.Properties + if properties == nil { + properties = make(Schemas) + schema.Properties = properties + } + properties[name] = ref + return schema +} + +func (schema *Schema) WithProperties(properties map[string]*Schema) *Schema { + result := make(Schemas, len(properties)) + for k, v := range properties { + result[k] = &SchemaRef{ + Value: v, + } + } + schema.Properties = result + return schema +} + +func (schema *Schema) WithRequired(required []string) *Schema { + schema.Required = required + return schema +} + +func (schema *Schema) WithMinProperties(i int64) *Schema { + n := uint64(i) + schema.MinProps = n + return schema +} + +func (schema *Schema) WithMaxProperties(i int64) *Schema { + n := uint64(i) + schema.MaxProps = &n + return schema +} + +func (schema *Schema) WithAnyAdditionalProperties() *Schema { + schema.AdditionalProperties = AdditionalProperties{Has: BoolPtr(true)} + return schema +} + +func (schema *Schema) WithoutAdditionalProperties() *Schema { + schema.AdditionalProperties = AdditionalProperties{Has: BoolPtr(false)} + return schema +} + +func (schema *Schema) WithAdditionalProperties(v *Schema) *Schema { + schema.AdditionalProperties = AdditionalProperties{} + if v != nil { + schema.AdditionalProperties.Schema = &SchemaRef{Value: v} + } + return schema +} + +func (schema *Schema) PermitsNull() bool { + return schema.Nullable || schema.Type.Includes("null") +} + +// IsEmpty tells whether schema is equivalent to the empty schema `{}`. +func (schema *Schema) IsEmpty() bool { + if schema.Type != nil || schema.Format != "" || len(schema.Enum) != 0 || + schema.UniqueItems || schema.ExclusiveMin || schema.ExclusiveMax || + schema.Nullable || schema.ReadOnly || schema.WriteOnly || schema.AllowEmptyValue || + schema.Min != nil || schema.Max != nil || schema.MultipleOf != nil || + schema.MinLength != 0 || schema.MaxLength != nil || schema.Pattern != "" || + schema.MinItems != 0 || schema.MaxItems != nil || + len(schema.Required) != 0 || + schema.MinProps != 0 || schema.MaxProps != nil { + return false + } + if n := schema.Not; n != nil && n.Value != nil && !n.Value.IsEmpty() { + return false + } + if ap := schema.AdditionalProperties.Schema; ap != nil && ap.Value != nil && !ap.Value.IsEmpty() { + return false + } + if apa := schema.AdditionalProperties.Has; apa != nil && !*apa { + return false + } + if items := schema.Items; items != nil && items.Value != nil && !items.Value.IsEmpty() { + return false + } + for _, s := range schema.Properties { + if ss := s.Value; ss != nil && !ss.IsEmpty() { + return false + } + } + for _, s := range schema.OneOf { + if ss := s.Value; ss != nil && !ss.IsEmpty() { + return false + } + } + for _, s := range schema.AnyOf { + if ss := s.Value; ss != nil && !ss.IsEmpty() { + return false + } + } + for _, s := range schema.AllOf { + if ss := s.Value; ss != nil && !ss.IsEmpty() { + return false + } + } + return true +} + +// Validate returns an error if Schema does not comply with the OpenAPI spec. +func (schema *Schema) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + _, err := schema.validate(ctx, []*Schema{}) + return err +} + +// returns the updated stack and an error if Schema does not comply with the OpenAPI spec. +func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, error) { + validationOpts := getValidationOptions(ctx) + + for _, existing := range stack { + if existing == schema { + return stack, nil + } + } + stack = append(stack, schema) + + if schema.ReadOnly && schema.WriteOnly { + return stack, errors.New("a property MUST NOT be marked as both readOnly and writeOnly being true") + } + + for _, item := range schema.OneOf { + v := item.Value + if v == nil { + return stack, foundUnresolvedRef(item.Ref) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + + for _, item := range schema.AnyOf { + v := item.Value + if v == nil { + return stack, foundUnresolvedRef(item.Ref) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + + for _, item := range schema.AllOf { + v := item.Value + if v == nil { + return stack, foundUnresolvedRef(item.Ref) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + + if ref := schema.Not; ref != nil { + v := ref.Value + if v == nil { + return stack, foundUnresolvedRef(ref.Ref) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + + for _, schemaType := range schema.Type.Slice() { + switch schemaType { + case TypeBoolean: + case TypeNumber: + if format := schema.Format; len(format) > 0 { + switch format { + case "float", "double": + default: + if _, ok := SchemaNumberFormats[format]; !ok && validationOpts.schemaFormatValidationEnabled { + return stack, unsupportedFormat(format) + } + } + } + case TypeInteger: + if format := schema.Format; len(format) > 0 { + switch format { + case "int32", "int64": + default: + if _, ok := SchemaIntegerFormats[format]; !ok && validationOpts.schemaFormatValidationEnabled { + return stack, unsupportedFormat(format) + } + } + } + case TypeString: + if format := schema.Format; len(format) > 0 { + switch format { + // Supported by OpenAPIv3.0.3: + // https://spec.openapis.org/oas/v3.0.3 + case "byte", "binary", "date", "date-time", "password": + // In JSON Draft-07 (not validated yet though): + // https://json-schema.org/draft-07/json-schema-release-notes.html#formats + case "iri", "iri-reference", "uri-template", "idn-email", "idn-hostname": + case "json-pointer", "relative-json-pointer", "regex", "time": + // In JSON Draft 2019-09 (not validated yet though): + // https://json-schema.org/draft/2019-09/release-notes.html#format-vocabulary + case "duration", "uuid": + // Defined in some other specification + case "email", "hostname", "ipv4", "ipv6", "uri", "uri-reference": + default: + if _, ok := SchemaStringFormats[format]; !ok && validationOpts.schemaFormatValidationEnabled { + return stack, unsupportedFormat(format) + } + } + } + if !validationOpts.schemaPatternValidationDisabled && schema.Pattern != "" { + if _, err := schema.compilePattern(validationOpts.regexCompilerFunc); err != nil { + return stack, err + } + } + case TypeArray: + if schema.Items == nil { + return stack, errors.New("when schema type is 'array', schema 'items' must be non-null") + } + case TypeObject: + default: + return stack, fmt.Errorf("unsupported 'type' value %q", schemaType) + } + } + + if ref := schema.Items; ref != nil { + v := ref.Value + if v == nil { + return stack, foundUnresolvedRef(ref.Ref) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + + properties := make([]string, 0, len(schema.Properties)) + for name := range schema.Properties { + properties = append(properties, name) + } + sort.Strings(properties) + for _, name := range properties { + ref := schema.Properties[name] + v := ref.Value + if v == nil { + return stack, foundUnresolvedRef(ref.Ref) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + + if schema.AdditionalProperties.Has != nil && schema.AdditionalProperties.Schema != nil { + return stack, errors.New("additionalProperties are set to both boolean and schema") + } + if ref := schema.AdditionalProperties.Schema; ref != nil { + v := ref.Value + if v == nil { + return stack, foundUnresolvedRef(ref.Ref) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + + if v := schema.ExternalDocs; v != nil { + if err := v.Validate(ctx); err != nil { + return stack, fmt.Errorf("invalid external docs: %w", err) + } + } + + if v := schema.Default; v != nil && !validationOpts.schemaDefaultsValidationDisabled { + if err := schema.VisitJSON(v); err != nil { + return stack, fmt.Errorf("invalid default: %w", err) + } + } + + if x := schema.Example; x != nil && !validationOpts.examplesValidationDisabled { + if err := validateExampleValue(ctx, x, schema); err != nil { + return stack, fmt.Errorf("invalid example: %w", err) + } + } + + return stack, validateExtensions(ctx, schema.Extensions) +} + +func (schema *Schema) IsMatching(value any) bool { + settings := newSchemaValidationSettings(FailFast()) + return schema.visitJSON(settings, value) == nil +} + +func (schema *Schema) IsMatchingJSONBoolean(value bool) bool { + settings := newSchemaValidationSettings(FailFast()) + return schema.visitJSON(settings, value) == nil +} + +func (schema *Schema) IsMatchingJSONNumber(value float64) bool { + settings := newSchemaValidationSettings(FailFast()) + return schema.visitJSON(settings, value) == nil +} + +func (schema *Schema) IsMatchingJSONString(value string) bool { + settings := newSchemaValidationSettings(FailFast()) + return schema.visitJSON(settings, value) == nil +} + +func (schema *Schema) IsMatchingJSONArray(value []any) bool { + settings := newSchemaValidationSettings(FailFast()) + return schema.visitJSON(settings, value) == nil +} + +func (schema *Schema) IsMatchingJSONObject(value map[string]any) bool { + settings := newSchemaValidationSettings(FailFast()) + return schema.visitJSON(settings, value) == nil +} + +func (schema *Schema) VisitJSON(value any, opts ...SchemaValidationOption) error { + settings := newSchemaValidationSettings(opts...) + return schema.visitJSON(settings, value) +} + +func (schema *Schema) visitJSON(settings *schemaValidationSettings, value any) (err error) { + switch value := value.(type) { + case nil: + // Don't use VisitJSONNull, as we still want to reach 'visitXOFOperations', since + // those could allow for a nullable value even though this one doesn't + if schema.PermitsNull() { + return + } + case float64: + if math.IsNaN(value) { + return ErrSchemaInputNaN + } + if math.IsInf(value, 0) { + return ErrSchemaInputInf + } + } + + if schema.IsEmpty() { + switch value.(type) { + case nil: + return schema.visitJSONNull(settings) + default: + return + } + } + + if err = schema.visitNotOperation(settings, value); err != nil { + return + } + var run bool + if err, run = schema.visitXOFOperations(settings, value); err != nil || !run { + return + } + if err = schema.visitEnumOperation(settings, value); err != nil { + return + } + + switch value := value.(type) { + case nil: + return schema.visitJSONNull(settings) + case bool: + return schema.visitJSONBoolean(settings, value) + case json.Number: + valueFloat64, err := value.Float64() + if err != nil { + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "type", + Reason: "cannot convert json.Number to float64", + customizeMessageError: settings.customizeMessageError, + Origin: err, + } + } + return schema.visitJSONNumber(settings, valueFloat64) + case int: + return schema.visitJSONNumber(settings, float64(value)) + case int32: + return schema.visitJSONNumber(settings, float64(value)) + case int64: + return schema.visitJSONNumber(settings, float64(value)) + case float64: + return schema.visitJSONNumber(settings, value) + case string: + return schema.visitJSONString(settings, value) + case []any: + return schema.visitJSONArray(settings, value) + case map[string]any: + return schema.visitJSONObject(settings, value) + case map[any]any: // for YAML cf. issue https://github.com/getkin/kin-openapi/issues/444 + values := make(map[string]any, len(value)) + for key, v := range value { + if k, ok := key.(string); ok { + values[k] = v + } + } + if len(value) == len(values) { + return schema.visitJSONObject(settings, values) + } + } + + // Catch slice of non-empty interface type + if reflect.TypeOf(value).Kind() == reflect.Slice { + valueR := reflect.ValueOf(value) + newValue := make([]any, 0, valueR.Len()) + for i := 0; i < valueR.Len(); i++ { + newValue = append(newValue, valueR.Index(i).Interface()) + } + return schema.visitJSONArray(settings, newValue) + } + + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "type", + Reason: fmt.Sprintf("unhandled value of type %T", value), + customizeMessageError: settings.customizeMessageError, + } +} + +func (schema *Schema) visitEnumOperation(settings *schemaValidationSettings, value any) (err error) { + if enum := schema.Enum; len(enum) != 0 { + for _, v := range enum { + switch c := value.(type) { + case json.Number: + var f float64 + if f, err = strconv.ParseFloat(c.String(), 64); err != nil { + return err + } + if v == f { + return + } + case int64: + if v == float64(c) { + return + } + default: + if reflect.DeepEqual(v, value) { + return + } + } + } + if settings.failfast { + return errSchema + } + allowedValues, _ := json.Marshal(enum) + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "enum", + Reason: fmt.Sprintf("value is not one of the allowed values %s", string(allowedValues)), + customizeMessageError: settings.customizeMessageError, + } + } + return +} + +func (schema *Schema) visitNotOperation(settings *schemaValidationSettings, value any) (err error) { + if ref := schema.Not; ref != nil { + v := ref.Value + if v == nil { + return foundUnresolvedRef(ref.Ref) + } + if err := v.visitJSON(settings, value); err == nil { + if settings.failfast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "not", + customizeMessageError: settings.customizeMessageError, + } + } + } + return +} + +// If the XOF operations pass successfully, abort further run of validation, as they will already be satisfied (unless the schema +// itself is badly specified +func (schema *Schema) visitXOFOperations(settings *schemaValidationSettings, value any) (err error, run bool) { + var visitedOneOf, visitedAnyOf, visitedAllOf bool + if v := schema.OneOf; len(v) > 0 { + var discriminatorRef string + if schema.Discriminator != nil { + pn := schema.Discriminator.PropertyName + if valuemap, okcheck := value.(map[string]any); okcheck { + discriminatorVal, okcheck := valuemap[pn] + if !okcheck { + return &SchemaError{ + Schema: schema, + SchemaField: "discriminator", + Reason: fmt.Sprintf("input does not contain the discriminator property %q", pn), + }, false + } + + discriminatorValString, okcheck := discriminatorVal.(string) + if !okcheck { + return &SchemaError{ + Value: discriminatorVal, + Schema: schema, + SchemaField: "discriminator", + Reason: fmt.Sprintf("value of discriminator property %q is not a string", pn), + }, false + } + + if discriminatorRef, okcheck = schema.Discriminator.Mapping[discriminatorValString]; len(schema.Discriminator.Mapping) > 0 && !okcheck { + return &SchemaError{ + Value: discriminatorVal, + Schema: schema, + SchemaField: "discriminator", + Reason: fmt.Sprintf("discriminator property %q has invalid value", pn), + }, false + } + } + } + + var ( + ok = 0 + validationErrors = multiErrorForOneOf{} + matchedOneOfIndices = make([]int, 0) + tempValue = value + ) + for idx, item := range v { + v := item.Value + if v == nil { + return foundUnresolvedRef(item.Ref), false + } + + if discriminatorRef != "" && discriminatorRef != item.Ref { + continue + } + + // make a deep copy to protect origin value from being injected default value that defined in mismatched oneOf schema + if settings.asreq || settings.asrep { + tempValue = deepcopy.Copy(value) + } + + if err := v.visitJSON(settings, tempValue); err != nil { + validationErrors = append(validationErrors, err) + continue + } + + matchedOneOfIndices = append(matchedOneOfIndices, idx) + ok++ + } + + if ok != 1 { + if settings.failfast { + return errSchema, false + } + e := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "oneOf", + customizeMessageError: settings.customizeMessageError, + } + if ok > 1 { + e.Origin = ErrOneOfConflict + e.Reason = fmt.Sprintf(`value matches more than one schema from "oneOf" (matches schemas at indices %v)`, matchedOneOfIndices) + } else { + e.Origin = fmt.Errorf("doesn't match schema due to: %w", validationErrors) + e.Reason = `value doesn't match any schema from "oneOf"` + } + + return e, false + } + + // run again to inject default value that defined in matched oneOf schema + if settings.asreq || settings.asrep { + _ = v[matchedOneOfIndices[0]].Value.visitJSON(settings, value) + } + visitedOneOf = true + } + + if v := schema.AnyOf; len(v) > 0 { + var ( + ok = false + matchedAnyOfIdx = 0 + tempValue = value + ) + for idx, item := range v { + v := item.Value + if v == nil { + return foundUnresolvedRef(item.Ref), false + } + // make a deep copy to protect origin value from being injected default value that defined in mismatched anyOf schema + if settings.asreq || settings.asrep { + tempValue = deepcopy.Copy(value) + } + if err := v.visitJSON(settings, tempValue); err == nil { + ok = true + matchedAnyOfIdx = idx + break + } + } + if !ok { + if settings.failfast { + return errSchema, false + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "anyOf", + Reason: `doesn't match any schema from "anyOf"`, + customizeMessageError: settings.customizeMessageError, + }, false + } + + _ = v[matchedAnyOfIdx].Value.visitJSON(settings, value) + visitedAnyOf = true + } + + for _, item := range schema.AllOf { + v := item.Value + if v == nil { + return foundUnresolvedRef(item.Ref), false + } + if err := v.visitJSON(settings, value); err != nil { + if settings.failfast { + return errSchema, false + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "allOf", + Reason: `doesn't match all schemas from "allOf"`, + Origin: err, + customizeMessageError: settings.customizeMessageError, + }, false + } + visitedAllOf = true + } + + run = !((visitedOneOf || visitedAnyOf || visitedAllOf) && value == nil) + return +} + +// The value is not considered in visitJSONNull because according to the spec +// "null is not supported as a type" unless `nullable` is also set to true +// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#data-types +// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object +func (schema *Schema) visitJSONNull(settings *schemaValidationSettings) (err error) { + if schema.PermitsNull() { + return + } + if settings.failfast { + return errSchema + } + return &SchemaError{ + Value: nil, + Schema: schema, + SchemaField: "nullable", + Reason: "Value is not nullable", + customizeMessageError: settings.customizeMessageError, + } +} + +func (schema *Schema) VisitJSONBoolean(value bool) error { + settings := newSchemaValidationSettings() + return schema.visitJSONBoolean(settings, value) +} + +func (schema *Schema) visitJSONBoolean(settings *schemaValidationSettings, value bool) (err error) { + if !schema.Type.Permits(TypeBoolean) { + return schema.expectedType(settings, value) + } + return +} + +func (schema *Schema) VisitJSONNumber(value float64) error { + settings := newSchemaValidationSettings() + return schema.visitJSONNumber(settings, value) +} + +func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value float64) error { + var me MultiError + schemaType := schema.Type + requireInteger := false + if schemaType.Permits(TypeInteger) && !schemaType.Permits(TypeNumber) { + requireInteger = true + if bigFloat := big.NewFloat(value); !bigFloat.IsInt() { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "type", + Reason: "value must be an integer", + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + } else if !(schemaType.Permits(TypeInteger) || schemaType.Permits(TypeNumber)) { + return schema.expectedType(settings, value) + } + + // formats + var formatStrErr string + var formatErr error + format := schema.Format + if format != "" { + if requireInteger { + if f, ok := SchemaIntegerFormats[format]; ok { + if err := f.Validate(int64(value)); err != nil { + var reason string + schemaErr := &SchemaError{} + if errors.As(err, &schemaErr) { + reason = schemaErr.Reason + } else { + reason = err.Error() + } + formatStrErr = fmt.Sprintf(`integer doesn't match the format %q (%v)`, format, reason) + formatErr = fmt.Errorf("integer doesn't match the format %q: %w", format, err) + } + } + } else { + if f, ok := SchemaNumberFormats[format]; ok { + if err := f.Validate(value); err != nil { + var reason string + schemaErr := &SchemaError{} + if errors.As(err, &schemaErr) { + reason = schemaErr.Reason + } else { + reason = err.Error() + } + formatStrErr = fmt.Sprintf(`number doesn't match the format %q (%v)`, format, reason) + formatErr = fmt.Errorf("number doesn't match the format %q: %w", format, err) + } + } + } + } + + if formatStrErr != "" || formatErr != nil { + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "format", + Reason: formatStrErr, + Origin: formatErr, + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "exclusiveMinimum" + if v := schema.ExclusiveMin; v && !(*schema.Min < value) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "exclusiveMinimum", + Reason: fmt.Sprintf("number must be more than %g", *schema.Min), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "exclusiveMaximum" + if v := schema.ExclusiveMax; v && !(*schema.Max > value) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "exclusiveMaximum", + Reason: fmt.Sprintf("number must be less than %g", *schema.Max), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "minimum" + if v := schema.Min; v != nil && !(*v <= value) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "minimum", + Reason: fmt.Sprintf("number must be at least %g", *v), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "maximum" + if v := schema.Max; v != nil && !(*v >= value) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "maximum", + Reason: fmt.Sprintf("number must be at most %g", *v), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "multipleOf" + if v := schema.MultipleOf; v != nil { + // "A numeric instance is valid only if division by this keyword's + // value results in an integer." + if bigFloat := big.NewFloat(value / *v); !bigFloat.IsInt() { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "multipleOf", + Reason: fmt.Sprintf("number must be a multiple of %g", *v), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + } + + if len(me) > 0 { + return me + } + + return nil +} + +func (schema *Schema) VisitJSONString(value string) error { + settings := newSchemaValidationSettings() + return schema.visitJSONString(settings, value) +} + +func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value string) error { + if !schema.Type.Permits(TypeString) { + return schema.expectedType(settings, value) + } + + var me MultiError + + // "minLength" and "maxLength" + minLength := schema.MinLength + maxLength := schema.MaxLength + if minLength != 0 || maxLength != nil { + // JSON schema string lengths are UTF-16, not UTF-8! + length := int64(0) + for _, r := range value { + if utf16.IsSurrogate(r) { + length += 2 + } else { + length++ + } + } + if minLength != 0 && length < int64(minLength) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "minLength", + Reason: fmt.Sprintf("minimum string length is %d", minLength), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + if maxLength != nil && length > int64(*maxLength) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "maxLength", + Reason: fmt.Sprintf("maximum string length is %d", *maxLength), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + } + + // "pattern" + if !settings.patternValidationDisabled && schema.Pattern != "" { + cpiface, _ := compiledPatterns.Load(schema.Pattern) + cp, _ := cpiface.(RegexMatcher) + if cp == nil { + var err error + if cp, err = schema.compilePattern(settings.regexCompiler); err != nil { + if !settings.multiError { + return err + } + me = append(me, err) + } + } + if !cp.MatchString(value) { + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "pattern", + Reason: fmt.Sprintf(`string doesn't match the regular expression "%s"`, schema.Pattern), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + } + + // "format" + var formatStrErr string + var formatErr error + if format := schema.Format; format != "" { + if f, ok := SchemaStringFormats[format]; ok { + if err := f.Validate(value); err != nil { + var reason string + schemaErr := &SchemaError{} + if errors.As(err, &schemaErr) { + reason = schemaErr.Reason + } else { + reason = err.Error() + } + formatStrErr = fmt.Sprintf(`string doesn't match the format %q (%v)`, format, reason) + formatErr = fmt.Errorf("string doesn't match the format %q: %w", format, err) + } + } + } + if formatStrErr != "" || formatErr != nil { + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "format", + Reason: formatStrErr, + Origin: formatErr, + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + + } + + if len(me) > 0 { + return me + } + + return nil +} + +func (schema *Schema) VisitJSONArray(value []any) error { + settings := newSchemaValidationSettings() + return schema.visitJSONArray(settings, value) +} + +func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value []any) error { + if !schema.Type.Permits(TypeArray) { + return schema.expectedType(settings, value) + } + + var me MultiError + + lenValue := int64(len(value)) + + // "minItems" + if v := schema.MinItems; v != 0 && lenValue < int64(v) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "minItems", + Reason: fmt.Sprintf("minimum number of items is %d", v), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "maxItems" + if v := schema.MaxItems; v != nil && lenValue > int64(*v) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "maxItems", + Reason: fmt.Sprintf("maximum number of items is %d", *v), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "uniqueItems" + if sliceUniqueItemsChecker == nil { + sliceUniqueItemsChecker = isSliceOfUniqueItems + } + if v := schema.UniqueItems; v && !sliceUniqueItemsChecker(value) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "uniqueItems", + Reason: "duplicate items found", + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "items" + if itemSchemaRef := schema.Items; itemSchemaRef != nil { + itemSchema := itemSchemaRef.Value + if itemSchema == nil { + return foundUnresolvedRef(itemSchemaRef.Ref) + } + for i, item := range value { + if err := itemSchema.visitJSON(settings, item); err != nil { + err = markSchemaErrorIndex(err, i) + if !settings.multiError { + return err + } + if itemMe, ok := err.(MultiError); ok { + me = append(me, itemMe...) + } else { + me = append(me, err) + } + } + } + } + + if len(me) > 0 { + return me + } + + return nil +} + +func (schema *Schema) VisitJSONObject(value map[string]any) error { + settings := newSchemaValidationSettings() + return schema.visitJSONObject(settings, value) +} + +func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value map[string]any) error { + if !schema.Type.Permits(TypeObject) { + return schema.expectedType(settings, value) + } + + var me MultiError + + if settings.asreq || settings.asrep { + properties := make([]string, 0, len(schema.Properties)) + for propName := range schema.Properties { + properties = append(properties, propName) + } + sort.Strings(properties) + for _, propName := range properties { + propSchema := schema.Properties[propName] + reqRO := settings.asreq && propSchema.Value.ReadOnly && !settings.readOnlyValidationDisabled + repWO := settings.asrep && propSchema.Value.WriteOnly && !settings.writeOnlyValidationDisabled + + if f := settings.defaultsSet; f != nil && value[propName] == nil { + if dflt := propSchema.Value.Default; dflt != nil && !reqRO && !repWO { + value[propName] = dflt + settings.onceSettingDefaults.Do(f) + } + } + + if value[propName] != nil { + if reqRO { + me = append(me, fmt.Errorf("readOnly property %q in request", propName)) + } else if repWO { + me = append(me, fmt.Errorf("writeOnly property %q in response", propName)) + } + } + } + } + + // "properties" + properties := schema.Properties + lenValue := int64(len(value)) + + // "minProperties" + if v := schema.MinProps; v != 0 && lenValue < int64(v) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "minProperties", + Reason: fmt.Sprintf("there must be at least %d properties", v), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "maxProperties" + if v := schema.MaxProps; v != nil && lenValue > int64(*v) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "maxProperties", + Reason: fmt.Sprintf("there must be at most %d properties", *v), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "additionalProperties" + var additionalProperties *Schema + if ref := schema.AdditionalProperties.Schema; ref != nil { + additionalProperties = ref.Value + } + keys := make([]string, 0, len(value)) + for k := range value { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + v := value[k] + if properties != nil { + propertyRef := properties[k] + if propertyRef != nil { + p := propertyRef.Value + if p == nil { + return foundUnresolvedRef(propertyRef.Ref) + } + if err := p.visitJSON(settings, v); err != nil { + if settings.failfast { + return errSchema + } + err = markSchemaErrorKey(err, k) + if !settings.multiError { + return err + } + if v, ok := err.(MultiError); ok { + me = append(me, v...) + continue + } + me = append(me, err) + } + continue + } + } + if allowed := schema.AdditionalProperties.Has; allowed == nil || *allowed { + if additionalProperties != nil { + if err := additionalProperties.visitJSON(settings, v); err != nil { + if settings.failfast { + return errSchema + } + err = markSchemaErrorKey(err, k) + if !settings.multiError { + return err + } + if v, ok := err.(MultiError); ok { + me = append(me, v...) + continue + } + me = append(me, err) + } + } + continue + } + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "properties", + Reason: fmt.Sprintf("property %q is unsupported", k), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "required" + for _, k := range schema.Required { + if _, ok := value[k]; !ok { + if s := schema.Properties[k]; s != nil && s.Value.ReadOnly && settings.asreq { + continue + } + if s := schema.Properties[k]; s != nil && s.Value.WriteOnly && settings.asrep { + continue + } + if settings.failfast { + return errSchema + } + err := markSchemaErrorKey(&SchemaError{ + Value: value, + Schema: schema, + SchemaField: "required", + Reason: fmt.Sprintf("property %q is missing", k), + customizeMessageError: settings.customizeMessageError, + }, k) + if !settings.multiError { + return err + } + me = append(me, err) + } + } + + if len(me) > 0 { + return me + } + + return nil +} + +func (schema *Schema) expectedType(settings *schemaValidationSettings, value any) error { + if settings.failfast { + return errSchema + } + + a := "a" + var x string + schemaTypes := (*schema.Type) + if len(schemaTypes) == 1 { + x = schemaTypes[0] + switch x { + case TypeArray, TypeObject, TypeInteger: + a = "an" + } + } else { + a = "one of" + x = strings.Join(schemaTypes, ", ") + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "type", + Reason: fmt.Sprintf("value must be %s %s", a, x), + customizeMessageError: settings.customizeMessageError, + } +} + +// SchemaError is an error that occurs during schema validation. +type SchemaError struct { + // Value is the value that failed validation. + Value any + // reversePath is the path to the value that failed validation. + reversePath []string + // Schema is the schema that failed validation. + Schema *Schema + // SchemaField is the field of the schema that failed validation. + SchemaField string + // Reason is a human-readable message describing the error. + // The message should never include the original value to prevent leakage of potentially sensitive inputs in error messages. + Reason string + // Origin is the original error that caused this error. + Origin error + // customizeMessageError is a function that can be used to customize the error message. + customizeMessageError func(err *SchemaError) string +} + +var _ interface{ Unwrap() error } = SchemaError{} + +func markSchemaErrorKey(err error, key string) error { + + if v, ok := err.(*SchemaError); ok { + v.reversePath = append(v.reversePath, key) + if v.Origin != nil { + if unwrapped := errors.Unwrap(v.Origin); unwrapped != nil { + if me, ok := unwrapped.(multiErrorForOneOf); ok { + _ = markSchemaErrorKey(MultiError(me), key) + } + } + } + return v + } + if v, ok := err.(MultiError); ok { + for _, e := range v { + _ = markSchemaErrorKey(e, key) + } + return v + } + return err +} + +func markSchemaErrorIndex(err error, index int) error { + return markSchemaErrorKey(err, strconv.FormatInt(int64(index), 10)) +} + +func (err *SchemaError) JSONPointer() []string { + reversePath := err.reversePath + path := append([]string(nil), reversePath...) + for left, right := 0, len(path)-1; left < right; left, right = left+1, right-1 { + path[left], path[right] = path[right], path[left] + } + return path +} + +func (err *SchemaError) Error() string { + if err.customizeMessageError != nil { + if msg := err.customizeMessageError(err); msg != "" { + return msg + } + } + + buf := bytes.NewBuffer(make([]byte, 0, 256)) + + if len(err.reversePath) > 0 { + buf.WriteString(`Error at "`) + reversePath := err.reversePath + for i := len(reversePath) - 1; i >= 0; i-- { + buf.WriteByte('/') + buf.WriteString(reversePath[i]) + } + buf.WriteString(`": `) + } + + if err.Origin != nil { + buf.WriteString(err.Origin.Error()) + + return buf.String() + } + + reason := err.Reason + if reason == "" { + buf.WriteString(`Doesn't match schema "`) + buf.WriteString(err.SchemaField) + buf.WriteString(`"`) + } else { + buf.WriteString(reason) + } + + if !SchemaErrorDetailsDisabled { + buf.WriteString("\nSchema:\n ") + encoder := json.NewEncoder(buf) + encoder.SetIndent(" ", " ") + if err := encoder.Encode(err.Schema); err != nil { + panic(err) + } + buf.WriteString("\nValue:\n ") + if err := encoder.Encode(err.Value); err != nil { + panic(err) + } + } + + return buf.String() +} + +func (err SchemaError) Unwrap() error { + return err.Origin +} + +func isSliceOfUniqueItems(xs []any) bool { + s := len(xs) + m := make(map[string]struct{}, s) + for _, x := range xs { + // The input slice is converted from a JSON string, there shall + // have no error when convert it back. + key, _ := json.Marshal(&x) + m[string(key)] = struct{}{} + } + return s == len(m) +} + +// SliceUniqueItemsChecker is an function used to check if an given slice +// have unique items. +type SliceUniqueItemsChecker func(items []any) bool + +// By default using predefined func isSliceOfUniqueItems which make use of +// json.Marshal to generate a key for map used to check if a given slice +// have unique items. +var sliceUniqueItemsChecker SliceUniqueItemsChecker = isSliceOfUniqueItems + +// RegisterArrayUniqueItemsChecker is used to register a customized function +// used to check if JSON array have unique items. +func RegisterArrayUniqueItemsChecker(fn SliceUniqueItemsChecker) { + sliceUniqueItemsChecker = fn +} + +func unsupportedFormat(format string) error { + return fmt.Errorf("unsupported 'format' value %q", format) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go new file mode 100644 index 00000000..023c2669 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go @@ -0,0 +1,169 @@ +package openapi3 + +import ( + "fmt" + "math" + "net/netip" + "regexp" +) + +type ( + // FormatValidator is an interface for custom format validators. + FormatValidator[T any] interface { + Validate(value T) error + } + // StringFormatValidator is a type alias for FormatValidator[string] + StringFormatValidator = FormatValidator[string] + // NumberFormatValidator is a type alias for FormatValidator[float64] + NumberFormatValidator = FormatValidator[float64] + // IntegerFormatValidator is a type alias for FormatValidator[int64] + IntegerFormatValidator = FormatValidator[int64] +) + +var ( + // SchemaStringFormats is a map of custom string format validators. + SchemaStringFormats = make(map[string]StringFormatValidator) + // SchemaNumberFormats is a map of custom number format validators. + SchemaNumberFormats = make(map[string]NumberFormatValidator) + // SchemaIntegerFormats is a map of custom integer format validators. + SchemaIntegerFormats = make(map[string]IntegerFormatValidator) +) + +const ( + // FormatOfStringForUUIDOfRFC4122 is an optional predefined format for UUID v1-v5 as specified by RFC4122 + FormatOfStringForUUIDOfRFC4122 = `^(?:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000)$` + + // FormatOfStringForEmail pattern catches only some suspiciously wrong-looking email addresses. + // Use DefineStringFormat(...) if you need something stricter. + FormatOfStringForEmail = `^[^@]+@[^@<>",\s]+$` + + // FormatOfStringByte is a regexp for base64-encoded characters, for example, "U3dhZ2dlciByb2Nrcw==" + FormatOfStringByte = `(^$|^[a-zA-Z0-9+/\-_]*=*$)` + + // FormatOfStringDate is a RFC3339 date format regexp, for example "2017-07-21". + FormatOfStringDate = `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)$` + + // FormatOfStringDateTime is a RFC3339 date-time format regexp, for example "2017-07-21T17:32:28Z". + FormatOfStringDateTime = `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$` +) + +func init() { + DefineStringFormatValidator("byte", NewRegexpFormatValidator(FormatOfStringByte)) + DefineStringFormatValidator("date", NewRegexpFormatValidator(FormatOfStringDate)) + DefineStringFormatValidator("date-time", NewRegexpFormatValidator(FormatOfStringDateTime)) + DefineIntegerFormatValidator("int32", NewRangeFormatValidator(int64(math.MinInt32), int64(math.MaxInt32))) + DefineIntegerFormatValidator("int64", NewRangeFormatValidator(int64(math.MinInt64), int64(math.MaxInt64))) +} + +// DefineIPv4Format opts in ipv4 format validation on top of OAS 3 spec +func DefineIPv4Format() { + DefineStringFormatValidator("ipv4", NewIPValidator(true)) +} + +// DefineIPv6Format opts in ipv6 format validation on top of OAS 3 spec +func DefineIPv6Format() { + DefineStringFormatValidator("ipv6", NewIPValidator(false)) +} + +type stringRegexpFormatValidator struct { + re *regexp.Regexp +} + +func (s stringRegexpFormatValidator) Validate(value string) error { + if !s.re.MatchString(value) { + return fmt.Errorf(`string doesn't match pattern "%s"`, s.re.String()) + } + return nil +} + +type callbackValidator[T any] struct { + fn func(T) error +} + +func (c callbackValidator[T]) Validate(value T) error { + return c.fn(value) +} + +type rangeFormat[T int64 | float64] struct { + min, max T +} + +func (r rangeFormat[T]) Validate(value T) error { + if value < r.min || value > r.max { + return fmt.Errorf("value should be between %v and %v", r.min, r.max) + } + return nil +} + +// NewRangeFormatValidator creates a new FormatValidator that validates the value is within a given range. +func NewRangeFormatValidator[T int64 | float64](min, max T) FormatValidator[T] { + return rangeFormat[T]{min: min, max: max} +} + +// NewRegexpFormatValidator creates a new FormatValidator that uses a regular expression to validate the value. +func NewRegexpFormatValidator(pattern string) StringFormatValidator { + re, err := regexp.Compile(pattern) + if err != nil { + err := fmt.Errorf("string regexp format has invalid pattern %q: %w", pattern, err) + panic(err) + } + return stringRegexpFormatValidator{re: re} +} + +// NewCallbackValidator creates a new FormatValidator that uses a callback function to validate the value. +func NewCallbackValidator[T any](fn func(T) error) FormatValidator[T] { + return callbackValidator[T]{fn: fn} +} + +// DefineStringFormatValidator defines a custom format validator for a given string format. +func DefineStringFormatValidator(name string, validator StringFormatValidator) { + SchemaStringFormats[name] = validator +} + +// DefineNumberFormatValidator defines a custom format validator for a given number format. +func DefineNumberFormatValidator(name string, validator NumberFormatValidator) { + SchemaNumberFormats[name] = validator +} + +// DefineIntegerFormatValidator defines a custom format validator for a given integer format. +func DefineIntegerFormatValidator(name string, validator IntegerFormatValidator) { + SchemaIntegerFormats[name] = validator +} + +// DefineStringFormat defines a regexp pattern for a given string format +// Deprecated: Use openapi3.DefineStringFormatValidator(name, NewRegexpFormatValidator(pattern)) instead. +func DefineStringFormat(name string, pattern string) { + DefineStringFormatValidator(name, NewRegexpFormatValidator(pattern)) +} + +// DefineStringFormatCallback defines a callback function for a given string format +// Deprecated: Use openapi3.DefineStringFormatValidator(name, NewCallbackValidator(fn)) instead. +func DefineStringFormatCallback(name string, callback func(string) error) { + DefineStringFormatValidator(name, NewCallbackValidator(callback)) +} + +// NewIPValidator creates a new FormatValidator that validates the value is an IP address. +func NewIPValidator(isIPv4 bool) FormatValidator[string] { + return callbackValidator[string]{fn: func(ip string) error { + addr, err := netip.ParseAddr(ip) + if err != nil { + return &SchemaError{ + Value: ip, + Reason: "Not an IP address", + } + } + if isIPv4 && !addr.Is4() { + return &SchemaError{ + Value: ip, + Reason: "Not an IPv4 address (it's IPv6)", + } + } + if !isIPv4 && !addr.Is6() { + return &SchemaError{ + Value: ip, + Reason: "Not an IPv6 address (it's IPv4)", + } + } + return nil + }} +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go new file mode 100644 index 00000000..58197137 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go @@ -0,0 +1,35 @@ +package openapi3 + +import ( + "fmt" + "regexp" +) + +var patRewriteCodepoints = regexp.MustCompile(`(?P\\u)(?P[0-9A-F]{4})`) + +// See https://pkg.go.dev/regexp/syntax +func intoGoRegexp(re string) string { + return patRewriteCodepoints.ReplaceAllString(re, `\x{${code}}`) +} + +// NOTE: racey WRT [writes to schema.Pattern] vs [reads schema.Pattern then writes to compiledPatterns] +func (schema *Schema) compilePattern(c RegexCompilerFunc) (cp RegexMatcher, err error) { + pattern := schema.Pattern + if c != nil { + cp, err = c(pattern) + } else { + cp, err = regexp.Compile(intoGoRegexp(pattern)) + } + if err != nil { + err = &SchemaError{ + Schema: schema, + SchemaField: "pattern", + Origin: err, + Reason: fmt.Sprintf("cannot compile pattern %q: %v", pattern, err), + } + return + } + + var _ bool = compiledPatterns.CompareAndSwap(pattern, nil, cp) + return +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema_validation_settings.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema_validation_settings.go new file mode 100644 index 00000000..e9c1422b --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema_validation_settings.go @@ -0,0 +1,92 @@ +package openapi3 + +import ( + "sync" +) + +// SchemaValidationOption describes options a user has when validating request / response bodies. +type SchemaValidationOption func(*schemaValidationSettings) + +type RegexCompilerFunc func(expr string) (RegexMatcher, error) + +type RegexMatcher interface { + MatchString(s string) bool +} + +type schemaValidationSettings struct { + failfast bool + multiError bool + asreq, asrep bool // exclusive (XOR) fields + formatValidationEnabled bool + patternValidationDisabled bool + readOnlyValidationDisabled bool + writeOnlyValidationDisabled bool + + regexCompiler RegexCompilerFunc + + onceSettingDefaults sync.Once + defaultsSet func() + + customizeMessageError func(err *SchemaError) string +} + +// FailFast returns schema validation errors quicker. +func FailFast() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.failfast = true } +} + +func MultiErrors() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.multiError = true } +} + +func VisitAsRequest() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.asreq, s.asrep = true, false } +} + +func VisitAsResponse() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.asreq, s.asrep = false, true } +} + +// EnableFormatValidation setting makes Validate not return an error when validating documents that mention schema formats that are not defined by the OpenAPIv3 specification. +func EnableFormatValidation() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.formatValidationEnabled = true } +} + +// DisablePatternValidation setting makes Validate not return an error when validating patterns that are not supported by the Go regexp engine. +func DisablePatternValidation() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.patternValidationDisabled = true } +} + +// DisableReadOnlyValidation setting makes Validate not return an error when validating properties marked as read-only +func DisableReadOnlyValidation() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.readOnlyValidationDisabled = true } +} + +// DisableWriteOnlyValidation setting makes Validate not return an error when validating properties marked as write-only +func DisableWriteOnlyValidation() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.writeOnlyValidationDisabled = true } +} + +// DefaultsSet executes the given callback (once) IFF schema validation set default values. +func DefaultsSet(f func()) SchemaValidationOption { + return func(s *schemaValidationSettings) { s.defaultsSet = f } +} + +// SetSchemaErrorMessageCustomizer allows to override the schema error message. +// If the passed function returns an empty string, it returns to the previous Error() implementation. +func SetSchemaErrorMessageCustomizer(f func(err *SchemaError) string) SchemaValidationOption { + return func(s *schemaValidationSettings) { s.customizeMessageError = f } +} + +// SetSchemaRegexCompiler allows to override the regex implementation used to validate field "pattern". +func SetSchemaRegexCompiler(c RegexCompilerFunc) SchemaValidationOption { + return func(s *schemaValidationSettings) { s.regexCompiler = c } +} + +func newSchemaValidationSettings(opts ...SchemaValidationOption) *schemaValidationSettings { + settings := &schemaValidationSettings{} + for _, opt := range opts { + opt(settings) + } + return settings +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go b/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go new file mode 100644 index 00000000..87891c95 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go @@ -0,0 +1,51 @@ +package openapi3 + +import ( + "context" +) + +type SecurityRequirements []SecurityRequirement + +func NewSecurityRequirements() *SecurityRequirements { + return &SecurityRequirements{} +} + +func (srs *SecurityRequirements) With(securityRequirement SecurityRequirement) *SecurityRequirements { + *srs = append(*srs, securityRequirement) + return srs +} + +// Validate returns an error if SecurityRequirements does not comply with the OpenAPI spec. +func (srs SecurityRequirements) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + for _, security := range srs { + if err := security.Validate(ctx); err != nil { + return err + } + } + return nil +} + +// SecurityRequirement is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-requirement-object +type SecurityRequirement map[string][]string + +func NewSecurityRequirement() SecurityRequirement { + return make(SecurityRequirement) +} + +func (security SecurityRequirement) Authenticate(provider string, scopes ...string) SecurityRequirement { + if len(scopes) == 0 { + scopes = []string{} // Forces the variable to be encoded as an array instead of null + } + security[provider] = scopes + return security +} + +// Validate returns an error if SecurityRequirement does not comply with the OpenAPI spec. +func (security *SecurityRequirement) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go b/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go new file mode 100644 index 00000000..b5c94b61 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go @@ -0,0 +1,429 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/url" +) + +// SecurityScheme is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object +type SecurityScheme struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Type string `json:"type,omitempty" yaml:"type,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + In string `json:"in,omitempty" yaml:"in,omitempty"` + Scheme string `json:"scheme,omitempty" yaml:"scheme,omitempty"` + BearerFormat string `json:"bearerFormat,omitempty" yaml:"bearerFormat,omitempty"` + Flows *OAuthFlows `json:"flows,omitempty" yaml:"flows,omitempty"` + OpenIdConnectUrl string `json:"openIdConnectUrl,omitempty" yaml:"openIdConnectUrl,omitempty"` +} + +func NewSecurityScheme() *SecurityScheme { + return &SecurityScheme{} +} + +func NewCSRFSecurityScheme() *SecurityScheme { + return &SecurityScheme{ + Type: "apiKey", + In: "header", + Name: "X-XSRF-TOKEN", + } +} + +func NewOIDCSecurityScheme(oidcUrl string) *SecurityScheme { + return &SecurityScheme{ + Type: "openIdConnect", + OpenIdConnectUrl: oidcUrl, + } +} + +func NewJWTSecurityScheme() *SecurityScheme { + return &SecurityScheme{ + Type: "http", + Scheme: "bearer", + BearerFormat: "JWT", + } +} + +// MarshalJSON returns the JSON encoding of SecurityScheme. +func (ss SecurityScheme) MarshalJSON() ([]byte, error) { + x, err := ss.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of SecurityScheme. +func (ss SecurityScheme) MarshalYAML() (any, error) { + m := make(map[string]any, 8+len(ss.Extensions)) + for k, v := range ss.Extensions { + m[k] = v + } + if x := ss.Type; x != "" { + m["type"] = x + } + if x := ss.Description; x != "" { + m["description"] = x + } + if x := ss.Name; x != "" { + m["name"] = x + } + if x := ss.In; x != "" { + m["in"] = x + } + if x := ss.Scheme; x != "" { + m["scheme"] = x + } + if x := ss.BearerFormat; x != "" { + m["bearerFormat"] = x + } + if x := ss.Flows; x != nil { + m["flows"] = x + } + if x := ss.OpenIdConnectUrl; x != "" { + m["openIdConnectUrl"] = x + } + return m, nil +} + +// UnmarshalJSON sets SecurityScheme to a copy of data. +func (ss *SecurityScheme) UnmarshalJSON(data []byte) error { + type SecuritySchemeBis SecurityScheme + var x SecuritySchemeBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "type") + delete(x.Extensions, "description") + delete(x.Extensions, "name") + delete(x.Extensions, "in") + delete(x.Extensions, "scheme") + delete(x.Extensions, "bearerFormat") + delete(x.Extensions, "flows") + delete(x.Extensions, "openIdConnectUrl") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *ss = SecurityScheme(x) + return nil +} + +func (ss *SecurityScheme) WithType(value string) *SecurityScheme { + ss.Type = value + return ss +} + +func (ss *SecurityScheme) WithDescription(value string) *SecurityScheme { + ss.Description = value + return ss +} + +func (ss *SecurityScheme) WithName(value string) *SecurityScheme { + ss.Name = value + return ss +} + +func (ss *SecurityScheme) WithIn(value string) *SecurityScheme { + ss.In = value + return ss +} + +func (ss *SecurityScheme) WithScheme(value string) *SecurityScheme { + ss.Scheme = value + return ss +} + +func (ss *SecurityScheme) WithBearerFormat(value string) *SecurityScheme { + ss.BearerFormat = value + return ss +} + +// Validate returns an error if SecurityScheme does not comply with the OpenAPI spec. +func (ss *SecurityScheme) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + hasIn := false + hasBearerFormat := false + hasFlow := false + switch ss.Type { + case "apiKey": + hasIn = true + case "http": + scheme := ss.Scheme + switch scheme { + case "bearer": + hasBearerFormat = true + case "basic", "negotiate", "digest": + default: + return fmt.Errorf("security scheme of type 'http' has invalid 'scheme' value %q", scheme) + } + case "oauth2": + hasFlow = true + case "openIdConnect": + if ss.OpenIdConnectUrl == "" { + return fmt.Errorf("no OIDC URL found for openIdConnect security scheme %q", ss.Name) + } + default: + return fmt.Errorf("security scheme 'type' can't be %q", ss.Type) + } + + // Validate "in" and "name" + if hasIn { + switch ss.In { + case "query", "header", "cookie": + default: + return fmt.Errorf("security scheme of type 'apiKey' should have 'in'. It can be 'query', 'header' or 'cookie', not %q", ss.In) + } + if ss.Name == "" { + return errors.New("security scheme of type 'apiKey' should have 'name'") + } + } else if len(ss.In) > 0 { + return fmt.Errorf("security scheme of type %q can't have 'in'", ss.Type) + } else if len(ss.Name) > 0 { + return fmt.Errorf("security scheme of type %q can't have 'name'", ss.Type) + } + + // Validate "format" + // "bearerFormat" is an arbitrary string so we only check if the scheme supports it + if !hasBearerFormat && len(ss.BearerFormat) > 0 { + return fmt.Errorf("security scheme of type %q can't have 'bearerFormat'", ss.Type) + } + + // Validate "flow" + if hasFlow { + flow := ss.Flows + if flow == nil { + return fmt.Errorf("security scheme of type %q should have 'flows'", ss.Type) + } + if err := flow.Validate(ctx); err != nil { + return fmt.Errorf("security scheme 'flow' is invalid: %w", err) + } + } else if ss.Flows != nil { + return fmt.Errorf("security scheme of type %q can't have 'flows'", ss.Type) + } + + return validateExtensions(ctx, ss.Extensions) +} + +// OAuthFlows is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object +type OAuthFlows struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Implicit *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"` + Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"` + ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty" yaml:"clientCredentials,omitempty"` + AuthorizationCode *OAuthFlow `json:"authorizationCode,omitempty" yaml:"authorizationCode,omitempty"` +} + +type oAuthFlowType int + +const ( + oAuthFlowTypeImplicit oAuthFlowType = iota + oAuthFlowTypePassword + oAuthFlowTypeClientCredentials + oAuthFlowAuthorizationCode +) + +// MarshalJSON returns the JSON encoding of OAuthFlows. +func (flows OAuthFlows) MarshalJSON() ([]byte, error) { + x, err := flows.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of OAuthFlows. +func (flows OAuthFlows) MarshalYAML() (any, error) { + m := make(map[string]any, 4+len(flows.Extensions)) + for k, v := range flows.Extensions { + m[k] = v + } + if x := flows.Implicit; x != nil { + m["implicit"] = x + } + if x := flows.Password; x != nil { + m["password"] = x + } + if x := flows.ClientCredentials; x != nil { + m["clientCredentials"] = x + } + if x := flows.AuthorizationCode; x != nil { + m["authorizationCode"] = x + } + return m, nil +} + +// UnmarshalJSON sets OAuthFlows to a copy of data. +func (flows *OAuthFlows) UnmarshalJSON(data []byte) error { + type OAuthFlowsBis OAuthFlows + var x OAuthFlowsBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "implicit") + delete(x.Extensions, "password") + delete(x.Extensions, "clientCredentials") + delete(x.Extensions, "authorizationCode") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *flows = OAuthFlows(x) + return nil +} + +// Validate returns an error if OAuthFlows does not comply with the OpenAPI spec. +func (flows *OAuthFlows) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if v := flows.Implicit; v != nil { + if err := v.validate(ctx, oAuthFlowTypeImplicit, opts...); err != nil { + return fmt.Errorf("the OAuth flow 'implicit' is invalid: %w", err) + } + } + + if v := flows.Password; v != nil { + if err := v.validate(ctx, oAuthFlowTypePassword, opts...); err != nil { + return fmt.Errorf("the OAuth flow 'password' is invalid: %w", err) + } + } + + if v := flows.ClientCredentials; v != nil { + if err := v.validate(ctx, oAuthFlowTypeClientCredentials, opts...); err != nil { + return fmt.Errorf("the OAuth flow 'clientCredentials' is invalid: %w", err) + } + } + + if v := flows.AuthorizationCode; v != nil { + if err := v.validate(ctx, oAuthFlowAuthorizationCode, opts...); err != nil { + return fmt.Errorf("the OAuth flow 'authorizationCode' is invalid: %w", err) + } + } + + return validateExtensions(ctx, flows.Extensions) +} + +// OAuthFlow is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flow-object +type OAuthFlow struct { + Extensions map[string]any `json:"-" yaml:"-"` + + AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"` + TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"` + RefreshURL string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"` + Scopes map[string]string `json:"scopes" yaml:"scopes"` // required +} + +// MarshalJSON returns the JSON encoding of OAuthFlow. +func (flow OAuthFlow) MarshalJSON() ([]byte, error) { + x, err := flow.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of OAuthFlow. +func (flow OAuthFlow) MarshalYAML() (any, error) { + m := make(map[string]any, 4+len(flow.Extensions)) + for k, v := range flow.Extensions { + m[k] = v + } + if x := flow.AuthorizationURL; x != "" { + m["authorizationUrl"] = x + } + if x := flow.TokenURL; x != "" { + m["tokenUrl"] = x + } + if x := flow.RefreshURL; x != "" { + m["refreshUrl"] = x + } + m["scopes"] = flow.Scopes + return m, nil +} + +// UnmarshalJSON sets OAuthFlow to a copy of data. +func (flow *OAuthFlow) UnmarshalJSON(data []byte) error { + type OAuthFlowBis OAuthFlow + var x OAuthFlowBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "authorizationUrl") + delete(x.Extensions, "tokenUrl") + delete(x.Extensions, "refreshUrl") + delete(x.Extensions, "scopes") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *flow = OAuthFlow(x) + return nil +} + +// Validate returns an error if OAuthFlows does not comply with the OpenAPI spec. +func (flow *OAuthFlow) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if v := flow.RefreshURL; v != "" { + if _, err := url.Parse(v); err != nil { + return fmt.Errorf("field 'refreshUrl' is invalid: %w", err) + } + } + + if flow.Scopes == nil { + return errors.New("field 'scopes' is missing") + } + + return validateExtensions(ctx, flow.Extensions) +} + +func (flow *OAuthFlow) validate(ctx context.Context, typ oAuthFlowType, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + typeIn := func(types ...oAuthFlowType) bool { + for _, ty := range types { + if ty == typ { + return true + } + } + return false + } + + if in := typeIn(oAuthFlowTypeImplicit, oAuthFlowAuthorizationCode); true { + switch { + case flow.AuthorizationURL == "" && in: + return errors.New("field 'authorizationUrl' is empty or missing") + case flow.AuthorizationURL != "" && !in: + return errors.New("field 'authorizationUrl' should not be set") + case flow.AuthorizationURL != "": + if _, err := url.Parse(flow.AuthorizationURL); err != nil { + return fmt.Errorf("field 'authorizationUrl' is invalid: %w", err) + } + } + } + + if in := typeIn(oAuthFlowTypePassword, oAuthFlowTypeClientCredentials, oAuthFlowAuthorizationCode); true { + switch { + case flow.TokenURL == "" && in: + return errors.New("field 'tokenUrl' is empty or missing") + case flow.TokenURL != "" && !in: + return errors.New("field 'tokenUrl' should not be set") + case flow.TokenURL != "": + if _, err := url.Parse(flow.TokenURL); err != nil { + return fmt.Errorf("field 'tokenUrl' is invalid: %w", err) + } + } + } + + return flow.Validate(ctx, opts...) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/serialization_method.go b/vendor/github.com/getkin/kin-openapi/openapi3/serialization_method.go new file mode 100644 index 00000000..2ec8bd2d --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/serialization_method.go @@ -0,0 +1,17 @@ +package openapi3 + +const ( + SerializationSimple = "simple" + SerializationLabel = "label" + SerializationMatrix = "matrix" + SerializationForm = "form" + SerializationSpaceDelimited = "spaceDelimited" + SerializationPipeDelimited = "pipeDelimited" + SerializationDeepObject = "deepObject" +) + +// SerializationMethod describes a serialization method of HTTP request's parameters and body. +type SerializationMethod struct { + Style string + Explode bool +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/server.go b/vendor/github.com/getkin/kin-openapi/openapi3/server.go new file mode 100644 index 00000000..7a2007f2 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/server.go @@ -0,0 +1,302 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math" + "net/url" + "sort" + "strings" +) + +// Servers is specified by OpenAPI/Swagger standard version 3. +type Servers []*Server + +// Validate returns an error if Servers does not comply with the OpenAPI spec. +func (servers Servers) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + for _, v := range servers { + if err := v.Validate(ctx); err != nil { + return err + } + } + return nil +} + +// BasePath returns the base path of the first server in the list, or /. +func (servers Servers) BasePath() (string, error) { + for _, server := range servers { + return server.BasePath() + } + return "/", nil +} + +func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string) { + rawURL := parsedURL.String() + if i := strings.IndexByte(rawURL, '?'); i >= 0 { + rawURL = rawURL[:i] + } + for _, server := range servers { + pathParams, remaining, ok := server.MatchRawURL(rawURL) + if ok { + return server, pathParams, remaining + } + } + return nil, nil, "" +} + +// Server is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-object +type Server struct { + Extensions map[string]any `json:"-" yaml:"-"` + + URL string `json:"url" yaml:"url"` // Required + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Variables map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"` +} + +// BasePath returns the base path extracted from the default values of variables, if any. +// Assumes a valid struct (per Validate()). +func (server *Server) BasePath() (string, error) { + if server == nil { + return "/", nil + } + + uri := server.URL + for name, svar := range server.Variables { + uri = strings.ReplaceAll(uri, "{"+name+"}", svar.Default) + } + + u, err := url.ParseRequestURI(uri) + if err != nil { + return "", err + } + + if bp := u.Path; bp != "" { + return bp, nil + } + + return "/", nil +} + +// MarshalJSON returns the JSON encoding of Server. +func (server Server) MarshalJSON() ([]byte, error) { + x, err := server.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Server. +func (server Server) MarshalYAML() (any, error) { + m := make(map[string]any, 3+len(server.Extensions)) + for k, v := range server.Extensions { + m[k] = v + } + m["url"] = server.URL + if x := server.Description; x != "" { + m["description"] = x + } + if x := server.Variables; len(x) != 0 { + m["variables"] = x + } + return m, nil +} + +// UnmarshalJSON sets Server to a copy of data. +func (server *Server) UnmarshalJSON(data []byte) error { + type ServerBis Server + var x ServerBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "url") + delete(x.Extensions, "description") + delete(x.Extensions, "variables") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *server = Server(x) + return nil +} + +func (server Server) ParameterNames() ([]string, error) { + pattern := server.URL + var params []string + for len(pattern) > 0 { + i := strings.IndexByte(pattern, '{') + if i < 0 { + break + } + pattern = pattern[i+1:] + i = strings.IndexByte(pattern, '}') + if i < 0 { + return nil, errors.New("missing '}'") + } + params = append(params, strings.TrimSpace(pattern[:i])) + pattern = pattern[i+1:] + } + return params, nil +} + +func (server Server) MatchRawURL(input string) ([]string, string, bool) { + pattern := server.URL + var params []string + for len(pattern) > 0 { + c := pattern[0] + if len(pattern) == 1 && c == '/' { + break + } + if c == '{' { + // Find end of pattern + i := strings.IndexByte(pattern, '}') + if i < 0 { + return nil, "", false + } + pattern = pattern[i+1:] + + // Find next matching pattern character or next '/' whichever comes first + np := -1 + if len(pattern) > 0 { + np = strings.IndexByte(input, pattern[0]) + } + ns := strings.IndexByte(input, '/') + + if np < 0 { + i = ns + } else if ns < 0 { + i = np + } else { + i = int(math.Min(float64(np), float64(ns))) + } + if i < 0 { + i = len(input) + } + params = append(params, input[:i]) + input = input[i:] + continue + } + if len(input) == 0 || input[0] != c { + return nil, "", false + } + pattern = pattern[1:] + input = input[1:] + } + if input == "" { + input = "/" + } + if input[0] != '/' { + return nil, "", false + } + return params, input, true +} + +// Validate returns an error if Server does not comply with the OpenAPI spec. +func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) (err error) { + ctx = WithValidationOptions(ctx, opts...) + + if server.URL == "" { + return errors.New("value of url must be a non-empty string") + } + + opening, closing := strings.Count(server.URL, "{"), strings.Count(server.URL, "}") + if opening != closing { + return errors.New("server URL has mismatched { and }") + } + + if opening != len(server.Variables) { + return errors.New("server has undeclared variables") + } + + variables := make([]string, 0, len(server.Variables)) + for name := range server.Variables { + variables = append(variables, name) + } + sort.Strings(variables) + for _, name := range variables { + v := server.Variables[name] + if !strings.Contains(server.URL, "{"+name+"}") { + return errors.New("server has undeclared variables") + } + if err = v.Validate(ctx); err != nil { + return + } + } + + return validateExtensions(ctx, server.Extensions) +} + +// ServerVariable is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object +type ServerVariable struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"` + Default string `json:"default,omitempty" yaml:"default,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` +} + +// MarshalJSON returns the JSON encoding of ServerVariable. +func (serverVariable ServerVariable) MarshalJSON() ([]byte, error) { + x, err := serverVariable.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of ServerVariable. +func (serverVariable ServerVariable) MarshalYAML() (any, error) { + m := make(map[string]any, 4+len(serverVariable.Extensions)) + for k, v := range serverVariable.Extensions { + m[k] = v + } + if x := serverVariable.Enum; len(x) != 0 { + m["enum"] = x + } + if x := serverVariable.Default; x != "" { + m["default"] = x + } + if x := serverVariable.Description; x != "" { + m["description"] = x + } + return m, nil +} + +// UnmarshalJSON sets ServerVariable to a copy of data. +func (serverVariable *ServerVariable) UnmarshalJSON(data []byte) error { + type ServerVariableBis ServerVariable + var x ServerVariableBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "enum") + delete(x.Extensions, "default") + delete(x.Extensions, "description") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *serverVariable = ServerVariable(x) + return nil +} + +// Validate returns an error if ServerVariable does not comply with the OpenAPI spec. +func (serverVariable *ServerVariable) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if serverVariable.Default == "" { + data, err := serverVariable.MarshalJSON() + if err != nil { + return err + } + return fmt.Errorf("field default is required in %s", data) + } + + return validateExtensions(ctx, serverVariable.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/tag.go b/vendor/github.com/getkin/kin-openapi/openapi3/tag.go new file mode 100644 index 00000000..182d0502 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/tag.go @@ -0,0 +1,99 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "fmt" +) + +// Tags is specified by OpenAPI/Swagger 3.0 standard. +type Tags []*Tag + +func (tags Tags) Get(name string) *Tag { + for _, tag := range tags { + if tag.Name == name { + return tag + } + } + return nil +} + +// Validate returns an error if Tags does not comply with the OpenAPI spec. +func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + for _, v := range tags { + if err := v.Validate(ctx); err != nil { + return err + } + } + return nil +} + +// Tag is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#tag-object +type Tag struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` +} + +// MarshalJSON returns the JSON encoding of Tag. +func (t Tag) MarshalJSON() ([]byte, error) { + x, err := t.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Tag. +func (t Tag) MarshalYAML() (any, error) { + m := make(map[string]any, 3+len(t.Extensions)) + for k, v := range t.Extensions { + m[k] = v + } + if x := t.Name; x != "" { + m["name"] = x + } + if x := t.Description; x != "" { + m["description"] = x + } + if x := t.ExternalDocs; x != nil { + m["externalDocs"] = x + } + return m, nil +} + +// UnmarshalJSON sets Tag to a copy of data. +func (t *Tag) UnmarshalJSON(data []byte) error { + type TagBis Tag + var x TagBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "name") + delete(x.Extensions, "description") + delete(x.Extensions, "externalDocs") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *t = Tag(x) + return nil +} + +// Validate returns an error if Tag does not comply with the OpenAPI spec. +func (t *Tag) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if v := t.ExternalDocs; v != nil { + if err := v.Validate(ctx); err != nil { + return fmt.Errorf("invalid external docs: %w", err) + } + } + + return validateExtensions(ctx, t.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/validation_options.go b/vendor/github.com/getkin/kin-openapi/openapi3/validation_options.go new file mode 100644 index 00000000..1d141d40 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/validation_options.go @@ -0,0 +1,142 @@ +package openapi3 + +import "context" + +// ValidationOption allows the modification of how the OpenAPI document is validated. +type ValidationOption func(options *ValidationOptions) + +// ValidationOptions provides configuration for validating OpenAPI documents. +type ValidationOptions struct { + examplesValidationAsReq, examplesValidationAsRes bool + examplesValidationDisabled bool + schemaDefaultsValidationDisabled bool + schemaFormatValidationEnabled bool + schemaPatternValidationDisabled bool + schemaExtensionsInRefProhibited bool + regexCompilerFunc RegexCompilerFunc + extraSiblingFieldsAllowed map[string]struct{} +} + +type validationOptionsKey struct{} + +// AllowExtraSiblingFields called as AllowExtraSiblingFields("description") makes Validate not return an error when said field appears next to a $ref. +func AllowExtraSiblingFields(fields ...string) ValidationOption { + return func(options *ValidationOptions) { + if options.extraSiblingFieldsAllowed == nil && len(fields) != 0 { + options.extraSiblingFieldsAllowed = make(map[string]struct{}, len(fields)) + } + for _, field := range fields { + options.extraSiblingFieldsAllowed[field] = struct{}{} + } + } +} + +// EnableSchemaFormatValidation makes Validate not return an error when validating documents that mention schema formats that are not defined by the OpenAPIv3 specification. +// By default, schema format validation is disabled. +func EnableSchemaFormatValidation() ValidationOption { + return func(options *ValidationOptions) { + options.schemaFormatValidationEnabled = true + } +} + +// DisableSchemaFormatValidation does the opposite of EnableSchemaFormatValidation. +// By default, schema format validation is disabled. +func DisableSchemaFormatValidation() ValidationOption { + return func(options *ValidationOptions) { + options.schemaFormatValidationEnabled = false + } +} + +// EnableSchemaPatternValidation does the opposite of DisableSchemaPatternValidation. +// By default, schema pattern validation is enabled. +func EnableSchemaPatternValidation() ValidationOption { + return func(options *ValidationOptions) { + options.schemaPatternValidationDisabled = false + } +} + +// DisableSchemaPatternValidation makes Validate not return an error when validating patterns that are not supported by the Go regexp engine. +func DisableSchemaPatternValidation() ValidationOption { + return func(options *ValidationOptions) { + options.schemaPatternValidationDisabled = true + } +} + +// EnableSchemaDefaultsValidation does the opposite of DisableSchemaDefaultsValidation. +// By default, schema default values are validated against their schema. +func EnableSchemaDefaultsValidation() ValidationOption { + return func(options *ValidationOptions) { + options.schemaDefaultsValidationDisabled = false + } +} + +// DisableSchemaDefaultsValidation disables schemas' default field validation. +// By default, schema default values are validated against their schema. +func DisableSchemaDefaultsValidation() ValidationOption { + return func(options *ValidationOptions) { + options.schemaDefaultsValidationDisabled = true + } +} + +// EnableExamplesValidation does the opposite of DisableExamplesValidation. +// By default, all schema examples are validated. +func EnableExamplesValidation() ValidationOption { + return func(options *ValidationOptions) { + options.examplesValidationDisabled = false + } +} + +// DisableExamplesValidation disables all example schema validation. +// By default, all schema examples are validated. +func DisableExamplesValidation() ValidationOption { + return func(options *ValidationOptions) { + options.examplesValidationDisabled = true + } +} + +// AllowExtensionsWithRef allows extensions (fields starting with 'x-') +// as siblings for $ref fields. This is the default. +// Non-extension fields are prohibited unless allowed explicitly with the +// AllowExtraSiblingFields option. +func AllowExtensionsWithRef() ValidationOption { + return func(options *ValidationOptions) { + options.schemaExtensionsInRefProhibited = false + } +} + +// ProhibitExtensionsWithRef causes the validation to return an +// error if extensions (fields starting with 'x-') are found as +// siblings for $ref fields. Non-extension fields are prohibited +// unless allowed explicitly with the AllowExtraSiblingFields option. +func ProhibitExtensionsWithRef() ValidationOption { + return func(options *ValidationOptions) { + options.schemaExtensionsInRefProhibited = true + } +} + +// SetRegexCompiler allows to override the regex implementation used to validate +// field "pattern". +func SetRegexCompiler(c RegexCompilerFunc) ValidationOption { + return func(options *ValidationOptions) { + options.regexCompilerFunc = c + } +} + +// WithValidationOptions allows adding validation options to a context object that can be used when validating any OpenAPI type. +func WithValidationOptions(ctx context.Context, opts ...ValidationOption) context.Context { + if len(opts) == 0 { + return ctx + } + options := &ValidationOptions{} + for _, opt := range opts { + opt(options) + } + return context.WithValue(ctx, validationOptionsKey{}, options) +} + +func getValidationOptions(ctx context.Context) *ValidationOptions { + if options, ok := ctx.Value(validationOptionsKey{}).(*ValidationOptions); ok { + return options + } + return &ValidationOptions{} +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/visited.go b/vendor/github.com/getkin/kin-openapi/openapi3/visited.go new file mode 100644 index 00000000..67f970e3 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/visited.go @@ -0,0 +1,41 @@ +package openapi3 + +func newVisited() visitedComponent { + return visitedComponent{ + header: make(map[*Header]struct{}), + schema: make(map[*Schema]struct{}), + } +} + +type visitedComponent struct { + header map[*Header]struct{} + schema map[*Schema]struct{} +} + +// resetVisited clears visitedComponent map +// should be called before recursion over doc *T +func (doc *T) resetVisited() { + doc.visited = newVisited() +} + +// isVisitedHeader returns `true` if the *Header pointer was already visited +// otherwise it returns `false` +func (doc *T) isVisitedHeader(h *Header) bool { + if _, ok := doc.visited.header[h]; ok { + return true + } + + doc.visited.header[h] = struct{}{} + return false +} + +// isVisitedHeader returns `true` if the *Schema pointer was already visited +// otherwise it returns `false` +func (doc *T) isVisitedSchema(s *Schema) bool { + if _, ok := doc.visited.schema[s]; ok { + return true + } + + doc.visited.schema[s] = struct{}{} + return false +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/xml.go b/vendor/github.com/getkin/kin-openapi/openapi3/xml.go new file mode 100644 index 00000000..69d1b348 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/xml.go @@ -0,0 +1,78 @@ +package openapi3 + +import ( + "context" + "encoding/json" +) + +// XML is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#xml-object +type XML struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` + Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"` + Attribute bool `json:"attribute,omitempty" yaml:"attribute,omitempty"` + Wrapped bool `json:"wrapped,omitempty" yaml:"wrapped,omitempty"` +} + +// MarshalJSON returns the JSON encoding of XML. +func (xml XML) MarshalJSON() ([]byte, error) { + x, err := xml.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of XML. +func (xml XML) MarshalYAML() (any, error) { + m := make(map[string]any, 5+len(xml.Extensions)) + for k, v := range xml.Extensions { + m[k] = v + } + if x := xml.Name; x != "" { + m["name"] = x + } + if x := xml.Namespace; x != "" { + m["namespace"] = x + } + if x := xml.Prefix; x != "" { + m["prefix"] = x + } + if x := xml.Attribute; x { + m["attribute"] = x + } + if x := xml.Wrapped; x { + m["wrapped"] = x + } + return m, nil +} + +// UnmarshalJSON sets XML to a copy of data. +func (xml *XML) UnmarshalJSON(data []byte) error { + type XMLBis XML + var x XMLBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "name") + delete(x.Extensions, "namespace") + delete(x.Extensions, "prefix") + delete(x.Extensions, "attribute") + delete(x.Extensions, "wrapped") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *xml = XML(x) + return nil +} + +// Validate returns an error if XML does not comply with the OpenAPI spec. +func (xml *XML) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + return validateExtensions(ctx, xml.Extensions) +} diff --git a/vendor/github.com/ghodss/yaml/.gitignore b/vendor/github.com/ghodss/yaml/.gitignore new file mode 100644 index 00000000..e256a31e --- /dev/null +++ b/vendor/github.com/ghodss/yaml/.gitignore @@ -0,0 +1,20 @@ +# OSX leaves these everywhere on SMB shares +._* + +# Eclipse files +.classpath +.project +.settings/** + +# Emacs save files +*~ + +# Vim-related files +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist + +# Go test binaries +*.test diff --git a/vendor/github.com/ghodss/yaml/.travis.yml b/vendor/github.com/ghodss/yaml/.travis.yml new file mode 100644 index 00000000..0e9d6edc --- /dev/null +++ b/vendor/github.com/ghodss/yaml/.travis.yml @@ -0,0 +1,7 @@ +language: go +go: + - 1.3 + - 1.4 +script: + - go test + - go build diff --git a/vendor/github.com/ghodss/yaml/LICENSE b/vendor/github.com/ghodss/yaml/LICENSE new file mode 100644 index 00000000..7805d36d --- /dev/null +++ b/vendor/github.com/ghodss/yaml/LICENSE @@ -0,0 +1,50 @@ +The MIT License (MIT) + +Copyright (c) 2014 Sam Ghods + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/ghodss/yaml/README.md b/vendor/github.com/ghodss/yaml/README.md new file mode 100644 index 00000000..0200f75b --- /dev/null +++ b/vendor/github.com/ghodss/yaml/README.md @@ -0,0 +1,121 @@ +# YAML marshaling and unmarshaling support for Go + +[![Build Status](https://travis-ci.org/ghodss/yaml.svg)](https://travis-ci.org/ghodss/yaml) + +## Introduction + +A wrapper around [go-yaml](https://github.com/go-yaml/yaml) designed to enable a better way of handling YAML when marshaling to and from structs. + +In short, this library first converts YAML to JSON using go-yaml and then uses `json.Marshal` and `json.Unmarshal` to convert to or from the struct. This means that it effectively reuses the JSON struct tags as well as the custom JSON methods `MarshalJSON` and `UnmarshalJSON` unlike go-yaml. For a detailed overview of the rationale behind this method, [see this blog post](http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/). + +## Compatibility + +This package uses [go-yaml](https://github.com/go-yaml/yaml) and therefore supports [everything go-yaml supports](https://github.com/go-yaml/yaml#compatibility). + +## Caveats + +**Caveat #1:** When using `yaml.Marshal` and `yaml.Unmarshal`, binary data should NOT be preceded with the `!!binary` YAML tag. If you do, go-yaml will convert the binary data from base64 to native binary data, which is not compatible with JSON. You can still use binary in your YAML files though - just store them without the `!!binary` tag and decode the base64 in your code (e.g. in the custom JSON methods `MarshalJSON` and `UnmarshalJSON`). This also has the benefit that your YAML and your JSON binary data will be decoded exactly the same way. As an example: + +``` +BAD: + exampleKey: !!binary gIGC + +GOOD: + exampleKey: gIGC +... and decode the base64 data in your code. +``` + +**Caveat #2:** When using `YAMLToJSON` directly, maps with keys that are maps will result in an error since this is not supported by JSON. This error will occur in `Unmarshal` as well since you can't unmarshal map keys anyways since struct fields can't be keys. + +## Installation and usage + +To install, run: + +``` +$ go get github.com/ghodss/yaml +``` + +And import using: + +``` +import "github.com/ghodss/yaml" +``` + +Usage is very similar to the JSON library: + +```go +package main + +import ( + "fmt" + + "github.com/ghodss/yaml" +) + +type Person struct { + Name string `json:"name"` // Affects YAML field names too. + Age int `json:"age"` +} + +func main() { + // Marshal a Person struct to YAML. + p := Person{"John", 30} + y, err := yaml.Marshal(p) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(string(y)) + /* Output: + age: 30 + name: John + */ + + // Unmarshal the YAML back into a Person struct. + var p2 Person + err = yaml.Unmarshal(y, &p2) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(p2) + /* Output: + {John 30} + */ +} +``` + +`yaml.YAMLToJSON` and `yaml.JSONToYAML` methods are also available: + +```go +package main + +import ( + "fmt" + + "github.com/ghodss/yaml" +) + +func main() { + j := []byte(`{"name": "John", "age": 30}`) + y, err := yaml.JSONToYAML(j) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(string(y)) + /* Output: + name: John + age: 30 + */ + j2, err := yaml.YAMLToJSON(y) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(string(j2)) + /* Output: + {"age":30,"name":"John"} + */ +} +``` diff --git a/vendor/github.com/ghodss/yaml/fields.go b/vendor/github.com/ghodss/yaml/fields.go new file mode 100644 index 00000000..58600740 --- /dev/null +++ b/vendor/github.com/ghodss/yaml/fields.go @@ -0,0 +1,501 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package yaml + +import ( + "bytes" + "encoding" + "encoding/json" + "reflect" + "sort" + "strings" + "sync" + "unicode" + "unicode/utf8" +) + +// indirect walks down v allocating pointers as needed, +// until it gets to a non-pointer. +// if it encounters an Unmarshaler, indirect stops and returns that. +// if decodingNull is true, indirect stops at the last pointer so it can be set to nil. +func indirect(v reflect.Value, decodingNull bool) (json.Unmarshaler, encoding.TextUnmarshaler, reflect.Value) { + // If v is a named type and is addressable, + // start with its address, so that if the type has pointer methods, + // we find them. + if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() { + v = v.Addr() + } + for { + // Load value from interface, but only if the result will be + // usefully addressable. + if v.Kind() == reflect.Interface && !v.IsNil() { + e := v.Elem() + if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) { + v = e + continue + } + } + + if v.Kind() != reflect.Ptr { + break + } + + if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() { + break + } + if v.IsNil() { + if v.CanSet() { + v.Set(reflect.New(v.Type().Elem())) + } else { + v = reflect.New(v.Type().Elem()) + } + } + if v.Type().NumMethod() > 0 { + if u, ok := v.Interface().(json.Unmarshaler); ok { + return u, nil, reflect.Value{} + } + if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { + return nil, u, reflect.Value{} + } + } + v = v.Elem() + } + return nil, nil, v +} + +// A field represents a single field found in a struct. +type field struct { + name string + nameBytes []byte // []byte(name) + equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent + + tag bool + index []int + typ reflect.Type + omitEmpty bool + quoted bool +} + +func fillField(f field) field { + f.nameBytes = []byte(f.name) + f.equalFold = foldFunc(f.nameBytes) + return f +} + +// byName sorts field by name, breaking ties with depth, +// then breaking ties with "name came from json tag", then +// breaking ties with index sequence. +type byName []field + +func (x byName) Len() int { return len(x) } + +func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byName) Less(i, j int) bool { + if x[i].name != x[j].name { + return x[i].name < x[j].name + } + if len(x[i].index) != len(x[j].index) { + return len(x[i].index) < len(x[j].index) + } + if x[i].tag != x[j].tag { + return x[i].tag + } + return byIndex(x).Less(i, j) +} + +// byIndex sorts field by index sequence. +type byIndex []field + +func (x byIndex) Len() int { return len(x) } + +func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byIndex) Less(i, j int) bool { + for k, xik := range x[i].index { + if k >= len(x[j].index) { + return false + } + if xik != x[j].index[k] { + return xik < x[j].index[k] + } + } + return len(x[i].index) < len(x[j].index) +} + +// typeFields returns a list of fields that JSON should recognize for the given type. +// The algorithm is breadth-first search over the set of structs to include - the top struct +// and then any reachable anonymous structs. +func typeFields(t reflect.Type) []field { + // Anonymous fields to explore at the current level and the next. + current := []field{} + next := []field{{typ: t}} + + // Count of queued names for current level and the next. + count := map[reflect.Type]int{} + nextCount := map[reflect.Type]int{} + + // Types already visited at an earlier level. + visited := map[reflect.Type]bool{} + + // Fields found. + var fields []field + + for len(next) > 0 { + current, next = next, current[:0] + count, nextCount = nextCount, map[reflect.Type]int{} + + for _, f := range current { + if visited[f.typ] { + continue + } + visited[f.typ] = true + + // Scan f.typ for fields to include. + for i := 0; i < f.typ.NumField(); i++ { + sf := f.typ.Field(i) + if sf.PkgPath != "" { // unexported + continue + } + tag := sf.Tag.Get("json") + if tag == "-" { + continue + } + name, opts := parseTag(tag) + if !isValidTag(name) { + name = "" + } + index := make([]int, len(f.index)+1) + copy(index, f.index) + index[len(f.index)] = i + + ft := sf.Type + if ft.Name() == "" && ft.Kind() == reflect.Ptr { + // Follow pointer. + ft = ft.Elem() + } + + // Record found field and index sequence. + if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { + tagged := name != "" + if name == "" { + name = sf.Name + } + fields = append(fields, fillField(field{ + name: name, + tag: tagged, + index: index, + typ: ft, + omitEmpty: opts.Contains("omitempty"), + quoted: opts.Contains("string"), + })) + if count[f.typ] > 1 { + // If there were multiple instances, add a second, + // so that the annihilation code will see a duplicate. + // It only cares about the distinction between 1 or 2, + // so don't bother generating any more copies. + fields = append(fields, fields[len(fields)-1]) + } + continue + } + + // Record new anonymous struct to explore in next round. + nextCount[ft]++ + if nextCount[ft] == 1 { + next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft})) + } + } + } + } + + sort.Sort(byName(fields)) + + // Delete all fields that are hidden by the Go rules for embedded fields, + // except that fields with JSON tags are promoted. + + // The fields are sorted in primary order of name, secondary order + // of field index length. Loop over names; for each name, delete + // hidden fields by choosing the one dominant field that survives. + out := fields[:0] + for advance, i := 0, 0; i < len(fields); i += advance { + // One iteration per name. + // Find the sequence of fields with the name of this first field. + fi := fields[i] + name := fi.name + for advance = 1; i+advance < len(fields); advance++ { + fj := fields[i+advance] + if fj.name != name { + break + } + } + if advance == 1 { // Only one field with this name + out = append(out, fi) + continue + } + dominant, ok := dominantField(fields[i : i+advance]) + if ok { + out = append(out, dominant) + } + } + + fields = out + sort.Sort(byIndex(fields)) + + return fields +} + +// dominantField looks through the fields, all of which are known to +// have the same name, to find the single field that dominates the +// others using Go's embedding rules, modified by the presence of +// JSON tags. If there are multiple top-level fields, the boolean +// will be false: This condition is an error in Go and we skip all +// the fields. +func dominantField(fields []field) (field, bool) { + // The fields are sorted in increasing index-length order. The winner + // must therefore be one with the shortest index length. Drop all + // longer entries, which is easy: just truncate the slice. + length := len(fields[0].index) + tagged := -1 // Index of first tagged field. + for i, f := range fields { + if len(f.index) > length { + fields = fields[:i] + break + } + if f.tag { + if tagged >= 0 { + // Multiple tagged fields at the same level: conflict. + // Return no field. + return field{}, false + } + tagged = i + } + } + if tagged >= 0 { + return fields[tagged], true + } + // All remaining fields have the same length. If there's more than one, + // we have a conflict (two fields named "X" at the same level) and we + // return no field. + if len(fields) > 1 { + return field{}, false + } + return fields[0], true +} + +var fieldCache struct { + sync.RWMutex + m map[reflect.Type][]field +} + +// cachedTypeFields is like typeFields but uses a cache to avoid repeated work. +func cachedTypeFields(t reflect.Type) []field { + fieldCache.RLock() + f := fieldCache.m[t] + fieldCache.RUnlock() + if f != nil { + return f + } + + // Compute fields without lock. + // Might duplicate effort but won't hold other computations back. + f = typeFields(t) + if f == nil { + f = []field{} + } + + fieldCache.Lock() + if fieldCache.m == nil { + fieldCache.m = map[reflect.Type][]field{} + } + fieldCache.m[t] = f + fieldCache.Unlock() + return f +} + +func isValidTag(s string) bool { + if s == "" { + return false + } + for _, c := range s { + switch { + case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): + // Backslash and quote chars are reserved, but + // otherwise any punctuation chars are allowed + // in a tag name. + default: + if !unicode.IsLetter(c) && !unicode.IsDigit(c) { + return false + } + } + } + return true +} + +const ( + caseMask = ^byte(0x20) // Mask to ignore case in ASCII. + kelvin = '\u212a' + smallLongEss = '\u017f' +) + +// foldFunc returns one of four different case folding equivalence +// functions, from most general (and slow) to fastest: +// +// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8 +// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S') +// 3) asciiEqualFold, no special, but includes non-letters (including _) +// 4) simpleLetterEqualFold, no specials, no non-letters. +// +// The letters S and K are special because they map to 3 runes, not just 2: +// * S maps to s and to U+017F 'ſ' Latin small letter long s +// * k maps to K and to U+212A 'K' Kelvin sign +// See http://play.golang.org/p/tTxjOc0OGo +// +// The returned function is specialized for matching against s and +// should only be given s. It's not curried for performance reasons. +func foldFunc(s []byte) func(s, t []byte) bool { + nonLetter := false + special := false // special letter + for _, b := range s { + if b >= utf8.RuneSelf { + return bytes.EqualFold + } + upper := b & caseMask + if upper < 'A' || upper > 'Z' { + nonLetter = true + } else if upper == 'K' || upper == 'S' { + // See above for why these letters are special. + special = true + } + } + if special { + return equalFoldRight + } + if nonLetter { + return asciiEqualFold + } + return simpleLetterEqualFold +} + +// equalFoldRight is a specialization of bytes.EqualFold when s is +// known to be all ASCII (including punctuation), but contains an 's', +// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t. +// See comments on foldFunc. +func equalFoldRight(s, t []byte) bool { + for _, sb := range s { + if len(t) == 0 { + return false + } + tb := t[0] + if tb < utf8.RuneSelf { + if sb != tb { + sbUpper := sb & caseMask + if 'A' <= sbUpper && sbUpper <= 'Z' { + if sbUpper != tb&caseMask { + return false + } + } else { + return false + } + } + t = t[1:] + continue + } + // sb is ASCII and t is not. t must be either kelvin + // sign or long s; sb must be s, S, k, or K. + tr, size := utf8.DecodeRune(t) + switch sb { + case 's', 'S': + if tr != smallLongEss { + return false + } + case 'k', 'K': + if tr != kelvin { + return false + } + default: + return false + } + t = t[size:] + + } + if len(t) > 0 { + return false + } + return true +} + +// asciiEqualFold is a specialization of bytes.EqualFold for use when +// s is all ASCII (but may contain non-letters) and contains no +// special-folding letters. +// See comments on foldFunc. +func asciiEqualFold(s, t []byte) bool { + if len(s) != len(t) { + return false + } + for i, sb := range s { + tb := t[i] + if sb == tb { + continue + } + if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') { + if sb&caseMask != tb&caseMask { + return false + } + } else { + return false + } + } + return true +} + +// simpleLetterEqualFold is a specialization of bytes.EqualFold for +// use when s is all ASCII letters (no underscores, etc) and also +// doesn't contain 'k', 'K', 's', or 'S'. +// See comments on foldFunc. +func simpleLetterEqualFold(s, t []byte) bool { + if len(s) != len(t) { + return false + } + for i, b := range s { + if b&caseMask != t[i]&caseMask { + return false + } + } + return true +} + +// tagOptions is the string following a comma in a struct field's "json" +// tag, or the empty string. It does not include the leading comma. +type tagOptions string + +// parseTag splits a struct field's json tag into its name and +// comma-separated options. +func parseTag(tag string) (string, tagOptions) { + if idx := strings.Index(tag, ","); idx != -1 { + return tag[:idx], tagOptions(tag[idx+1:]) + } + return tag, tagOptions("") +} + +// Contains reports whether a comma-separated list of options +// contains a particular substr flag. substr must be surrounded by a +// string boundary or commas. +func (o tagOptions) Contains(optionName string) bool { + if len(o) == 0 { + return false + } + s := string(o) + for s != "" { + var next string + i := strings.Index(s, ",") + if i >= 0 { + s, next = s[:i], s[i+1:] + } + if s == optionName { + return true + } + s = next + } + return false +} diff --git a/vendor/github.com/ghodss/yaml/yaml.go b/vendor/github.com/ghodss/yaml/yaml.go new file mode 100644 index 00000000..4fb4054a --- /dev/null +++ b/vendor/github.com/ghodss/yaml/yaml.go @@ -0,0 +1,277 @@ +package yaml + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strconv" + + "gopkg.in/yaml.v2" +) + +// Marshals the object into JSON then converts JSON to YAML and returns the +// YAML. +func Marshal(o interface{}) ([]byte, error) { + j, err := json.Marshal(o) + if err != nil { + return nil, fmt.Errorf("error marshaling into JSON: %v", err) + } + + y, err := JSONToYAML(j) + if err != nil { + return nil, fmt.Errorf("error converting JSON to YAML: %v", err) + } + + return y, nil +} + +// Converts YAML to JSON then uses JSON to unmarshal into an object. +func Unmarshal(y []byte, o interface{}) error { + vo := reflect.ValueOf(o) + j, err := yamlToJSON(y, &vo) + if err != nil { + return fmt.Errorf("error converting YAML to JSON: %v", err) + } + + err = json.Unmarshal(j, o) + if err != nil { + return fmt.Errorf("error unmarshaling JSON: %v", err) + } + + return nil +} + +// Convert JSON to YAML. +func JSONToYAML(j []byte) ([]byte, error) { + // Convert the JSON to an object. + var jsonObj interface{} + // We are using yaml.Unmarshal here (instead of json.Unmarshal) because the + // Go JSON library doesn't try to pick the right number type (int, float, + // etc.) when unmarshalling to interface{}, it just picks float64 + // universally. go-yaml does go through the effort of picking the right + // number type, so we can preserve number type throughout this process. + err := yaml.Unmarshal(j, &jsonObj) + if err != nil { + return nil, err + } + + // Marshal this object into YAML. + return yaml.Marshal(jsonObj) +} + +// Convert YAML to JSON. Since JSON is a subset of YAML, passing JSON through +// this method should be a no-op. +// +// Things YAML can do that are not supported by JSON: +// * In YAML you can have binary and null keys in your maps. These are invalid +// in JSON. (int and float keys are converted to strings.) +// * Binary data in YAML with the !!binary tag is not supported. If you want to +// use binary data with this library, encode the data as base64 as usual but do +// not use the !!binary tag in your YAML. This will ensure the original base64 +// encoded data makes it all the way through to the JSON. +func YAMLToJSON(y []byte) ([]byte, error) { + return yamlToJSON(y, nil) +} + +func yamlToJSON(y []byte, jsonTarget *reflect.Value) ([]byte, error) { + // Convert the YAML to an object. + var yamlObj interface{} + err := yaml.Unmarshal(y, &yamlObj) + if err != nil { + return nil, err + } + + // YAML objects are not completely compatible with JSON objects (e.g. you + // can have non-string keys in YAML). So, convert the YAML-compatible object + // to a JSON-compatible object, failing with an error if irrecoverable + // incompatibilties happen along the way. + jsonObj, err := convertToJSONableObject(yamlObj, jsonTarget) + if err != nil { + return nil, err + } + + // Convert this object to JSON and return the data. + return json.Marshal(jsonObj) +} + +func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (interface{}, error) { + var err error + + // Resolve jsonTarget to a concrete value (i.e. not a pointer or an + // interface). We pass decodingNull as false because we're not actually + // decoding into the value, we're just checking if the ultimate target is a + // string. + if jsonTarget != nil { + ju, tu, pv := indirect(*jsonTarget, false) + // We have a JSON or Text Umarshaler at this level, so we can't be trying + // to decode into a string. + if ju != nil || tu != nil { + jsonTarget = nil + } else { + jsonTarget = &pv + } + } + + // If yamlObj is a number or a boolean, check if jsonTarget is a string - + // if so, coerce. Else return normal. + // If yamlObj is a map or array, find the field that each key is + // unmarshaling to, and when you recurse pass the reflect.Value for that + // field back into this function. + switch typedYAMLObj := yamlObj.(type) { + case map[interface{}]interface{}: + // JSON does not support arbitrary keys in a map, so we must convert + // these keys to strings. + // + // From my reading of go-yaml v2 (specifically the resolve function), + // keys can only have the types string, int, int64, float64, binary + // (unsupported), or null (unsupported). + strMap := make(map[string]interface{}) + for k, v := range typedYAMLObj { + // Resolve the key to a string first. + var keyString string + switch typedKey := k.(type) { + case string: + keyString = typedKey + case int: + keyString = strconv.Itoa(typedKey) + case int64: + // go-yaml will only return an int64 as a key if the system + // architecture is 32-bit and the key's value is between 32-bit + // and 64-bit. Otherwise the key type will simply be int. + keyString = strconv.FormatInt(typedKey, 10) + case float64: + // Stolen from go-yaml to use the same conversion to string as + // the go-yaml library uses to convert float to string when + // Marshaling. + s := strconv.FormatFloat(typedKey, 'g', -1, 32) + switch s { + case "+Inf": + s = ".inf" + case "-Inf": + s = "-.inf" + case "NaN": + s = ".nan" + } + keyString = s + case bool: + if typedKey { + keyString = "true" + } else { + keyString = "false" + } + default: + return nil, fmt.Errorf("Unsupported map key of type: %s, key: %+#v, value: %+#v", + reflect.TypeOf(k), k, v) + } + + // jsonTarget should be a struct or a map. If it's a struct, find + // the field it's going to map to and pass its reflect.Value. If + // it's a map, find the element type of the map and pass the + // reflect.Value created from that type. If it's neither, just pass + // nil - JSON conversion will error for us if it's a real issue. + if jsonTarget != nil { + t := *jsonTarget + if t.Kind() == reflect.Struct { + keyBytes := []byte(keyString) + // Find the field that the JSON library would use. + var f *field + fields := cachedTypeFields(t.Type()) + for i := range fields { + ff := &fields[i] + if bytes.Equal(ff.nameBytes, keyBytes) { + f = ff + break + } + // Do case-insensitive comparison. + if f == nil && ff.equalFold(ff.nameBytes, keyBytes) { + f = ff + } + } + if f != nil { + // Find the reflect.Value of the most preferential + // struct field. + jtf := t.Field(f.index[0]) + strMap[keyString], err = convertToJSONableObject(v, &jtf) + if err != nil { + return nil, err + } + continue + } + } else if t.Kind() == reflect.Map { + // Create a zero value of the map's element type to use as + // the JSON target. + jtv := reflect.Zero(t.Type().Elem()) + strMap[keyString], err = convertToJSONableObject(v, &jtv) + if err != nil { + return nil, err + } + continue + } + } + strMap[keyString], err = convertToJSONableObject(v, nil) + if err != nil { + return nil, err + } + } + return strMap, nil + case []interface{}: + // We need to recurse into arrays in case there are any + // map[interface{}]interface{}'s inside and to convert any + // numbers to strings. + + // If jsonTarget is a slice (which it really should be), find the + // thing it's going to map to. If it's not a slice, just pass nil + // - JSON conversion will error for us if it's a real issue. + var jsonSliceElemValue *reflect.Value + if jsonTarget != nil { + t := *jsonTarget + if t.Kind() == reflect.Slice { + // By default slices point to nil, but we need a reflect.Value + // pointing to a value of the slice type, so we create one here. + ev := reflect.Indirect(reflect.New(t.Type().Elem())) + jsonSliceElemValue = &ev + } + } + + // Make and use a new array. + arr := make([]interface{}, len(typedYAMLObj)) + for i, v := range typedYAMLObj { + arr[i], err = convertToJSONableObject(v, jsonSliceElemValue) + if err != nil { + return nil, err + } + } + return arr, nil + default: + // If the target type is a string and the YAML type is a number, + // convert the YAML type to a string. + if jsonTarget != nil && (*jsonTarget).Kind() == reflect.String { + // Based on my reading of go-yaml, it may return int, int64, + // float64, or uint64. + var s string + switch typedVal := typedYAMLObj.(type) { + case int: + s = strconv.FormatInt(int64(typedVal), 10) + case int64: + s = strconv.FormatInt(typedVal, 10) + case float64: + s = strconv.FormatFloat(typedVal, 'g', -1, 32) + case uint64: + s = strconv.FormatUint(typedVal, 10) + case bool: + if typedVal { + s = "true" + } else { + s = "false" + } + } + if len(s) > 0 { + yamlObj = interface{}(s) + } + } + return yamlObj, nil + } + + return nil, nil +} diff --git a/vendor/github.com/go-openapi/jsonpointer/.editorconfig b/vendor/github.com/go-openapi/jsonpointer/.editorconfig new file mode 100644 index 00000000..3152da69 --- /dev/null +++ b/vendor/github.com/go-openapi/jsonpointer/.editorconfig @@ -0,0 +1,26 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +# Set default charset +[*.{js,py,go,scala,rb,java,html,css,less,sass,md}] +charset = utf-8 + +# Tab indentation (no size specified) +[*.go] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 diff --git a/vendor/github.com/go-openapi/jsonpointer/.gitignore b/vendor/github.com/go-openapi/jsonpointer/.gitignore new file mode 100644 index 00000000..769c2440 --- /dev/null +++ b/vendor/github.com/go-openapi/jsonpointer/.gitignore @@ -0,0 +1 @@ +secrets.yml diff --git a/vendor/github.com/go-openapi/jsonpointer/.golangci.yml b/vendor/github.com/go-openapi/jsonpointer/.golangci.yml new file mode 100644 index 00000000..22f8d21c --- /dev/null +++ b/vendor/github.com/go-openapi/jsonpointer/.golangci.yml @@ -0,0 +1,61 @@ +linters-settings: + govet: + check-shadowing: true + golint: + min-confidence: 0 + gocyclo: + min-complexity: 45 + maligned: + suggest-new: true + dupl: + threshold: 200 + goconst: + min-len: 2 + min-occurrences: 3 + +linters: + enable-all: true + disable: + - maligned + - unparam + - lll + - gochecknoinits + - gochecknoglobals + - funlen + - godox + - gocognit + - whitespace + - wsl + - wrapcheck + - testpackage + - nlreturn + - gomnd + - exhaustivestruct + - goerr113 + - errorlint + - nestif + - godot + - gofumpt + - paralleltest + - tparallel + - thelper + - ifshort + - exhaustruct + - varnamelen + - gci + - depguard + - errchkjson + - inamedparam + - nonamedreturns + - musttag + - ireturn + - forcetypeassert + - cyclop + # deprecated linters + - deadcode + - interfacer + - scopelint + - varcheck + - structcheck + - golint + - nosnakecase diff --git a/vendor/github.com/go-openapi/jsonpointer/CODE_OF_CONDUCT.md b/vendor/github.com/go-openapi/jsonpointer/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..9322b065 --- /dev/null +++ b/vendor/github.com/go-openapi/jsonpointer/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at ivan+abuse@flanders.co.nz. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/vendor/github.com/go-openapi/jsonpointer/LICENSE b/vendor/github.com/go-openapi/jsonpointer/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/github.com/go-openapi/jsonpointer/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/go-openapi/jsonpointer/README.md b/vendor/github.com/go-openapi/jsonpointer/README.md new file mode 100644 index 00000000..0108f1d5 --- /dev/null +++ b/vendor/github.com/go-openapi/jsonpointer/README.md @@ -0,0 +1,19 @@ +# 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) + +An implementation of JSON Pointer - Go language + +## Status +Completed YES + +Tested YES + +## References +http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07 + +### Note +The 4.Evaluation part of the previous reference, starting with 'If the currently referenced value is a JSON array, the reference token MUST contain either...' is not implemented. diff --git a/vendor/github.com/go-openapi/jsonpointer/pointer.go b/vendor/github.com/go-openapi/jsonpointer/pointer.go new file mode 100644 index 00000000..d970c7cf --- /dev/null +++ b/vendor/github.com/go-openapi/jsonpointer/pointer.go @@ -0,0 +1,531 @@ +// Copyright 2013 sigu-399 ( https://github.com/sigu-399 ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author sigu-399 +// author-github https://github.com/sigu-399 +// author-mail sigu.399@gmail.com +// +// repository-name jsonpointer +// repository-desc An implementation of JSON Pointer - Go language +// +// description Main and unique file. +// +// created 25-02-2013 + +package jsonpointer + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "strconv" + "strings" + + "github.com/go-openapi/swag" +) + +const ( + emptyPointer = `` + pointerSeparator = `/` + + invalidStart = `JSON pointer must be empty or start with a "` + pointerSeparator + notFound = `Can't find the pointer in the document` +) + +var jsonPointableType = reflect.TypeOf(new(JSONPointable)).Elem() +var jsonSetableType = reflect.TypeOf(new(JSONSetable)).Elem() + +// JSONPointable is an interface for structs to implement when they need to customize the +// json pointer process +type JSONPointable interface { + JSONLookup(string) (any, error) +} + +// JSONSetable is an interface for structs to implement when they need to customize the +// json pointer process +type JSONSetable interface { + JSONSet(string, any) error +} + +// New creates a new json pointer for the given string +func New(jsonPointerString string) (Pointer, error) { + + var p Pointer + err := p.parse(jsonPointerString) + return p, err + +} + +// Pointer the json pointer reprsentation +type Pointer struct { + referenceTokens []string +} + +// "Constructor", parses the given string JSON pointer +func (p *Pointer) parse(jsonPointerString string) error { + + var err error + + if jsonPointerString != emptyPointer { + if !strings.HasPrefix(jsonPointerString, pointerSeparator) { + err = errors.New(invalidStart) + } else { + referenceTokens := strings.Split(jsonPointerString, pointerSeparator) + p.referenceTokens = append(p.referenceTokens, referenceTokens[1:]...) + } + } + + return err +} + +// Get uses the pointer to retrieve a value from a JSON document +func (p *Pointer) Get(document any) (any, reflect.Kind, error) { + return p.get(document, swag.DefaultJSONNameProvider) +} + +// Set uses the pointer to set a value from a JSON document +func (p *Pointer) Set(document any, value any) (any, error) { + return document, p.set(document, value, swag.DefaultJSONNameProvider) +} + +// GetForToken gets a value for a json pointer token 1 level deep +func GetForToken(document any, decodedToken string) (any, reflect.Kind, error) { + return getSingleImpl(document, decodedToken, swag.DefaultJSONNameProvider) +} + +// SetForToken gets a value for a json pointer token 1 level deep +func SetForToken(document any, decodedToken string, value any) (any, error) { + return document, setSingleImpl(document, value, decodedToken, swag.DefaultJSONNameProvider) +} + +func isNil(input any) bool { + if input == nil { + return true + } + + kind := reflect.TypeOf(input).Kind() + switch kind { //nolint:exhaustive + case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan: + return reflect.ValueOf(input).IsNil() + default: + return false + } +} + +func getSingleImpl(node any, decodedToken string, nameProvider *swag.NameProvider) (any, reflect.Kind, error) { + rValue := reflect.Indirect(reflect.ValueOf(node)) + kind := rValue.Kind() + if isNil(node) { + return nil, kind, fmt.Errorf("nil value has not field %q", decodedToken) + } + + switch typed := node.(type) { + case JSONPointable: + r, err := typed.JSONLookup(decodedToken) + if err != nil { + return nil, kind, err + } + return r, kind, nil + case *any: // case of a pointer to interface, that is not resolved by reflect.Indirect + return getSingleImpl(*typed, decodedToken, nameProvider) + } + + switch kind { //nolint:exhaustive + case reflect.Struct: + nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken) + if !ok { + return nil, kind, fmt.Errorf("object has no field %q", decodedToken) + } + fld := rValue.FieldByName(nm) + return fld.Interface(), kind, nil + + case reflect.Map: + kv := reflect.ValueOf(decodedToken) + mv := rValue.MapIndex(kv) + + if mv.IsValid() { + return mv.Interface(), kind, nil + } + return nil, kind, fmt.Errorf("object has no key %q", decodedToken) + + case reflect.Slice: + tokenIndex, err := strconv.Atoi(decodedToken) + if err != nil { + return nil, kind, err + } + sLength := rValue.Len() + if tokenIndex < 0 || tokenIndex >= sLength { + return nil, kind, fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength-1, tokenIndex) + } + + elem := rValue.Index(tokenIndex) + return elem.Interface(), kind, nil + + default: + return nil, kind, fmt.Errorf("invalid token reference %q", decodedToken) + } + +} + +func setSingleImpl(node, data any, decodedToken string, nameProvider *swag.NameProvider) error { + rValue := reflect.Indirect(reflect.ValueOf(node)) + + if ns, ok := node.(JSONSetable); ok { // pointer impl + return ns.JSONSet(decodedToken, data) + } + + if rValue.Type().Implements(jsonSetableType) { + return node.(JSONSetable).JSONSet(decodedToken, data) + } + + switch rValue.Kind() { //nolint:exhaustive + case reflect.Struct: + nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken) + if !ok { + return fmt.Errorf("object has no field %q", decodedToken) + } + fld := rValue.FieldByName(nm) + if fld.IsValid() { + fld.Set(reflect.ValueOf(data)) + } + return nil + + case reflect.Map: + kv := reflect.ValueOf(decodedToken) + rValue.SetMapIndex(kv, reflect.ValueOf(data)) + return nil + + case reflect.Slice: + tokenIndex, err := strconv.Atoi(decodedToken) + if err != nil { + return err + } + sLength := rValue.Len() + if tokenIndex < 0 || tokenIndex >= sLength { + return fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength, tokenIndex) + } + + elem := rValue.Index(tokenIndex) + if !elem.CanSet() { + return fmt.Errorf("can't set slice index %s to %v", decodedToken, data) + } + elem.Set(reflect.ValueOf(data)) + return nil + + default: + return fmt.Errorf("invalid token reference %q", decodedToken) + } + +} + +func (p *Pointer) get(node any, nameProvider *swag.NameProvider) (any, reflect.Kind, error) { + + if nameProvider == nil { + nameProvider = swag.DefaultJSONNameProvider + } + + kind := reflect.Invalid + + // Full document when empty + if len(p.referenceTokens) == 0 { + return node, kind, nil + } + + for _, token := range p.referenceTokens { + + decodedToken := Unescape(token) + + r, knd, err := getSingleImpl(node, decodedToken, nameProvider) + if err != nil { + return nil, knd, err + } + node = r + } + + rValue := reflect.ValueOf(node) + kind = rValue.Kind() + + return node, kind, nil +} + +func (p *Pointer) set(node, data any, nameProvider *swag.NameProvider) error { + knd := reflect.ValueOf(node).Kind() + + if knd != reflect.Ptr && knd != reflect.Struct && knd != reflect.Map && knd != reflect.Slice && knd != reflect.Array { + return errors.New("only structs, pointers, maps and slices are supported for setting values") + } + + if nameProvider == nil { + nameProvider = swag.DefaultJSONNameProvider + } + + // Full document when empty + if len(p.referenceTokens) == 0 { + return nil + } + + lastI := len(p.referenceTokens) - 1 + for i, token := range p.referenceTokens { + isLastToken := i == lastI + decodedToken := Unescape(token) + + if isLastToken { + + return setSingleImpl(node, data, decodedToken, nameProvider) + } + + rValue := reflect.Indirect(reflect.ValueOf(node)) + kind := rValue.Kind() + + if rValue.Type().Implements(jsonPointableType) { + r, err := node.(JSONPointable).JSONLookup(decodedToken) + if err != nil { + return err + } + fld := reflect.ValueOf(r) + if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Ptr { + node = fld.Addr().Interface() + continue + } + node = r + continue + } + + switch kind { //nolint:exhaustive + case reflect.Struct: + nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken) + if !ok { + return fmt.Errorf("object has no field %q", decodedToken) + } + fld := rValue.FieldByName(nm) + if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Ptr { + node = fld.Addr().Interface() + continue + } + node = fld.Interface() + + case reflect.Map: + kv := reflect.ValueOf(decodedToken) + mv := rValue.MapIndex(kv) + + if !mv.IsValid() { + return fmt.Errorf("object has no key %q", decodedToken) + } + if mv.CanAddr() && mv.Kind() != reflect.Interface && mv.Kind() != reflect.Map && mv.Kind() != reflect.Slice && mv.Kind() != reflect.Ptr { + node = mv.Addr().Interface() + continue + } + node = mv.Interface() + + case reflect.Slice: + tokenIndex, err := strconv.Atoi(decodedToken) + if err != nil { + return err + } + sLength := rValue.Len() + if tokenIndex < 0 || tokenIndex >= sLength { + return fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength, tokenIndex) + } + + elem := rValue.Index(tokenIndex) + if elem.CanAddr() && elem.Kind() != reflect.Interface && elem.Kind() != reflect.Map && elem.Kind() != reflect.Slice && elem.Kind() != reflect.Ptr { + node = elem.Addr().Interface() + continue + } + node = elem.Interface() + + default: + return fmt.Errorf("invalid token reference %q", decodedToken) + } + + } + + return nil +} + +// DecodedTokens returns the decoded tokens +func (p *Pointer) DecodedTokens() []string { + result := make([]string, 0, len(p.referenceTokens)) + for _, t := range p.referenceTokens { + result = append(result, Unescape(t)) + } + return result +} + +// IsEmpty returns true if this is an empty json pointer +// this indicates that it points to the root document +func (p *Pointer) IsEmpty() bool { + return len(p.referenceTokens) == 0 +} + +// Pointer to string representation function +func (p *Pointer) String() string { + + if len(p.referenceTokens) == 0 { + return emptyPointer + } + + pointerString := pointerSeparator + strings.Join(p.referenceTokens, pointerSeparator) + + return pointerString +} + +func (p *Pointer) Offset(document string) (int64, error) { + dec := json.NewDecoder(strings.NewReader(document)) + var offset int64 + for _, ttk := range p.DecodedTokens() { + tk, err := dec.Token() + if err != nil { + return 0, err + } + switch tk := tk.(type) { + case json.Delim: + switch tk { + case '{': + offset, err = offsetSingleObject(dec, ttk) + if err != nil { + return 0, err + } + case '[': + offset, err = offsetSingleArray(dec, ttk) + if err != nil { + return 0, err + } + default: + return 0, fmt.Errorf("invalid token %#v", tk) + } + default: + return 0, fmt.Errorf("invalid token %#v", tk) + } + } + return offset, nil +} + +func offsetSingleObject(dec *json.Decoder, decodedToken string) (int64, error) { + for dec.More() { + offset := dec.InputOffset() + tk, err := dec.Token() + if err != nil { + return 0, err + } + switch tk := tk.(type) { + case json.Delim: + switch tk { + case '{': + if err = drainSingle(dec); err != nil { + return 0, err + } + case '[': + if err = drainSingle(dec); err != nil { + return 0, err + } + } + case string: + if tk == decodedToken { + return offset, nil + } + default: + return 0, fmt.Errorf("invalid token %#v", tk) + } + } + return 0, fmt.Errorf("token reference %q not found", decodedToken) +} + +func offsetSingleArray(dec *json.Decoder, decodedToken string) (int64, error) { + idx, err := strconv.Atoi(decodedToken) + if err != nil { + return 0, fmt.Errorf("token reference %q is not a number: %v", decodedToken, err) + } + var i int + for i = 0; i < idx && dec.More(); i++ { + tk, err := dec.Token() + if err != nil { + return 0, err + } + + if delim, isDelim := tk.(json.Delim); isDelim { + switch delim { + case '{': + if err = drainSingle(dec); err != nil { + return 0, err + } + case '[': + if err = drainSingle(dec); err != nil { + return 0, err + } + } + } + } + + if !dec.More() { + return 0, fmt.Errorf("token reference %q not found", decodedToken) + } + return dec.InputOffset(), nil +} + +// drainSingle drains a single level of object or array. +// The decoder has to guarantee the beginning delim (i.e. '{' or '[') has been consumed. +func drainSingle(dec *json.Decoder) error { + for dec.More() { + tk, err := dec.Token() + if err != nil { + return err + } + if delim, isDelim := tk.(json.Delim); isDelim { + switch delim { + case '{': + if err = drainSingle(dec); err != nil { + return err + } + case '[': + if err = drainSingle(dec); err != nil { + return err + } + } + } + } + + // Consumes the ending delim + if _, err := dec.Token(); err != nil { + return err + } + return nil +} + +// Specific JSON pointer encoding here +// ~0 => ~ +// ~1 => / +// ... and vice versa + +const ( + encRefTok0 = `~0` + encRefTok1 = `~1` + decRefTok0 = `~` + decRefTok1 = `/` +) + +// Unescape unescapes a json pointer reference token string to the original representation +func Unescape(token string) string { + step1 := strings.ReplaceAll(token, encRefTok1, decRefTok1) + step2 := strings.ReplaceAll(step1, encRefTok0, decRefTok0) + return step2 +} + +// Escape escapes a pointer reference token string +func Escape(token string) string { + step1 := strings.ReplaceAll(token, decRefTok0, encRefTok0) + step2 := strings.ReplaceAll(step1, decRefTok1, encRefTok1) + return step2 +} diff --git a/vendor/github.com/go-openapi/jsonreference/.gitignore b/vendor/github.com/go-openapi/jsonreference/.gitignore new file mode 100644 index 00000000..769c2440 --- /dev/null +++ b/vendor/github.com/go-openapi/jsonreference/.gitignore @@ -0,0 +1 @@ +secrets.yml diff --git a/vendor/github.com/go-openapi/jsonreference/.golangci.yml b/vendor/github.com/go-openapi/jsonreference/.golangci.yml new file mode 100644 index 00000000..f9381aee --- /dev/null +++ b/vendor/github.com/go-openapi/jsonreference/.golangci.yml @@ -0,0 +1,41 @@ +linters-settings: + govet: + check-shadowing: true + golint: + min-confidence: 0 + gocyclo: + min-complexity: 30 + maligned: + suggest-new: true + dupl: + threshold: 100 + goconst: + min-len: 2 + min-occurrences: 4 +linters: + enable-all: true + disable: + - maligned + - lll + - gochecknoglobals + - godox + - gocognit + - whitespace + - wsl + - funlen + - gochecknoglobals + - gochecknoinits + - scopelint + - wrapcheck + - exhaustivestruct + - exhaustive + - nlreturn + - testpackage + - gci + - gofumpt + - goerr113 + - gomnd + - tparallel + - nestif + - godot + - errorlint diff --git a/vendor/github.com/go-openapi/jsonreference/.travis.yml b/vendor/github.com/go-openapi/jsonreference/.travis.yml new file mode 100644 index 00000000..05482f4b --- /dev/null +++ b/vendor/github.com/go-openapi/jsonreference/.travis.yml @@ -0,0 +1,24 @@ +after_success: +- bash <(curl -s https://codecov.io/bash) +go: +- 1.14.x +- 1.x +install: +- go get gotest.tools/gotestsum +jobs: + include: + # include linting job, but only for latest go version and amd64 arch + - go: 1.x + arch: amd64 + install: + go get github.com/golangci/golangci-lint/cmd/golangci-lint + script: + - golangci-lint run --new-from-rev master +env: +- GO111MODULE=on +language: go +notifications: + slack: + secure: OpQG/36F7DSF00HLm9WZMhyqFCYYyYTsVDObW226cWiR8PWYiNfLZiSEvIzT1Gx4dDjhigKTIqcLhG34CkL5iNXDjm9Yyo2RYhQPlK8NErNqUEXuBqn4RqYHW48VGhEhOyDd4Ei0E2FN5ZbgpvHgtpkdZ6XDi64r3Ac89isP9aPHXQTuv2Jog6b4/OKKiUTftLcTIst0p4Cp3gqOJWf1wnoj+IadWiECNVQT6zb47IYjtyw6+uV8iUjTzdKcRB6Zc6b4Dq7JAg1Zd7Jfxkql3hlKp4PNlRf9Cy7y5iA3G7MLyg3FcPX5z2kmcyPt2jOTRMBWUJ5zIQpOxizAcN8WsT3WWBL5KbuYK6k0PzujrIDLqdxGpNmjkkMfDBT9cKmZpm2FdW+oZgPFJP+oKmAo4u4KJz/vjiPTXgQlN5bmrLuRMCp+AwC5wkIohTqWZVPE2TK6ZSnMYcg/W39s+RP/9mJoyryAvPSpBOLTI+biCgaUCTOAZxNTWpMFc3tPYntc41WWkdKcooZ9JA5DwfcaVFyTGQ3YXz+HvX6G1z/gW0Q/A4dBi9mj2iE1xm7tRTT+4VQ2AXFvSEI1HJpfPgYnwAtwOD1v3Qm2EUHk9sCdtEDR4wVGEPIVn44GnwFMnGKx9JWppMPYwFu3SVDdHt+E+LOlhZUply11Aa+IVrT2KUQ= +script: +- gotestsum -f short-verbose -- -race -coverprofile=coverage.txt -covermode=atomic ./... diff --git a/vendor/github.com/go-openapi/jsonreference/CODE_OF_CONDUCT.md b/vendor/github.com/go-openapi/jsonreference/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..9322b065 --- /dev/null +++ b/vendor/github.com/go-openapi/jsonreference/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at ivan+abuse@flanders.co.nz. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/vendor/github.com/go-openapi/jsonreference/LICENSE b/vendor/github.com/go-openapi/jsonreference/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/github.com/go-openapi/jsonreference/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/go-openapi/jsonreference/README.md b/vendor/github.com/go-openapi/jsonreference/README.md new file mode 100644 index 00000000..b94753aa --- /dev/null +++ b/vendor/github.com/go-openapi/jsonreference/README.md @@ -0,0 +1,15 @@ +# 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) + +[![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 + +## References +http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07 + +http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03 diff --git a/vendor/github.com/go-openapi/jsonreference/reference.go b/vendor/github.com/go-openapi/jsonreference/reference.go new file mode 100644 index 00000000..3bc0a6e2 --- /dev/null +++ b/vendor/github.com/go-openapi/jsonreference/reference.go @@ -0,0 +1,156 @@ +// Copyright 2013 sigu-399 ( https://github.com/sigu-399 ) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// author sigu-399 +// author-github https://github.com/sigu-399 +// author-mail sigu.399@gmail.com +// +// repository-name jsonreference +// repository-desc An implementation of JSON Reference - Go language +// +// description Main and unique file. +// +// created 26-02-2013 + +package jsonreference + +import ( + "errors" + "net/url" + "strings" + + "github.com/PuerkitoBio/purell" + "github.com/go-openapi/jsonpointer" +) + +const ( + fragmentRune = `#` +) + +// New creates a new reference for the given string +func New(jsonReferenceString string) (Ref, error) { + + var r Ref + err := r.parse(jsonReferenceString) + return r, err + +} + +// MustCreateRef parses the ref string and panics when it's invalid. +// Use the New method for a version that returns an error +func MustCreateRef(ref string) Ref { + r, err := New(ref) + if err != nil { + panic(err) + } + return r +} + +// Ref represents a json reference object +type Ref struct { + referenceURL *url.URL + referencePointer jsonpointer.Pointer + + HasFullURL bool + HasURLPathOnly bool + HasFragmentOnly bool + HasFileScheme bool + HasFullFilePath bool +} + +// GetURL gets the URL for this reference +func (r *Ref) GetURL() *url.URL { + return r.referenceURL +} + +// GetPointer gets the json pointer for this reference +func (r *Ref) GetPointer() *jsonpointer.Pointer { + return &r.referencePointer +} + +// String returns the best version of the url for this reference +func (r *Ref) String() string { + + if r.referenceURL != nil { + return r.referenceURL.String() + } + + if r.HasFragmentOnly { + return fragmentRune + r.referencePointer.String() + } + + return r.referencePointer.String() +} + +// IsRoot returns true if this reference is a root document +func (r *Ref) IsRoot() bool { + return r.referenceURL != nil && + !r.IsCanonical() && + !r.HasURLPathOnly && + r.referenceURL.Fragment == "" +} + +// IsCanonical returns true when this pointer starts with http(s):// or file:// +func (r *Ref) IsCanonical() bool { + return (r.HasFileScheme && r.HasFullFilePath) || (!r.HasFileScheme && r.HasFullURL) +} + +// "Constructor", parses the given string JSON reference +func (r *Ref) parse(jsonReferenceString string) error { + + parsed, err := url.Parse(jsonReferenceString) + if err != nil { + return err + } + + r.referenceURL, _ = url.Parse(purell.NormalizeURL(parsed, purell.FlagsSafe|purell.FlagRemoveDuplicateSlashes)) + refURL := r.referenceURL + + if refURL.Scheme != "" && refURL.Host != "" { + r.HasFullURL = true + } else { + if refURL.Path != "" { + r.HasURLPathOnly = true + } else if refURL.RawQuery == "" && refURL.Fragment != "" { + r.HasFragmentOnly = true + } + } + + r.HasFileScheme = refURL.Scheme == "file" + r.HasFullFilePath = strings.HasPrefix(refURL.Path, "/") + + // invalid json-pointer error means url has no json-pointer fragment. simply ignore error + r.referencePointer, _ = jsonpointer.New(refURL.Fragment) + + return nil +} + +// Inherits creates a new reference from a parent and a child +// If the child cannot inherit from the parent, an error is returned +func (r *Ref) Inherits(child Ref) (*Ref, error) { + childURL := child.GetURL() + parentURL := r.GetURL() + if childURL == nil { + return nil, errors.New("child url is nil") + } + if parentURL == nil { + return &child, nil + } + + ref, err := New(parentURL.ResolveReference(childURL).String()) + if err != nil { + return nil, err + } + return &ref, nil +} diff --git a/vendor/github.com/go-openapi/spec/.editorconfig b/vendor/github.com/go-openapi/spec/.editorconfig new file mode 100644 index 00000000..3152da69 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/.editorconfig @@ -0,0 +1,26 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +# Set default charset +[*.{js,py,go,scala,rb,java,html,css,less,sass,md}] +charset = utf-8 + +# Tab indentation (no size specified) +[*.go] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 diff --git a/vendor/github.com/go-openapi/spec/.gitignore b/vendor/github.com/go-openapi/spec/.gitignore new file mode 100644 index 00000000..dd91ed6a --- /dev/null +++ b/vendor/github.com/go-openapi/spec/.gitignore @@ -0,0 +1,2 @@ +secrets.yml +coverage.out diff --git a/vendor/github.com/go-openapi/spec/.golangci.yml b/vendor/github.com/go-openapi/spec/.golangci.yml new file mode 100644 index 00000000..835d55e7 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/.golangci.yml @@ -0,0 +1,42 @@ +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: 2 + +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 diff --git a/vendor/github.com/go-openapi/spec/.travis.yml b/vendor/github.com/go-openapi/spec/.travis.yml new file mode 100644 index 00000000..2281a07b --- /dev/null +++ b/vendor/github.com/go-openapi/spec/.travis.yml @@ -0,0 +1,31 @@ +after_success: +- bash <(curl -s https://codecov.io/bash) +go: +- 1.16.x +- 1.x +arch: + - amd64 +jobs: + include: + # only run fast tests on ppc64le + - go: 1.x + arch: ppc64le + script: + - gotestsum -f short-verbose -- ./... + + # include linting job, but only for latest go version and amd64 arch + - go: 1.x + arch: amd64 + install: + go get github.com/golangci/golangci-lint/cmd/golangci-lint + script: + - golangci-lint run --new-from-rev master + +install: +- GO111MODULE=off go get -u gotest.tools/gotestsum +language: go +notifications: + slack: + secure: QUWvCkBBK09GF7YtEvHHVt70JOkdlNBG0nIKu/5qc4/nW5HP8I2w0SEf/XR2je0eED1Qe3L/AfMCWwrEj+IUZc3l4v+ju8X8R3Lomhme0Eb0jd1MTMCuPcBT47YCj0M7RON7vXtbFfm1hFJ/jLe5+9FXz0hpXsR24PJc5ZIi/ogNwkaPqG4BmndzecpSh0vc2FJPZUD9LT0I09REY/vXR0oQAalLkW0asGD5taHZTUZq/kBpsNxaAFrLM23i4mUcf33M5fjLpvx5LRICrX/57XpBrDh2TooBU6Qj3CgoY0uPRYUmSNxbVx1czNzl2JtEpb5yjoxfVPQeg0BvQM00G8LJINISR+ohrjhkZmAqchDupAX+yFrxTtORa78CtnIL6z/aTNlgwwVD8kvL/1pFA/JWYmKDmz93mV/+6wubGzNSQCstzjkFA4/iZEKewKUoRIAi/fxyscP6L/rCpmY/4llZZvrnyTqVbt6URWpopUpH4rwYqreXAtJxJsfBJIeSmUIiDIOMGkCTvyTEW3fWGmGoqWtSHLoaWDyAIGb7azb+KvfpWtEcoPFWfSWU+LGee0A/YsUhBl7ADB9A0CJEuR8q4BPpKpfLwPKSiKSAXL7zDkyjExyhtgqbSl2jS+rKIHOZNL8JkCcTP2MKMVd563C5rC5FMKqu3S9m2b6380E= +script: +- gotestsum -f short-verbose -- -race -coverprofile=coverage.txt -covermode=atomic ./... diff --git a/vendor/github.com/go-openapi/spec/CODE_OF_CONDUCT.md b/vendor/github.com/go-openapi/spec/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..9322b065 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at ivan+abuse@flanders.co.nz. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/vendor/github.com/go-openapi/spec/LICENSE b/vendor/github.com/go-openapi/spec/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/go-openapi/spec/README.md b/vendor/github.com/go-openapi/spec/README.md new file mode 100644 index 00000000..18782c6d --- /dev/null +++ b/vendor/github.com/go-openapi/spec/README.md @@ -0,0 +1,34 @@ +# OAI object model + +[![Build Status](https://travis-ci.org/go-openapi/spec.svg?branch=master)](https://travis-ci.org/go-openapi/spec) + +[![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) +[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/spec)](https://goreportcard.com/report/github.com/go-openapi/spec) + +The object model for OpenAPI specification documents. + +### FAQ + +* What does this do? + +> 1. This package knows how to marshal and unmarshal Swagger API specifications into a golang object model +> 2. It knows how to resolve $ref and expand them to make a single root document + +* How does it play with the rest of the go-openapi packages ? + +> 1. This package is at the core of the go-openapi suite of packages and [code generator](https://github.com/go-swagger/go-swagger) +> 2. There is a [spec loading package](https://github.com/go-openapi/loads) to fetch specs as JSON or YAML from local or remote locations +> 3. There is a [spec validation package](https://github.com/go-openapi/validate) built on top of it +> 4. There is a [spec analysis package](https://github.com/go-openapi/analysis) built on top of it, to analyze, flatten, fix and merge spec documents + +* Does this library support OpenAPI 3? + +> No. +> This package currently only supports OpenAPI 2.0 (aka Swagger 2.0). +> There is no plan to make it evolve toward supporting OpenAPI 3.x. +> This [discussion thread](https://github.com/go-openapi/spec/issues/21) relates the full story. +> +> An early attempt to support Swagger 3 may be found at: https://github.com/go-openapi/spec3 diff --git a/vendor/github.com/go-openapi/spec/appveyor.yml b/vendor/github.com/go-openapi/spec/appveyor.yml new file mode 100644 index 00000000..09035939 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/appveyor.yml @@ -0,0 +1,32 @@ +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 diff --git a/vendor/github.com/go-openapi/spec/bindata.go b/vendor/github.com/go-openapi/spec/bindata.go new file mode 100644 index 00000000..afc83850 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/bindata.go @@ -0,0 +1,297 @@ +// Code generated by go-bindata. DO NOT EDIT. +// sources: +// schemas/jsonschema-draft-04.json (4.357kB) +// schemas/v2/schema.json (40.248kB) + +package spec + +import ( + "bytes" + "compress/gzip" + "crypto/sha256" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo + digest [sha256.Size]byte +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _jsonschemaDraft04Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x57\x3d\x6f\xdb\x3c\x10\xde\xf3\x2b\x08\x26\x63\xf2\x2a\x2f\xd0\xc9\x5b\xd1\x2e\x01\x5a\x34\x43\x37\x23\x03\x6d\x9d\x6c\x06\x14\xa9\x50\x54\x60\xc3\xd0\x7f\x2f\x28\x4a\x14\x29\x91\x92\x2d\xa7\x8d\x97\x28\xbc\xaf\xe7\x8e\xf7\xc5\xd3\x0d\x42\x08\x61\x9a\xe2\x15\xc2\x7b\xa5\x8a\x55\x92\xbc\x96\x82\x3f\x94\xdb\x3d\xe4\xe4\x3f\x21\x77\x49\x2a\x49\xa6\x1e\x1e\xbf\x24\xe6\xec\x16\xdf\x1b\xa1\x3b\xf3\xff\x02\xc9\x14\xca\xad\xa4\x85\xa2\x82\x6b\xe9\x6f\x42\x02\x32\x2c\x28\x07\x45\x5a\x15\x3d\x77\x46\x39\xd5\xcc\x25\x5e\x21\x83\xb8\x21\x18\xb6\xaf\x52\x92\xa3\x47\x68\x88\xea\x58\x80\x56\x4e\x1a\xf2\xbd\x4f\xcc\x29\x7f\x52\x90\x6b\x7d\xff\x0f\x48\xb4\x3d\x3f\x21\x7c\x27\x21\xd3\x2a\x6e\x31\xaa\x2d\x53\xdd\xf3\xe3\x42\x94\x54\xd1\x77\x78\xe2\x0a\x76\x20\xe3\x20\x68\xcb\x30\x86\x41\xf3\x2a\xc7\x2b\xf4\x78\x8e\xfe\xef\x90\x91\x8a\xa9\xc7\xb1\x1d\xc2\xd8\x2f\x0d\x75\xed\xc1\x4e\x9c\xc8\x25\x43\xac\xa8\xbe\xd7\xcc\xa9\xd1\xa9\x21\xa0\x1a\xbd\x04\x61\x94\x34\x2f\x18\xfc\x3e\x16\x50\x8e\x4d\x03\x6f\x1c\x58\xdb\x48\x23\xbc\x11\x82\x01\xe1\xfa\xd3\x3a\x8e\x30\xaf\x18\x33\x7f\xf3\x8d\x39\x11\x9b\x57\xd8\x2a\xfd\x55\x2a\x49\xf9\x0e\xc7\xec\x37\xd4\x25\xf7\xec\x5c\x66\xc7\xd7\x99\xaa\xcf\x4f\x89\x8a\xd3\xb7\x0a\x3a\xaa\x92\x15\xf4\x30\x6f\x1c\xb0\xd6\x46\xe7\x98\x39\x2d\xa4\x28\x40\x2a\x3a\x88\x9e\x29\xba\x88\x37\x2d\xca\x60\x38\xfa\xba\x5b\x20\xac\xa8\x62\xb0\x4c\xd4\xaf\xda\x45\x0a\xba\x5c\x3b\xb9\xc7\x79\xc5\x14\x2d\x18\x34\x19\x1c\x51\xdb\x25\x4d\xb4\x7e\x06\x14\x38\x6c\x59\x55\xd2\x77\xf8\x69\x59\xfc\x7b\x73\xed\x93\x43\xcb\x32\x6d\x3c\x28\xdc\x1b\x9a\xd3\x62\xab\xc2\x27\xf7\x41\xc9\x08\x2b\x23\x08\xad\x13\x57\x21\x9c\xd3\x72\x0d\x42\x72\xf8\x01\x7c\xa7\xf6\x83\xce\x39\xd7\x82\x3c\x1f\x2f\xd6\x60\x1b\xa2\xdf\x35\x89\x52\x20\xe7\x73\x74\xe0\x66\x26\x64\x4e\xb4\x97\x58\xc2\x0e\x0e\xe1\x60\x92\x34\x6d\xa0\x10\xd6\xb5\x83\x61\x27\xe6\x47\xd3\x89\xbd\x63\xfd\x3b\x8d\x03\x3d\x6c\x42\x2d\x5b\x70\xee\xe8\xdf\x4b\xf4\x66\x4e\xe1\x01\x45\x17\x80\x74\xad\x4f\xc3\xf3\xae\xc6\x1d\xc6\xd7\xc2\xce\xc9\xe1\x29\x30\x86\x2f\x4a\xa6\x4b\x15\x84\x73\xc9\x6f\xfd\x7f\xa5\x6e\x9e\xbd\xf1\xb0\xd4\xdd\x45\x5a\xc2\x3e\x4b\x78\xab\xa8\x84\x74\x4a\x91\x3b\x92\x23\x05\xf2\x1c\x1e\x7b\xf3\x09\xf8\xcf\xab\x24\xb6\x60\xa2\xe8\x4c\x9f\x75\x77\xaa\x8c\xe6\x01\x45\x36\x86\xcf\xc3\x63\x3a\xea\xd4\x8d\x7e\x06\xac\x14\x0a\xe0\x29\xf0\xed\x07\x22\x1a\x65\xda\x44\xae\xa2\x73\x1a\xe6\x90\x69\xa2\x8c\x46\xb2\x2f\xde\x49\x38\x08\xed\xfe\xfd\x41\xaf\x9f\xa9\x55\xd7\xdd\x22\x8d\xfa\x45\x63\xc5\x0f\x80\xf3\xb4\x08\xd6\x79\x30\x9e\x93\xee\x59\xa6\xd0\x4b\xee\x22\xe3\x33\xc1\x3a\x27\x68\x36\x78\x7e\x87\x0a\x06\xd5\x2e\x20\xd3\xaf\x15\xfb\xd8\x3b\x73\x14\xbb\x92\xed\x05\x5d\x2e\x29\x38\x2c\x94\xe4\x42\x45\x5e\xd3\xb5\x7d\xdf\x47\xca\x38\xb4\x5c\xaf\xfb\x7d\xdd\x6d\xf4\xa1\x2d\x77\xdd\x2f\xce\x6d\xc4\x7b\x8b\x4e\x67\xa9\x6f\xfe\x04\x00\x00\xff\xff\xb1\xd1\x27\x78\x05\x11\x00\x00") + +func jsonschemaDraft04JsonBytes() ([]byte, error) { + return bindataRead( + _jsonschemaDraft04Json, + "jsonschema-draft-04.json", + ) +} + +func jsonschemaDraft04Json() (*asset, error) { + bytes, err := jsonschemaDraft04JsonBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "jsonschema-draft-04.json", size: 4357, mode: os.FileMode(0640), modTime: time.Unix(1568963823, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe1, 0x48, 0x9d, 0xb, 0x47, 0x55, 0xf0, 0x27, 0x93, 0x30, 0x25, 0x91, 0xd3, 0xfc, 0xb8, 0xf0, 0x7b, 0x68, 0x93, 0xa8, 0x2a, 0x94, 0xf2, 0x48, 0x95, 0xf8, 0xe4, 0xed, 0xf1, 0x1b, 0x82, 0xe2}} + return a, nil +} + +var _v2SchemaJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5d\x4f\x93\xdb\x36\xb2\xbf\xfb\x53\xa0\x14\x57\xd9\xae\xd8\x92\xe3\xf7\x2e\xcf\x97\xd4\xbc\xd8\x49\x66\x37\x5e\x4f\x79\x26\xbb\x87\x78\x5c\x05\x91\x2d\x09\x09\x09\x30\x00\x38\x33\x5a\xef\x7c\xf7\x2d\xf0\x9f\x08\x02\x20\x41\x8a\xd2\xc8\x0e\x0f\xa9\x78\x28\xa0\xd1\xdd\x68\x34\x7e\xdd\xf8\xf7\xf9\x11\x42\x33\x49\x64\x04\xb3\xd7\x68\x76\x86\xfe\x76\xf9\xfe\x1f\xe8\x32\xd8\x40\x8c\xd1\x8a\x71\x74\x79\x8b\xd7\x6b\xe0\xe8\xd5\xfc\x25\x3a\xbb\x38\x9f\xcf\x9e\xab\x0a\x24\x54\xa5\x37\x52\x26\xaf\x17\x0b\x91\x17\x99\x13\xb6\xb8\x79\xb5\x10\x59\xdd\xf9\xef\x82\xd1\x6f\xf2\xc2\x8f\xf3\x4f\xb5\x1a\xea\xc7\x17\x45\x41\xc6\xd7\x8b\x90\xe3\x95\x7c\xf1\xf2\x7f\x8b\xca\x45\x3d\xb9\x4d\x32\xa6\xd8\xf2\x77\x08\x64\xfe\x8d\xc3\x9f\x29\xe1\xa0\x9a\xff\xed\x11\x42\x08\xcd\x8a\xd6\xb3\x9f\x15\x67\x74\xc5\xca\x7f\x27\x58\x6e\xc4\xec\x11\x42\xd7\x59\x5d\x1c\x86\x44\x12\x46\x71\x74\xc1\x59\x02\x5c\x12\x10\xb3\xd7\x68\x85\x23\x01\x59\x81\x04\x4b\x09\x9c\x6a\xbf\x7e\xce\x49\x7d\xba\x7b\x51\xfd\xa1\x44\xe2\xb0\x52\xac\x7d\xb3\x08\x61\x45\x68\x46\x56\x2c\x6e\x80\x86\x8c\xbf\xbd\x93\x40\x05\x61\x74\x96\x95\xbe\x7f\x84\xd0\x7d\x4e\xde\x42\xb7\xe4\xbe\x46\xbb\x14\x5b\x48\x4e\xe8\xba\x90\x05\xa1\x19\xd0\x34\xae\xc4\xce\xbe\xbc\x9a\xbf\x9c\x15\x7f\x5d\x57\xc5\x42\x10\x01\x27\x89\xe2\x48\x51\xb9\xda\x40\xd5\x87\x37\xc0\x15\x5f\x88\xad\x90\xdc\x10\x81\x42\x16\xa4\x31\x50\x39\x2f\x38\xad\xab\xb0\x53\xd8\xac\x94\x56\x6f\xc3\x84\xf4\x11\xa4\x50\xb3\xfa\xe9\xd3\x6f\x9f\x3e\xdf\x2f\xd0\xeb\x8f\x1f\x3f\x7e\xbc\xfe\xf6\xe9\xf7\xaf\x5f\x7f\xfc\x18\x7e\xfb\xec\xfb\xc7\xb3\x36\x79\x54\x43\xe8\x29\xc5\x31\x20\xc6\x11\x49\x9e\xe5\x12\x41\x66\xa0\xe8\xed\x1d\x8e\x93\x08\x5e\xa3\x27\x3b\xc3\x7c\xa2\x73\xba\xc4\x02\x2e\xb0\xdc\xf4\xe5\x76\xd1\xca\x96\xa2\x8a\x94\xcd\x21\xc9\x6c\xec\x2c\x70\x42\x9e\x34\x74\x9d\x19\x7c\xcd\x20\x9c\xea\x2e\x0a\xfe\x42\x84\xd4\x29\x04\x8c\x8a\xb4\x41\xa2\xc1\xdc\x19\x8a\x88\x90\x4a\x49\xef\xce\xdf\xbd\x45\x4a\x52\x81\x70\x10\x40\x22\x21\x44\xcb\x6d\xc5\xec\x4e\x3c\x1c\x45\xef\x57\x9a\xb5\x7d\xae\xfe\xe5\xe4\x31\x86\x90\xe0\xab\x6d\x02\x3b\x2e\xcb\x11\x90\xd9\xa8\xc6\x77\xc2\x59\x98\x06\xfd\xf9\x2e\x78\x45\x01\xa6\xa8\xa0\x71\x5c\xbe\x33\xa7\xd2\xd9\x5f\x95\xef\xd9\xd5\xac\xfd\xdc\x5d\xbf\x5e\xb8\xd1\x3e\xc7\x31\x48\xe0\x5e\x4c\x14\x65\xdf\xb8\xa8\x71\x10\x09\xa3\xc2\xc7\x02\xcb\xa2\x4e\x5a\x02\x82\x94\x13\xb9\xf5\x30\xe6\xb2\xa4\xb5\xfe\x9b\x3e\x7a\xb2\x55\xd2\xa8\x4a\xbc\x16\xb6\x71\x8e\x39\xc7\xdb\x9d\xe1\x10\x09\x71\xbd\x9c\xb3\x41\x89\xd7\xa5\x89\xdc\x57\xb5\x53\x4a\xfe\x4c\xe1\xbc\xa0\x21\x79\x0a\x1a\x0f\x70\xa7\x5c\x08\x8e\xde\xb0\xc0\x43\x24\xad\x74\x63\x0e\xb1\xd9\x90\xe1\xb0\x2d\x13\xa7\x6d\x78\xfd\x04\x14\x38\x8e\x90\xaa\xce\x63\xac\x3e\x23\xbc\x64\xa9\xb4\xf8\x03\x63\xde\xcd\xbe\x16\x13\x4a\x55\xac\x82\x12\xc6\xac\xd4\x35\xf7\x22\xd4\x3a\xff\x22\x73\x0e\x6e\x51\xa0\x75\x1e\xae\x8f\xe8\x5d\xc7\x59\xe6\xe4\x9a\x18\x8d\xd6\x1c\x53\x84\x4d\xb7\x67\x28\x37\x09\x84\x69\x88\x12\x0e\x01\x11\x80\x32\xa2\xf5\xb9\xaa\xc6\xd9\x73\x53\xab\xfb\xb4\x2e\x20\xc6\x54\x92\xa0\x9a\xf3\x69\x1a\x2f\x81\x77\x37\xae\x53\x1a\xce\x40\xc4\xa8\x82\x1c\xb5\xef\xda\x24\x7d\xb9\x61\x69\x14\xa2\x25\xa0\x90\xac\x56\xc0\x81\x4a\xb4\xe2\x2c\xce\x4a\x64\x7a\x9a\x23\xf4\x13\x91\x3f\xa7\x4b\xf4\x63\x84\x6f\x18\x87\x10\xbd\xc3\xfc\x8f\x90\xdd\x52\x44\x04\xc2\x51\xc4\x6e\x21\x74\x48\x21\x81\xc7\xe2\xfd\xea\x12\xf8\x0d\x09\xf6\xe9\x47\x35\xaf\x67\xc4\x14\xf7\x22\x27\x97\xe1\xe2\x76\x2d\x06\x8c\x4a\x1c\x48\x3f\x73\x2d\x0b\x5b\x29\x45\x24\x00\x2a\x0c\x11\xec\x94\xca\xc2\xa6\xc1\x37\x21\x43\x83\x3b\x5f\x97\xf1\x43\x5e\x53\x73\x19\xa5\x36\xd8\x2d\x05\x2e\x34\x0b\xeb\x39\xfc\x1d\x63\x51\x01\xbd\x3d\xbb\x90\x84\x40\x25\x59\x6d\x09\x5d\xa3\x1c\x37\xe6\x5c\x16\x9a\x40\x09\x70\xc1\xe8\x82\xf1\x35\xa6\xe4\xdf\x99\x5c\x8e\x9e\x4d\x79\xb4\x27\x2f\xbf\x7e\xf8\x05\x25\x8c\x50\xa9\x98\x29\x90\x62\x60\xea\x75\xae\x13\xca\xbf\x2b\x1a\x29\x27\x76\xd6\x20\xc6\x64\x5f\xe6\x32\x1a\x08\x87\x21\x07\x21\xbc\xb4\xe4\xe0\x32\x67\xa6\xcd\xf3\x1e\xcd\xd9\x6b\xb6\x6f\x8e\x27\xa7\xed\xdb\xe7\xbc\xcc\x1a\x07\xce\x6f\x87\x33\xf0\xba\x51\x17\x22\x66\x78\x79\x8e\xce\xe5\x13\x81\x80\x06\x2c\xe5\x78\x0d\xa1\xb2\xb8\x54\xa8\x79\x09\xbd\xbf\x3c\x47\x01\x8b\x13\x2c\xc9\x32\xaa\xaa\x1d\xd5\xee\xab\x36\xbd\x6c\xfd\x54\x6c\xc8\x08\x01\x3c\xbd\xe7\x07\x88\xb0\x24\x37\x79\x90\x28\x4a\x1d\x10\x1a\x92\x1b\x12\xa6\x38\x42\x40\xc3\x4c\x43\x62\x8e\xae\x36\xb0\x45\x71\x2a\xa4\x9a\x23\x79\x59\xb1\xa8\xf2\xa4\x0c\x60\x9f\xcc\x8d\x40\xf5\x80\xca\xa8\x99\xc3\xa7\x85\x1f\x31\x25\xa9\x82\xc5\x6d\xbd\xd8\x36\x76\x7c\x02\x28\x97\xf6\x1d\x74\x3b\x11\x7e\x91\xae\x32\xf8\x6c\xf4\xe6\x7b\x9a\xa5\x1f\x62\xc6\x21\xcf\x9a\xe5\xed\x8b\x02\xf3\x2c\x33\x33\xdf\x00\xca\xc9\x09\xb4\x04\xf5\xa5\x08\xd7\xc3\x02\x18\x66\xf1\xab\x1e\x83\x37\x4c\xcd\x12\xc1\x1d\x50\xf6\xaa\xbd\xfe\xe2\x73\x48\x38\x08\xa0\x32\x9b\x18\x44\x86\x0b\x6a\xc1\xaa\x26\x96\x2d\x96\x3c\xa0\x54\x65\x73\xe3\x08\xb5\x8b\x99\xbd\x82\xbc\x9e\xc2\xe8\x53\x46\x83\x3f\x33\x54\x2b\x5b\xad\x92\x79\xd9\x8f\x5d\x93\x98\xf2\xe6\xc6\x1c\xe6\x9a\x9e\xfc\x43\x82\x31\x66\x8e\x53\x77\xfe\x90\xe7\xf3\xf6\xe9\x62\x23\x3f\x10\x93\x18\xae\x72\x1a\x9d\xf9\x48\xcb\xcc\x5a\x65\xc7\x4a\x04\xf0\xf3\xd5\xd5\x05\x8a\x41\x08\xbc\x86\x86\x43\x51\x6c\xe0\x46\x57\xf6\x44\x40\x0d\xfb\xff\xa2\xc3\x7c\x3d\x39\x84\xdc\x09\x22\x64\x4f\x12\xd9\xba\xaa\xf6\xe3\xbd\x56\xdd\x91\x25\x6a\x14\x9c\x89\x34\x8e\x31\xdf\xee\x15\x7e\x2f\x39\x81\x15\x2a\x28\x95\x66\x51\xf5\xfd\x83\xc5\xfe\x15\x07\xcf\xf7\x08\xee\x1d\x8e\xb6\xc5\x52\xcc\x8c\x5a\x93\x66\xc5\xd8\x79\x38\x46\xd6\xa7\x88\x37\xc9\x2e\xe3\xd2\xa5\x7b\x4b\x3a\xdc\xa1\xdc\x9e\x29\xf1\x8c\x8a\x99\x16\x47\x8d\xd4\x78\x8b\xf6\x1c\xe9\x71\x54\x1b\x69\xa8\x4a\x93\x37\xe5\xb2\x2c\x4f\x0c\x92\xab\xa0\x73\x32\x72\x59\xd3\xf0\x2d\x8d\xed\xca\x37\x16\x19\x9e\xdb\x1c\xab\x17\x49\xc3\x0f\x37\xdc\x88\xb1\xb4\xd4\x42\xcb\x58\x5e\x6a\x52\x0b\x15\x10\x0a\xb0\x04\xe7\xf8\x58\x32\x16\x01\xa6\xcd\x01\xb2\xc2\x69\x24\x35\x38\x6f\x30\x6a\xae\x1b\xb4\x71\xaa\xad\x1d\xa0\xd6\x20\x2d\x8b\x3c\xc6\x82\x62\x27\x34\x6d\x15\x84\x7b\x43\xb1\x35\x78\xa6\x24\x77\x28\xc1\x6e\xfc\xe9\x48\x74\xf4\x15\xe3\xe1\x84\x42\x88\x40\x7a\x26\x49\x3b\x48\xb1\xa4\x19\x8e\x0c\xa7\xb5\x01\x6c\x0c\x97\x61\x8a\xc2\x32\xd8\x8c\x44\x69\x24\xbf\x65\x1d\x74\xd6\xe5\x44\xef\xec\x48\x5e\xb7\x8a\xa3\x29\x8e\x41\x64\xce\x1f\x88\xdc\x00\x47\x4b\x40\x98\x6e\xd1\x0d\x8e\x48\x98\x63\x5c\x21\xb1\x4c\x05\x0a\x58\x98\xc5\x6d\x4f\x0a\x77\x53\x4f\x8b\xc4\x44\x1f\xb2\xdf\x8d\x3b\xea\x9f\xfe\xf6\xf2\xc5\xff\x5d\x7f\xfe\x9f\xfb\x67\x8f\xff\xf3\xe9\x69\xd1\xfe\xb3\xc7\xfd\x3c\xf8\x3f\x71\x94\x82\x23\xd1\x72\x00\xb7\x42\x99\x6c\xc0\x60\x7b\x0f\x79\xea\xa8\x53\x4b\x56\x31\xfa\x0b\x52\x9f\x96\xdb\xcd\x2f\xd7\x67\xcd\x04\x19\x85\xfe\xdb\x02\x9a\x59\x03\xad\x63\x3c\xea\xff\x2e\x18\xfd\x00\xd9\xe2\x56\x60\x59\x93\xb9\xb6\xb2\x3e\x3c\x2c\xab\x0f\xa7\xb2\x89\x43\xc7\xf6\xd5\xce\x2e\xad\xa6\xa9\xed\xa6\xc6\x5a\xb4\xa6\x67\xdf\x8c\x26\x7b\x50\x5a\x91\x08\x2e\x6d\xd4\x3a\xc1\x9d\xf2\xdb\xde\x1e\xb2\x2c\x6c\xa5\x64\xc9\x16\xb4\x90\xaa\x4a\xb7\x0c\xde\x13\xc3\x2a\x9a\x11\x9b\x7a\x1b\x3d\x95\x97\x37\x31\x6b\x69\x7e\x34\xc0\x67\x1f\x66\x19\x49\xef\xf1\x25\xf5\xac\x0e\xea\x0a\x28\x8d\x4d\x7e\xd9\x57\x4b\x49\xe5\xc6\xb3\x25\xfd\xe6\x57\x42\x25\xac\xcd\xcf\x36\x74\x8e\xca\x24\x47\xe7\x80\xa8\x92\x72\xbd\x3d\x84\x2d\x65\xe2\x82\x1a\x9c\xc4\x44\x92\x1b\x10\x79\x8a\xc4\x4a\x2f\x60\x51\x04\x81\xaa\xf0\xa3\x95\x27\xd7\x12\x7b\xa3\x96\x03\x45\x96\xc1\x8a\x07\xc9\xb2\xb0\x95\x52\x8c\xef\x48\x9c\xc6\x7e\x94\xca\xc2\x0e\x07\x12\x44\xa9\x20\x37\xf0\xae\x0f\x49\xa3\x96\x9d\x4b\x42\x7b\x70\x59\x14\xee\xe0\xb2\x0f\x49\xa3\x96\x4b\x97\xbf\x00\x5d\x4b\x4f\xfc\xbb\x2b\xee\x92\xb9\x17\xb5\xaa\xb8\x0b\x97\x17\x9b\x43\xfd\xd6\xc2\xb2\xc2\x2e\x29\xcf\xfd\x87\x4a\x55\xda\x25\x63\x1f\x5a\x65\x69\x2b\x2d\x3d\x67\xe9\x41\xae\x5e\xc1\x6e\x2b\xd4\xdb\x3e\xa8\xd3\x26\xd2\x48\x92\x24\xca\x61\x86\x8f\x8c\xbb\xf2\x8e\x91\xdf\x1f\x06\x19\x33\xf3\x03\x4d\xba\xcd\xe2\x2d\xfb\x69\xe9\x16\x15\x13\xd5\x56\x85\x4e\x3c\x5b\x8a\xbf\x25\x72\x83\xee\x5e\x20\x22\xf2\xc8\xaa\x7b\xdb\x8e\xe4\x29\x58\xca\x38\xb7\x3f\x2e\x59\xb8\xbd\xa8\x16\x16\xf7\xdb\x79\x51\x9f\x5a\xb4\x8d\x87\x3a\x6e\xbc\x3e\xc5\xb4\xcd\x58\xf9\xf5\x3c\xb9\x6f\x49\xaf\x57\xc1\xfa\x1c\x5d\x6d\x88\x8a\x8b\xd3\x28\xcc\xb7\xef\x10\x8a\x4a\x74\xa9\x4a\xa7\x62\xbf\x0d\x76\x23\x6f\x59\xd9\x31\xee\x40\x11\xfb\x28\xec\x8d\x22\x1c\x13\x5a\x64\x94\x23\x16\x60\xbb\xd2\x7c\xa0\x98\xb2\xe5\x6e\xbc\x54\x33\xe0\x3e\xb9\x52\x17\xdb\xb7\x1b\xc8\x12\x20\x8c\x23\xca\x64\x7e\x78\xa3\x62\x5b\x75\x56\xd9\x9e\x2a\x91\x27\xb0\x70\x34\x1f\x90\x89\xb5\x86\x73\x7e\x71\xda\x1e\xfb\x3a\x72\xdc\x5e\x79\x88\xcb\x74\x79\xd9\x64\xe4\xd4\xc2\x9e\xce\xb1\xfe\x85\x5a\xc0\xe9\x0c\x34\x3d\xd0\x43\xce\xa1\x36\x39\xd5\xa1\x4e\xf5\xf8\xb1\xa9\x23\x08\x75\x84\xac\x53\x6c\x3a\xc5\xa6\x53\x6c\x3a\xc5\xa6\x7f\xc5\xd8\xf4\x51\xfd\xff\x25\x4e\xfa\x33\x05\xbe\x9d\x60\xd2\x04\x93\x6a\x5f\x33\x9b\x98\x50\xd2\xe1\x50\x52\xc6\xcc\xdb\x38\x91\xdb\xe6\xaa\xa2\x8f\xa1\x6a\xa6\xd4\xc6\x56\xd6\x8c\x40\x02\x68\x48\xe8\x1a\xe1\x9a\xd9\x2e\xb7\x05\xc3\x34\xda\x2a\xbb\xcd\x12\x36\x98\x22\x50\x4c\xa1\x1b\xc5\xd5\x84\xf0\xbe\x24\x84\xf7\x2f\x22\x37\xef\x94\xd7\x9f\xa0\xde\x04\xf5\x26\xa8\x37\x41\x3d\x64\x40\x3d\xe5\xf2\xde\x60\x89\x27\xb4\x37\xa1\xbd\xda\xd7\xd2\x2c\x26\xc0\x37\x01\x3e\x1b\xef\x5f\x06\xe0\x6b\x7c\x5c\x91\x08\x26\x10\x38\x81\xc0\x09\x04\x76\x4a\x3d\x81\xc0\xbf\x12\x08\x4c\xb0\xdc\x7c\x99\x00\xd0\x75\x70\xb4\xf8\x5a\x7c\xea\xde\x3e\x39\x08\x30\x5a\x27\x35\xed\xb4\x65\xad\x69\x74\x10\x88\x79\xe2\x30\x52\x19\xd6\x04\x21\xa7\x95\xd5\x0e\x03\xf8\xda\x20\xd7\x84\xb4\x26\xa4\x35\x21\xad\x09\x69\x21\x03\x69\x51\x46\xff\xff\x18\x9b\x54\xed\x87\x47\x06\x9d\x4e\x73\x6e\x9a\xb3\xa9\xce\x83\x5e\x4b\xc6\x71\x20\x45\xd7\x72\xf5\x40\x72\x0e\x34\x6c\xf4\x6c\xf3\xba\x5e\x4b\x97\x0e\x52\xb8\xbe\x8b\x79\xa0\x10\x86\xa1\x75\xb0\x6f\xec\xc8\xf4\x3d\x4d\x7b\x86\xc2\x02\x31\x12\x51\xbf\x07\x94\xad\x10\xd6\x2e\x79\xcf\xe9\x1c\xf5\x1e\x31\x23\x5c\x18\xfb\x9c\xfb\x70\xe0\x62\xbd\xf7\xb5\x94\xcf\xf3\xf6\xfa\xc5\x4e\x9c\x85\x76\x1d\xae\x37\xbc\xde\xa3\x41\xcb\x29\xd0\x5e\x70\x67\x50\x93\x6d\x98\xa8\xd3\x67\x0f\x68\xb1\xeb\x38\x47\x07\x10\x1b\xd2\xe2\x18\x68\x6d\x40\xbb\xa3\x40\xba\x21\xf2\x8e\x81\xfb\xf6\x92\x77\x2f\x70\xe8\xdb\xb2\x36\xbf\x30\x91\xc5\x21\xe7\x45\xcc\x34\x0c\x48\x8e\xd0\xf2\x9b\x7c\x3c\xbd\x1c\x04\x3e\x07\xe8\x7c\x2f\x84\x7a\x48\x4d\x1f\xba\xe1\x76\x45\x7b\x60\xe0\x01\xca\xee\x04\xca\x31\xbe\x73\x5f\xa3\x70\x0c\xad\x1f\xa5\xf5\x76\xd5\xbb\xd2\x7e\xfb\x30\x90\xcf\xfa\x67\x7a\xe6\xc3\x37\x42\x19\xe2\xc9\x9c\x61\x4c\xe7\xd1\x77\x55\x86\x6e\x8f\x7b\x85\x42\x33\xa3\xaa\x57\xae\xfd\xd5\xcc\x9c\x56\x68\xe2\xde\x0e\xa8\x2c\xa9\xb0\x7d\xf0\x54\x2d\x80\xf2\x48\x39\x3d\x98\x1a\x6d\x0b\x9d\xba\x53\xfb\xce\xf8\xd1\x7e\xbb\x60\x4f\x06\xf5\xce\xda\xab\xeb\xca\xcb\xd5\xac\x20\xda\x72\x3b\xa2\x4b\x38\xd7\xb5\x89\xbe\x42\xd9\xb9\x73\xc4\x0c\x6d\xb7\xd9\xf8\x8d\xbd\x3e\x9c\xf5\x53\x68\x48\x14\x36\x8f\x09\xc5\x92\xf1\x21\xd1\x09\x07\x1c\xbe\xa7\x91\xf3\x6a\xc8\xc1\x57\xb0\xdd\xc5\xc6\x1d\xad\x76\x1d\xa8\x82\x0e\x4c\x38\xfe\xa5\x8c\xc5\x0a\x40\x5d\xa1\xbb\x98\xd1\xfb\x74\x61\xed\x1a\x98\xaf\x3c\x8c\x1e\xe3\xc2\x92\x29\x74\x3e\x99\xd0\xf9\x41\x50\xd0\x38\x4b\x57\x7e\x5b\x7a\x0e\xe6\xce\x4e\xd7\x19\x35\x57\xbb\x3c\x3c\xd2\x5e\x4f\x4b\x4c\xf7\x0f\x4d\x2b\x91\x5d\x94\xa6\x95\xc8\x69\x25\x72\x5a\x89\x7c\xb8\x95\xc8\x07\x80\x8c\xda\x9c\x64\x7b\xb7\x71\xdf\x57\x12\x4b\x9a\x1f\x72\x0c\x13\x03\xad\x3c\xd5\x4e\xde\x8e\x57\x13\x6d\x34\x86\xcf\x97\xe6\xa4\x68\xc4\xb0\xf6\xc9\xc2\xeb\x8d\x0b\xd7\xcd\xfe\xba\xa6\xf5\x30\xeb\x30\x33\xbe\xc7\x56\x27\xab\x08\xd9\x6d\xbb\x09\xee\x7c\x2d\xcf\xee\x87\x38\xac\xc8\xdd\x90\x9a\x58\x4a\x4e\x96\xa9\x79\x79\xf3\xde\x20\xf0\x96\xe3\x24\x19\xeb\xba\xf2\x53\x19\xab\x12\xaf\x47\xb3\xa0\x3e\xef\x9b\x8d\x6d\x6d\x7b\xde\x3b\x3b\x1a\xc0\x3f\x95\x7e\xed\x78\xfb\x76\xb8\xaf\xb3\xdd\xc5\xeb\x95\xed\x5a\x62\x41\x82\xb3\x54\x6e\x80\x4a\x92\x6f\x36\xbd\x34\xae\xde\x6f\xa4\xc0\xbc\x08\xe3\x84\xfc\x1d\xb6\xe3\xd0\x62\x38\x95\x9b\x57\xe7\x71\x12\x91\x80\xc8\x31\x69\x5e\x60\x21\x6e\x19\x0f\xc7\xa4\x79\x96\x28\x3e\x47\x54\x65\x41\x36\x08\x40\x88\x1f\x58\x08\x56\xaa\xd5\xbf\xaf\xad\x96\xd7\xd6\xcf\x87\xf5\x34\x0f\x71\x93\x6e\x26\xed\x98\x5b\x9f\x4f\xcf\x95\x34\xc6\xd7\x11\xfa\xb0\x81\x22\x1a\xdb\xdf\x8e\xdc\xc3\xb9\xf8\xdd\x5d\x3c\x74\xe6\xea\xb7\x8b\xbf\xf5\x6e\xb3\x46\x2e\x64\xf4\xab\x3c\x4e\xcf\x36\x1d\xfe\xfa\xb8\x36\xba\x8a\xd8\xad\xf6\xc6\x41\x2a\x37\x8c\x17\x0f\xda\xfe\xda\xe7\x65\xbc\x71\x2c\x36\x57\x8a\x47\x12\x4c\xf1\xbd\x77\x6b\xa4\x50\x7e\x77\x7b\x22\x60\x89\xef\xcd\xf5\xb9\x0c\x97\x79\x0d\x2b\x35\x43\xcb\x3d\x24\xf1\x78\xfc\xf8\xcb\x1f\x15\x06\xe2\x78\xd8\x51\x21\xd9\x1f\xf0\xf5\x8f\x86\xa4\x50\xfa\xb1\x47\x43\xa5\xdd\x69\x14\xe8\xa3\xc0\x86\x91\xa7\x81\x50\xb4\x7c\xc0\x81\x80\x77\x7a\x9f\xc6\xc2\xa9\x8c\x05\x33\xb0\x3b\x31\xa4\xf4\xd7\x1b\x26\x55\x97\x7c\x65\xf8\x69\x1a\x84\x8e\x41\x78\xd9\xec\xc5\x11\x16\x1e\x74\x91\xf5\x56\xf5\x57\x49\x47\x5c\x92\xa9\x1e\x99\x36\xf4\xdb\xb1\x0e\xd3\x78\x02\xb0\x9b\x25\xcb\xe9\xe9\x1d\x0d\x44\x01\x42\x08\x91\x64\xd9\xdd\x37\x08\x17\xef\xf9\xe5\x0f\xbd\x46\x91\xf5\xf9\x89\x92\x37\xdd\x89\x59\x44\x1f\x9c\xee\x34\x1e\xbe\x47\x83\x32\x72\x8e\x37\xdf\xac\x69\x38\xef\x75\xb0\xda\xdb\xac\x83\x94\x2f\x39\xa6\x62\x05\x1c\x25\x9c\x49\x16\xb0\xa8\x3c\xc7\x7e\x76\x71\x3e\x6f\xb5\x24\xe7\xe8\xb7\xb9\xc7\x6c\x43\x92\xee\x21\xd4\x17\xa1\x7f\xba\x35\xfe\xae\x39\xbc\xde\xba\x69\xd9\x8e\xe1\x62\xde\x64\x7d\x16\x88\x1b\xed\x29\x11\xfd\x4f\xa9\xff\x99\x90\xc4\xf6\xf4\xf9\x6e\xe9\x28\x23\xd7\xca\xe5\xee\xee\x9f\x63\xb1\x5b\xfb\x10\xd7\x2f\x1d\xf2\xe3\xbf\xb9\xb5\x6f\xa4\x6d\x7d\x25\x79\xfb\x24\x31\xea\x56\xbe\x5d\x53\xcd\x2d\x36\xa3\x6d\xdf\xab\x1c\xb8\x6d\x6f\xc0\x98\xa7\xdd\xaa\x86\x8c\x1d\x39\xa3\x9d\x70\x2b\x9b\x68\xd9\xfd\x33\xfe\xa9\xb6\x4a\x2e\x63\x0f\xcf\x68\x27\xd9\x4c\xb9\x46\x6d\xcb\xbe\xa1\xa8\xd6\x5f\xc6\xd6\x9f\xf1\x4f\xf4\xd4\xb4\x78\xd0\xd6\xf4\x13\x3c\x3b\xac\xd0\xdc\x90\x34\xda\xc9\xb4\x9a\x1a\x8d\xbd\x93\x87\xd4\xe2\x21\x1b\xb3\x2b\xd1\xbe\xe7\x69\xd4\x53\x67\xd5\x40\xa0\xe3\x19\x3f\x6d\x1a\xbc\x0e\x86\x3c\x10\xb4\x3d\x2a\xcd\x78\x32\xe6\xab\xbd\x36\xc9\xf4\x3a\x58\xae\xc3\xf4\x47\xea\xbf\xfb\x47\xff\x0d\x00\x00\xff\xff\xd2\x32\x5a\x28\x38\x9d\x00\x00") + +func v2SchemaJsonBytes() ([]byte, error) { + return bindataRead( + _v2SchemaJson, + "v2/schema.json", + ) +} + +func v2SchemaJson() (*asset, error) { + bytes, err := v2SchemaJsonBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "v2/schema.json", size: 40248, mode: os.FileMode(0640), modTime: time.Unix(1568964748, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xab, 0x88, 0x5e, 0xf, 0xbf, 0x17, 0x74, 0x0, 0xb2, 0x5a, 0x7f, 0xbc, 0x58, 0xcd, 0xc, 0x25, 0x73, 0xd5, 0x29, 0x1c, 0x7a, 0xd0, 0xce, 0x79, 0xd4, 0x89, 0x31, 0x27, 0x90, 0xf2, 0xff, 0xe6}} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + canonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[canonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// AssetString returns the asset contents as a string (instead of a []byte). +func AssetString(name string) (string, error) { + data, err := Asset(name) + return string(data), err +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// MustAssetString is like AssetString but panics when Asset would return an +// error. It simplifies safe initialization of global variables. +func MustAssetString(name string) string { + return string(MustAsset(name)) +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + canonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[canonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetDigest returns the digest of the file with the given name. It returns an +// error if the asset could not be found or the digest could not be loaded. +func AssetDigest(name string) ([sha256.Size]byte, error) { + canonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[canonicalName]; ok { + a, err := f() + if err != nil { + return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err) + } + return a.digest, nil + } + return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name) +} + +// Digests returns a map of all known files and their checksums. +func Digests() (map[string][sha256.Size]byte, error) { + mp := make(map[string][sha256.Size]byte, len(_bindata)) + for name := range _bindata { + a, err := _bindata[name]() + if err != nil { + return nil, err + } + mp[name] = a.digest + } + return mp, nil +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "jsonschema-draft-04.json": jsonschemaDraft04Json, + + "v2/schema.json": v2SchemaJson, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"}, +// AssetDir("data/img") would return []string{"a.png", "b.png"}, +// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + canonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(canonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "jsonschema-draft-04.json": {jsonschemaDraft04Json, map[string]*bintree{}}, + "v2": {nil, map[string]*bintree{ + "schema.json": {v2SchemaJson, map[string]*bintree{}}, + }}, +}} + +// RestoreAsset restores an asset under the given directory. +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) +} + +// RestoreAssets restores an asset under the given directory recursively. +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + canonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...) +} diff --git a/vendor/github.com/go-openapi/spec/cache.go b/vendor/github.com/go-openapi/spec/cache.go new file mode 100644 index 00000000..122993b4 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/cache.go @@ -0,0 +1,98 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "sync" +) + +// ResolutionCache a cache for resolving urls +type ResolutionCache interface { + Get(string) (interface{}, bool) + Set(string, interface{}) +} + +type simpleCache struct { + lock sync.RWMutex + store map[string]interface{} +} + +func (s *simpleCache) ShallowClone() ResolutionCache { + store := make(map[string]interface{}, len(s.store)) + s.lock.RLock() + for k, v := range s.store { + store[k] = v + } + s.lock.RUnlock() + + return &simpleCache{ + store: store, + } +} + +// Get retrieves a cached URI +func (s *simpleCache) Get(uri string) (interface{}, bool) { + s.lock.RLock() + v, ok := s.store[uri] + + s.lock.RUnlock() + return v, ok +} + +// Set caches a URI +func (s *simpleCache) Set(uri string, data interface{}) { + s.lock.Lock() + s.store[uri] = data + s.lock.Unlock() +} + +var ( + // resCache is a package level cache for $ref resolution and expansion. + // It is initialized lazily by methods that have the need for it: no + // memory is allocated unless some expander methods are called. + // + // It is initialized with JSON schema and swagger schema, + // which do not mutate during normal operations. + // + // All subsequent utilizations of this cache are produced from a shallow + // clone of this initial version. + resCache *simpleCache + onceCache sync.Once + + _ ResolutionCache = &simpleCache{} +) + +// initResolutionCache initializes the URI resolution cache. To be wrapped in a sync.Once.Do call. +func initResolutionCache() { + resCache = defaultResolutionCache() +} + +func defaultResolutionCache() *simpleCache { + return &simpleCache{store: map[string]interface{}{ + "http://swagger.io/v2/schema.json": MustLoadSwagger20Schema(), + "http://json-schema.org/draft-04/schema": MustLoadJSONSchemaDraft04(), + }} +} + +func cacheOrDefault(cache ResolutionCache) ResolutionCache { + onceCache.Do(initResolutionCache) + + if cache != nil { + return cache + } + + // get a shallow clone of the base cache with swagger and json schema + return resCache.ShallowClone() +} diff --git a/vendor/github.com/go-openapi/spec/contact_info.go b/vendor/github.com/go-openapi/spec/contact_info.go new file mode 100644 index 00000000..2f7bb219 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/contact_info.go @@ -0,0 +1,57 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + + "github.com/go-openapi/swag" +) + +// ContactInfo contact information for the exposed API. +// +// For more information: http://goo.gl/8us55a#contactObject +type ContactInfo struct { + ContactInfoProps + VendorExtensible +} + +// ContactInfoProps hold the properties of a ContactInfo object +type ContactInfoProps struct { + Name string `json:"name,omitempty"` + URL string `json:"url,omitempty"` + Email string `json:"email,omitempty"` +} + +// UnmarshalJSON hydrates ContactInfo from json +func (c *ContactInfo) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &c.ContactInfoProps); err != nil { + return err + } + return json.Unmarshal(data, &c.VendorExtensible) +} + +// MarshalJSON produces ContactInfo as json +func (c ContactInfo) MarshalJSON() ([]byte, error) { + b1, err := json.Marshal(c.ContactInfoProps) + if err != nil { + return nil, err + } + b2, err := json.Marshal(c.VendorExtensible) + if err != nil { + return nil, err + } + return swag.ConcatJSON(b1, b2), nil +} diff --git a/vendor/github.com/go-openapi/spec/debug.go b/vendor/github.com/go-openapi/spec/debug.go new file mode 100644 index 00000000..fc889f6d --- /dev/null +++ b/vendor/github.com/go-openapi/spec/debug.go @@ -0,0 +1,49 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "fmt" + "log" + "os" + "path" + "runtime" +) + +// Debug is true when the SWAGGER_DEBUG env var is not empty. +// +// It enables a more verbose logging of this package. +var Debug = os.Getenv("SWAGGER_DEBUG") != "" + +var ( + // specLogger is a debug logger for this package + specLogger *log.Logger +) + +func init() { + debugOptions() +} + +func debugOptions() { + specLogger = log.New(os.Stdout, "spec:", log.LstdFlags) +} + +func debugLog(msg string, args ...interface{}) { + // A private, trivial trace logger, based on go-openapi/spec/expander.go:debugLog() + if Debug { + _, file1, pos1, _ := runtime.Caller(1) + specLogger.Printf("%s:%d: %s", path.Base(file1), pos1, fmt.Sprintf(msg, args...)) + } +} diff --git a/vendor/github.com/go-openapi/spec/errors.go b/vendor/github.com/go-openapi/spec/errors.go new file mode 100644 index 00000000..6992c7ba --- /dev/null +++ b/vendor/github.com/go-openapi/spec/errors.go @@ -0,0 +1,19 @@ +package spec + +import "errors" + +// Error codes +var ( + // ErrUnknownTypeForReference indicates that a resolved reference was found in an unsupported container type + ErrUnknownTypeForReference = errors.New("unknown type for the resolved reference") + + // ErrResolveRefNeedsAPointer indicates that a $ref target must be a valid JSON pointer + ErrResolveRefNeedsAPointer = errors.New("resolve ref: target needs to be a pointer") + + // ErrDerefUnsupportedType indicates that a resolved reference was found in an unsupported container type. + // At the moment, $ref are supported only inside: schemas, parameters, responses, path items + ErrDerefUnsupportedType = errors.New("deref: unsupported type") + + // ErrExpandUnsupportedType indicates that $ref expansion is attempted on some invalid type + ErrExpandUnsupportedType = errors.New("expand: unsupported type. Input should be of type *Parameter or *Response") +) diff --git a/vendor/github.com/go-openapi/spec/expander.go b/vendor/github.com/go-openapi/spec/expander.go new file mode 100644 index 00000000..d4ea889d --- /dev/null +++ b/vendor/github.com/go-openapi/spec/expander.go @@ -0,0 +1,594 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + "fmt" +) + +// ExpandOptions provides options for the spec expander. +// +// RelativeBase is the path to the root document. This can be a remote URL or a path to a local file. +// +// If left empty, the root document is assumed to be located in the current working directory: +// all relative $ref's will be resolved from there. +// +// PathLoader injects a document loading method. By default, this resolves to the function provided by the SpecLoader package variable. +// +type ExpandOptions struct { + RelativeBase string // the path to the root document to expand. This is a file, not a directory + SkipSchemas bool // do not expand schemas, just paths, parameters and responses + ContinueOnError bool // continue expanding even after and error is found + PathLoader func(string) (json.RawMessage, error) `json:"-"` // the document loading method that takes a path as input and yields a json document + AbsoluteCircularRef bool // circular $ref remaining after expansion remain absolute URLs +} + +func optionsOrDefault(opts *ExpandOptions) *ExpandOptions { + if opts != nil { + clone := *opts // shallow clone to avoid internal changes to be propagated to the caller + if clone.RelativeBase != "" { + clone.RelativeBase = normalizeBase(clone.RelativeBase) + } + // if the relative base is empty, let the schema loader choose a pseudo root document + return &clone + } + return &ExpandOptions{} +} + +// ExpandSpec expands the references in a swagger spec +func ExpandSpec(spec *Swagger, options *ExpandOptions) error { + options = optionsOrDefault(options) + resolver := defaultSchemaLoader(spec, options, nil, nil) + + specBasePath := options.RelativeBase + + if !options.SkipSchemas { + for key, definition := range spec.Definitions { + parentRefs := make([]string, 0, 10) + parentRefs = append(parentRefs, fmt.Sprintf("#/definitions/%s", key)) + + def, err := expandSchema(definition, parentRefs, resolver, specBasePath) + if resolver.shouldStopOnError(err) { + return err + } + if def != nil { + spec.Definitions[key] = *def + } + } + } + + for key := range spec.Parameters { + parameter := spec.Parameters[key] + if err := expandParameterOrResponse(¶meter, resolver, specBasePath); resolver.shouldStopOnError(err) { + return err + } + spec.Parameters[key] = parameter + } + + for key := range spec.Responses { + response := spec.Responses[key] + if err := expandParameterOrResponse(&response, resolver, specBasePath); resolver.shouldStopOnError(err) { + return err + } + spec.Responses[key] = response + } + + if spec.Paths != nil { + for key := range spec.Paths.Paths { + pth := spec.Paths.Paths[key] + if err := expandPathItem(&pth, resolver, specBasePath); resolver.shouldStopOnError(err) { + return err + } + spec.Paths.Paths[key] = pth + } + } + + return nil +} + +const rootBase = ".root" + +// baseForRoot loads in the cache the root document and produces a fake ".root" base path entry +// for further $ref resolution +// +// 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) + cache.Set(normalizedBase, root) + + return normalizedBase +} + +// ExpandSchema expands the refs in the schema object with reference to the root object. +// +// go-openapi/validate uses this function. +// +// Notice that it is impossible to reference a json schema in a different document other than root +// (use ExpandSchemaWithBasePath to resolve external references). +// +// Setting the cache is optional and this parameter may safely be left to nil. +func ExpandSchema(schema *Schema, root interface{}, cache ResolutionCache) error { + cache = cacheOrDefault(cache) + if root == nil { + root = schema + } + + opts := &ExpandOptions{ + // when a root is specified, cache the root as an in-memory document for $ref retrieval + RelativeBase: baseForRoot(root, cache), + SkipSchemas: false, + ContinueOnError: false, + } + + return ExpandSchemaWithBasePath(schema, cache, opts) +} + +// ExpandSchemaWithBasePath expands the refs in the schema object, base path configured through expand options. +// +// Setting the cache is optional and this parameter may safely be left to nil. +func ExpandSchemaWithBasePath(schema *Schema, cache ResolutionCache, opts *ExpandOptions) error { + if schema == nil { + return nil + } + + cache = cacheOrDefault(cache) + + opts = optionsOrDefault(opts) + + resolver := defaultSchemaLoader(nil, opts, cache, nil) + + parentRefs := make([]string, 0, 10) + s, err := expandSchema(*schema, parentRefs, resolver, opts.RelativeBase) + if err != nil { + return err + } + if s != nil { + // guard for when continuing on error + *schema = *s + } + + return nil +} + +func expandItems(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) { + if target.Items == nil { + return &target, nil + } + + // array + if target.Items.Schema != nil { + t, err := expandSchema(*target.Items.Schema, parentRefs, resolver, basePath) + if err != nil { + return nil, err + } + *target.Items.Schema = *t + } + + // tuple + for i := range target.Items.Schemas { + t, err := expandSchema(target.Items.Schemas[i], parentRefs, resolver, basePath) + if err != nil { + return nil, err + } + target.Items.Schemas[i] = *t + } + + return &target, nil +} + +func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) { + if target.Ref.String() == "" && target.Ref.IsRoot() { + newRef := normalizeRef(&target.Ref, basePath) + target.Ref = *newRef + return &target, nil + } + + // change the base path of resolution when an ID is encountered + // otherwise the basePath should inherit the parent's + if target.ID != "" { + basePath, _ = resolver.setSchemaID(target, target.ID, basePath) + } + + if target.Ref.String() != "" { + return expandSchemaRef(target, parentRefs, resolver, basePath) + } + + for k := range target.Definitions { + tt, err := expandSchema(target.Definitions[k], parentRefs, resolver, basePath) + if resolver.shouldStopOnError(err) { + return &target, err + } + if tt != nil { + target.Definitions[k] = *tt + } + } + + t, err := expandItems(target, parentRefs, resolver, basePath) + if resolver.shouldStopOnError(err) { + return &target, err + } + if t != nil { + target = *t + } + + for i := range target.AllOf { + t, err := expandSchema(target.AllOf[i], parentRefs, resolver, basePath) + if resolver.shouldStopOnError(err) { + return &target, err + } + if t != nil { + target.AllOf[i] = *t + } + } + + for i := range target.AnyOf { + t, err := expandSchema(target.AnyOf[i], parentRefs, resolver, basePath) + if resolver.shouldStopOnError(err) { + return &target, err + } + if t != nil { + target.AnyOf[i] = *t + } + } + + for i := range target.OneOf { + t, err := expandSchema(target.OneOf[i], parentRefs, resolver, basePath) + if resolver.shouldStopOnError(err) { + return &target, err + } + if t != nil { + target.OneOf[i] = *t + } + } + + if target.Not != nil { + t, err := expandSchema(*target.Not, parentRefs, resolver, basePath) + if resolver.shouldStopOnError(err) { + return &target, err + } + if t != nil { + *target.Not = *t + } + } + + for k := range target.Properties { + t, err := expandSchema(target.Properties[k], parentRefs, resolver, basePath) + if resolver.shouldStopOnError(err) { + return &target, err + } + if t != nil { + target.Properties[k] = *t + } + } + + if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil { + t, err := expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver, basePath) + if resolver.shouldStopOnError(err) { + return &target, err + } + if t != nil { + *target.AdditionalProperties.Schema = *t + } + } + + for k := range target.PatternProperties { + t, err := expandSchema(target.PatternProperties[k], parentRefs, resolver, basePath) + if resolver.shouldStopOnError(err) { + return &target, err + } + if t != nil { + target.PatternProperties[k] = *t + } + } + + for k := range target.Dependencies { + if target.Dependencies[k].Schema != nil { + t, err := expandSchema(*target.Dependencies[k].Schema, parentRefs, resolver, basePath) + if resolver.shouldStopOnError(err) { + return &target, err + } + if t != nil { + *target.Dependencies[k].Schema = *t + } + } + } + + if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil { + t, err := expandSchema(*target.AdditionalItems.Schema, parentRefs, resolver, basePath) + if resolver.shouldStopOnError(err) { + return &target, err + } + if t != nil { + *target.AdditionalItems.Schema = *t + } + } + return &target, nil +} + +func expandSchemaRef(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) { + // if a Ref is found, all sibling fields are skipped + // Ref also changes the resolution scope of children expandSchema + + // here the resolution scope is changed because a $ref was encountered + normalizedRef := normalizeRef(&target.Ref, basePath) + normalizedBasePath := normalizedRef.RemoteURI() + + if resolver.isCircular(normalizedRef, basePath, parentRefs...) { + // this means there is a cycle in the recursion tree: return the Ref + // - circular refs cannot be expanded. We leave them as ref. + // - denormalization means that a new local file ref is set relative to the original basePath + debugLog("short circuit circular ref: basePath: %s, normalizedPath: %s, normalized ref: %s", + basePath, normalizedBasePath, normalizedRef.String()) + if !resolver.options.AbsoluteCircularRef { + target.Ref = denormalizeRef(normalizedRef, resolver.context.basePath, resolver.context.rootID) + } else { + target.Ref = *normalizedRef + } + return &target, nil + } + + var t *Schema + err := resolver.Resolve(&target.Ref, &t, basePath) + if resolver.shouldStopOnError(err) { + return nil, err + } + + if t == nil { + // guard for when continuing on error + return &target, nil + } + + parentRefs = append(parentRefs, normalizedRef.String()) + transitiveResolver := resolver.transitiveResolver(basePath, target.Ref) + + basePath = resolver.updateBasePath(transitiveResolver, normalizedBasePath) + + return expandSchema(*t, parentRefs, transitiveResolver, basePath) +} + +func expandPathItem(pathItem *PathItem, resolver *schemaLoader, basePath string) error { + if pathItem == nil { + return nil + } + + parentRefs := make([]string, 0, 10) + if err := resolver.deref(pathItem, parentRefs, basePath); resolver.shouldStopOnError(err) { + return err + } + + if pathItem.Ref.String() != "" { + transitiveResolver := resolver.transitiveResolver(basePath, pathItem.Ref) + basePath = transitiveResolver.updateBasePath(resolver, basePath) + resolver = transitiveResolver + } + + pathItem.Ref = Ref{} + for i := range pathItem.Parameters { + if err := expandParameterOrResponse(&(pathItem.Parameters[i]), resolver, basePath); resolver.shouldStopOnError(err) { + return err + } + } + + ops := []*Operation{ + pathItem.Get, + pathItem.Head, + pathItem.Options, + pathItem.Put, + pathItem.Post, + pathItem.Patch, + pathItem.Delete, + } + for _, op := range ops { + if err := expandOperation(op, resolver, basePath); resolver.shouldStopOnError(err) { + return err + } + } + + return nil +} + +func expandOperation(op *Operation, resolver *schemaLoader, basePath string) error { + if op == nil { + return nil + } + + for i := range op.Parameters { + param := op.Parameters[i] + if err := expandParameterOrResponse(¶m, resolver, basePath); resolver.shouldStopOnError(err) { + return err + } + op.Parameters[i] = param + } + + if op.Responses == nil { + return nil + } + + responses := op.Responses + if err := expandParameterOrResponse(responses.Default, resolver, basePath); resolver.shouldStopOnError(err) { + return err + } + + for code := range responses.StatusCodeResponses { + response := responses.StatusCodeResponses[code] + if err := expandParameterOrResponse(&response, resolver, basePath); resolver.shouldStopOnError(err) { + return err + } + responses.StatusCodeResponses[code] = response + } + + return nil +} + +// ExpandResponseWithRoot expands a response based on a root document, not a fetchable document +// +// Notice that it is impossible to reference a json schema in a different document other than root +// (use ExpandResponse to resolve external references). +// +// Setting the cache is optional and this parameter may safely be left to nil. +func ExpandResponseWithRoot(response *Response, root interface{}, cache ResolutionCache) error { + cache = cacheOrDefault(cache) + opts := &ExpandOptions{ + RelativeBase: baseForRoot(root, cache), + } + resolver := defaultSchemaLoader(root, opts, cache, nil) + + return expandParameterOrResponse(response, resolver, opts.RelativeBase) +} + +// ExpandResponse expands a response based on a basepath +// +// All refs inside response will be resolved relative to basePath +func ExpandResponse(response *Response, basePath string) error { + opts := optionsOrDefault(&ExpandOptions{ + RelativeBase: basePath, + }) + resolver := defaultSchemaLoader(nil, opts, nil, nil) + + return expandParameterOrResponse(response, resolver, opts.RelativeBase) +} + +// ExpandParameterWithRoot expands a parameter based on a root document, not a fetchable document. +// +// Notice that it is impossible to reference a json schema in a different document other than root +// (use ExpandParameter to resolve external references). +func ExpandParameterWithRoot(parameter *Parameter, root interface{}, cache ResolutionCache) error { + cache = cacheOrDefault(cache) + + opts := &ExpandOptions{ + RelativeBase: baseForRoot(root, cache), + } + resolver := defaultSchemaLoader(root, opts, cache, nil) + + return expandParameterOrResponse(parameter, resolver, opts.RelativeBase) +} + +// ExpandParameter expands a parameter based on a basepath. +// This is the exported version of expandParameter +// all refs inside parameter will be resolved relative to basePath +func ExpandParameter(parameter *Parameter, basePath string) error { + opts := optionsOrDefault(&ExpandOptions{ + RelativeBase: basePath, + }) + resolver := defaultSchemaLoader(nil, opts, nil, nil) + + return expandParameterOrResponse(parameter, resolver, opts.RelativeBase) +} + +func getRefAndSchema(input interface{}) (*Ref, *Schema, error) { + var ( + ref *Ref + sch *Schema + ) + + switch refable := input.(type) { + case *Parameter: + if refable == nil { + return nil, nil, nil + } + ref = &refable.Ref + sch = refable.Schema + case *Response: + if refable == nil { + return nil, nil, nil + } + ref = &refable.Ref + sch = refable.Schema + default: + return nil, nil, fmt.Errorf("unsupported type: %T: %w", input, ErrExpandUnsupportedType) + } + + return ref, sch, nil +} + +func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePath string) error { + ref, _, err := getRefAndSchema(input) + if err != nil { + return err + } + + if ref == nil { + return nil + } + + parentRefs := make([]string, 0, 10) + if err = resolver.deref(input, parentRefs, basePath); resolver.shouldStopOnError(err) { + return err + } + + ref, sch, _ := getRefAndSchema(input) + if ref.String() != "" { + transitiveResolver := resolver.transitiveResolver(basePath, *ref) + basePath = resolver.updateBasePath(transitiveResolver, basePath) + resolver = transitiveResolver + } + + if sch == nil { + // nothing to be expanded + if ref != nil { + *ref = Ref{} + } + return nil + } + + if sch.Ref.String() != "" { + rebasedRef, ern := NewRef(normalizeURI(sch.Ref.String(), basePath)) + if ern != nil { + return ern + } + + switch { + case 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) + } + } + + 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 + } + *sch = *s + } + + return nil +} diff --git a/vendor/github.com/go-openapi/spec/external_docs.go b/vendor/github.com/go-openapi/spec/external_docs.go new file mode 100644 index 00000000..88add91b --- /dev/null +++ b/vendor/github.com/go-openapi/spec/external_docs.go @@ -0,0 +1,24 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +// ExternalDocumentation allows referencing an external resource for +// extended documentation. +// +// For more information: http://goo.gl/8us55a#externalDocumentationObject +type ExternalDocumentation struct { + Description string `json:"description,omitempty"` + URL string `json:"url,omitempty"` +} diff --git a/vendor/github.com/go-openapi/spec/header.go b/vendor/github.com/go-openapi/spec/header.go new file mode 100644 index 00000000..9dfd17b1 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/header.go @@ -0,0 +1,203 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + "strings" + + "github.com/go-openapi/jsonpointer" + "github.com/go-openapi/swag" +) + +const ( + jsonArray = "array" +) + +// HeaderProps describes a response header +type HeaderProps struct { + Description string `json:"description,omitempty"` +} + +// Header describes a header for a response of the API +// +// For more information: http://goo.gl/8us55a#headerObject +type Header struct { + CommonValidations + SimpleSchema + VendorExtensible + HeaderProps +} + +// ResponseHeader creates a new header instance for use in a response +func ResponseHeader() *Header { + return new(Header) +} + +// WithDescription sets the description on this response, allows for chaining +func (h *Header) WithDescription(description string) *Header { + h.Description = description + return h +} + +// Typed a fluent builder method for the type of parameter +func (h *Header) Typed(tpe, format string) *Header { + h.Type = tpe + h.Format = format + return h +} + +// CollectionOf a fluent builder method for an array item +func (h *Header) CollectionOf(items *Items, format string) *Header { + h.Type = jsonArray + h.Items = items + h.CollectionFormat = format + return h +} + +// WithDefault sets the default value on this item +func (h *Header) WithDefault(defaultValue interface{}) *Header { + h.Default = defaultValue + return h +} + +// WithMaxLength sets a max length value +func (h *Header) WithMaxLength(max int64) *Header { + h.MaxLength = &max + return h +} + +// WithMinLength sets a min length value +func (h *Header) WithMinLength(min int64) *Header { + h.MinLength = &min + return h +} + +// WithPattern sets a pattern value +func (h *Header) WithPattern(pattern string) *Header { + h.Pattern = pattern + return h +} + +// WithMultipleOf sets a multiple of value +func (h *Header) WithMultipleOf(number float64) *Header { + h.MultipleOf = &number + return h +} + +// WithMaximum sets a maximum number value +func (h *Header) WithMaximum(max float64, exclusive bool) *Header { + h.Maximum = &max + h.ExclusiveMaximum = exclusive + return h +} + +// WithMinimum sets a minimum number value +func (h *Header) WithMinimum(min float64, exclusive bool) *Header { + h.Minimum = &min + h.ExclusiveMinimum = exclusive + return h +} + +// WithEnum sets a the enum values (replace) +func (h *Header) WithEnum(values ...interface{}) *Header { + h.Enum = append([]interface{}{}, values...) + return h +} + +// WithMaxItems sets the max items +func (h *Header) WithMaxItems(size int64) *Header { + h.MaxItems = &size + return h +} + +// WithMinItems sets the min items +func (h *Header) WithMinItems(size int64) *Header { + h.MinItems = &size + return h +} + +// UniqueValues dictates that this array can only have unique items +func (h *Header) UniqueValues() *Header { + h.UniqueItems = true + return h +} + +// AllowDuplicates this array can have duplicates +func (h *Header) AllowDuplicates() *Header { + h.UniqueItems = false + return h +} + +// WithValidations is a fluent method to set header validations +func (h *Header) WithValidations(val CommonValidations) *Header { + h.SetValidations(SchemaValidations{CommonValidations: val}) + return h +} + +// MarshalJSON marshal this to JSON +func (h Header) MarshalJSON() ([]byte, error) { + b1, err := json.Marshal(h.CommonValidations) + if err != nil { + return nil, err + } + b2, err := json.Marshal(h.SimpleSchema) + if err != nil { + return nil, err + } + b3, err := json.Marshal(h.HeaderProps) + if err != nil { + return nil, err + } + return swag.ConcatJSON(b1, b2, b3), nil +} + +// UnmarshalJSON unmarshals this header from JSON +func (h *Header) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &h.CommonValidations); err != nil { + return err + } + if err := json.Unmarshal(data, &h.SimpleSchema); err != nil { + return err + } + if err := json.Unmarshal(data, &h.VendorExtensible); err != nil { + return err + } + return json.Unmarshal(data, &h.HeaderProps) +} + +// JSONLookup look up a value by the json property name +func (h Header) JSONLookup(token string) (interface{}, error) { + if ex, ok := h.Extensions[token]; ok { + return &ex, nil + } + + r, _, err := jsonpointer.GetForToken(h.CommonValidations, token) + if err != nil && !strings.HasPrefix(err.Error(), "object has no field") { + return nil, err + } + if r != nil { + return r, nil + } + r, _, err = jsonpointer.GetForToken(h.SimpleSchema, token) + if err != nil && !strings.HasPrefix(err.Error(), "object has no field") { + return nil, err + } + if r != nil { + return r, nil + } + r, _, err = jsonpointer.GetForToken(h.HeaderProps, token) + return r, err +} diff --git a/vendor/github.com/go-openapi/spec/info.go b/vendor/github.com/go-openapi/spec/info.go new file mode 100644 index 00000000..c458b49b --- /dev/null +++ b/vendor/github.com/go-openapi/spec/info.go @@ -0,0 +1,165 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + "strings" + + "github.com/go-openapi/jsonpointer" + "github.com/go-openapi/swag" +) + +// Extensions vendor specific extensions +type Extensions map[string]interface{} + +// Add adds a value to these extensions +func (e Extensions) Add(key string, value interface{}) { + realKey := strings.ToLower(key) + e[realKey] = value +} + +// GetString gets a string value from the extensions +func (e Extensions) GetString(key string) (string, bool) { + if v, ok := e[strings.ToLower(key)]; ok { + str, ok := v.(string) + return str, ok + } + return "", false +} + +// GetBool gets a string value from the extensions +func (e Extensions) GetBool(key string) (bool, bool) { + if v, ok := e[strings.ToLower(key)]; ok { + str, ok := v.(bool) + return str, ok + } + return false, false +} + +// GetStringSlice gets a string value from the extensions +func (e Extensions) GetStringSlice(key string) ([]string, bool) { + if v, ok := e[strings.ToLower(key)]; ok { + arr, isSlice := v.([]interface{}) + if !isSlice { + return nil, false + } + var strs []string + for _, iface := range arr { + str, isString := iface.(string) + if !isString { + return nil, false + } + strs = append(strs, str) + } + return strs, ok + } + return nil, false +} + +// VendorExtensible composition block. +type VendorExtensible struct { + Extensions Extensions +} + +// AddExtension adds an extension to this extensible object +func (v *VendorExtensible) AddExtension(key string, value interface{}) { + if value == nil { + return + } + if v.Extensions == nil { + v.Extensions = make(map[string]interface{}) + } + v.Extensions.Add(key, value) +} + +// MarshalJSON marshals the extensions to json +func (v VendorExtensible) MarshalJSON() ([]byte, error) { + toser := make(map[string]interface{}) + for k, v := range v.Extensions { + lk := strings.ToLower(k) + if strings.HasPrefix(lk, "x-") { + toser[k] = v + } + } + return json.Marshal(toser) +} + +// UnmarshalJSON for this extensible object +func (v *VendorExtensible) UnmarshalJSON(data []byte) error { + var d map[string]interface{} + if err := json.Unmarshal(data, &d); err != nil { + return err + } + for k, vv := range d { + lk := strings.ToLower(k) + if strings.HasPrefix(lk, "x-") { + if v.Extensions == nil { + v.Extensions = map[string]interface{}{} + } + v.Extensions[k] = vv + } + } + return nil +} + +// InfoProps the properties for an info definition +type InfoProps struct { + Description string `json:"description,omitempty"` + Title string `json:"title,omitempty"` + TermsOfService string `json:"termsOfService,omitempty"` + Contact *ContactInfo `json:"contact,omitempty"` + License *License `json:"license,omitempty"` + Version string `json:"version,omitempty"` +} + +// Info object provides metadata about the API. +// The metadata can be used by the clients if needed, and can be presented in the Swagger-UI for convenience. +// +// For more information: http://goo.gl/8us55a#infoObject +type Info struct { + VendorExtensible + InfoProps +} + +// JSONLookup look up a value by the json property name +func (i Info) JSONLookup(token string) (interface{}, error) { + if ex, ok := i.Extensions[token]; ok { + return &ex, nil + } + r, _, err := jsonpointer.GetForToken(i.InfoProps, token) + return r, err +} + +// MarshalJSON marshal this to JSON +func (i Info) MarshalJSON() ([]byte, error) { + b1, err := json.Marshal(i.InfoProps) + if err != nil { + return nil, err + } + b2, err := json.Marshal(i.VendorExtensible) + if err != nil { + return nil, err + } + return swag.ConcatJSON(b1, b2), nil +} + +// UnmarshalJSON marshal this from JSON +func (i *Info) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &i.InfoProps); err != nil { + return err + } + return json.Unmarshal(data, &i.VendorExtensible) +} diff --git a/vendor/github.com/go-openapi/spec/items.go b/vendor/github.com/go-openapi/spec/items.go new file mode 100644 index 00000000..e2afb213 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/items.go @@ -0,0 +1,234 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + "strings" + + "github.com/go-openapi/jsonpointer" + "github.com/go-openapi/swag" +) + +const ( + jsonRef = "$ref" +) + +// SimpleSchema describe swagger simple schemas for parameters and headers +type SimpleSchema struct { + Type string `json:"type,omitempty"` + Nullable bool `json:"nullable,omitempty"` + Format string `json:"format,omitempty"` + Items *Items `json:"items,omitempty"` + CollectionFormat string `json:"collectionFormat,omitempty"` + Default interface{} `json:"default,omitempty"` + Example interface{} `json:"example,omitempty"` +} + +// TypeName return the type (or format) of a simple schema +func (s *SimpleSchema) TypeName() string { + if s.Format != "" { + return s.Format + } + return s.Type +} + +// ItemsTypeName yields the type of items in a simple schema array +func (s *SimpleSchema) ItemsTypeName() string { + if s.Items == nil { + return "" + } + return s.Items.TypeName() +} + +// Items a limited subset of JSON-Schema's items object. +// It is used by parameter definitions that are not located in "body". +// +// For more information: http://goo.gl/8us55a#items-object +type Items struct { + Refable + CommonValidations + SimpleSchema + VendorExtensible +} + +// NewItems creates a new instance of items +func NewItems() *Items { + return &Items{} +} + +// Typed a fluent builder method for the type of item +func (i *Items) Typed(tpe, format string) *Items { + i.Type = tpe + i.Format = format + return i +} + +// AsNullable flags this schema as nullable. +func (i *Items) AsNullable() *Items { + i.Nullable = true + return i +} + +// CollectionOf a fluent builder method for an array item +func (i *Items) CollectionOf(items *Items, format string) *Items { + i.Type = jsonArray + i.Items = items + i.CollectionFormat = format + return i +} + +// WithDefault sets the default value on this item +func (i *Items) WithDefault(defaultValue interface{}) *Items { + i.Default = defaultValue + return i +} + +// WithMaxLength sets a max length value +func (i *Items) WithMaxLength(max int64) *Items { + i.MaxLength = &max + return i +} + +// WithMinLength sets a min length value +func (i *Items) WithMinLength(min int64) *Items { + i.MinLength = &min + return i +} + +// WithPattern sets a pattern value +func (i *Items) WithPattern(pattern string) *Items { + i.Pattern = pattern + return i +} + +// WithMultipleOf sets a multiple of value +func (i *Items) WithMultipleOf(number float64) *Items { + i.MultipleOf = &number + return i +} + +// WithMaximum sets a maximum number value +func (i *Items) WithMaximum(max float64, exclusive bool) *Items { + i.Maximum = &max + i.ExclusiveMaximum = exclusive + return i +} + +// WithMinimum sets a minimum number value +func (i *Items) WithMinimum(min float64, exclusive bool) *Items { + i.Minimum = &min + i.ExclusiveMinimum = exclusive + return i +} + +// WithEnum sets a the enum values (replace) +func (i *Items) WithEnum(values ...interface{}) *Items { + i.Enum = append([]interface{}{}, values...) + return i +} + +// WithMaxItems sets the max items +func (i *Items) WithMaxItems(size int64) *Items { + i.MaxItems = &size + return i +} + +// WithMinItems sets the min items +func (i *Items) WithMinItems(size int64) *Items { + i.MinItems = &size + return i +} + +// UniqueValues dictates that this array can only have unique items +func (i *Items) UniqueValues() *Items { + i.UniqueItems = true + return i +} + +// AllowDuplicates this array can have duplicates +func (i *Items) AllowDuplicates() *Items { + i.UniqueItems = false + return i +} + +// WithValidations is a fluent method to set Items validations +func (i *Items) WithValidations(val CommonValidations) *Items { + i.SetValidations(SchemaValidations{CommonValidations: val}) + return i +} + +// UnmarshalJSON hydrates this items instance with the data from JSON +func (i *Items) UnmarshalJSON(data []byte) error { + var validations CommonValidations + if err := json.Unmarshal(data, &validations); err != nil { + return err + } + var ref Refable + if err := json.Unmarshal(data, &ref); err != nil { + return err + } + var simpleSchema SimpleSchema + if err := json.Unmarshal(data, &simpleSchema); err != nil { + return err + } + var vendorExtensible VendorExtensible + if err := json.Unmarshal(data, &vendorExtensible); err != nil { + return err + } + i.Refable = ref + i.CommonValidations = validations + i.SimpleSchema = simpleSchema + i.VendorExtensible = vendorExtensible + return nil +} + +// MarshalJSON converts this items object to JSON +func (i Items) MarshalJSON() ([]byte, error) { + b1, err := json.Marshal(i.CommonValidations) + if err != nil { + return nil, err + } + b2, err := json.Marshal(i.SimpleSchema) + if err != nil { + return nil, err + } + b3, err := json.Marshal(i.Refable) + if err != nil { + return nil, err + } + b4, err := json.Marshal(i.VendorExtensible) + if err != nil { + return nil, err + } + return swag.ConcatJSON(b4, b3, b1, b2), nil +} + +// JSONLookup look up a value by the json property name +func (i Items) JSONLookup(token string) (interface{}, error) { + if token == jsonRef { + return &i.Ref, nil + } + + r, _, err := jsonpointer.GetForToken(i.CommonValidations, token) + if err != nil && !strings.HasPrefix(err.Error(), "object has no field") { + return nil, err + } + if r != nil { + return r, nil + } + r, _, err = jsonpointer.GetForToken(i.SimpleSchema, token) + return r, err +} diff --git a/vendor/github.com/go-openapi/spec/license.go b/vendor/github.com/go-openapi/spec/license.go new file mode 100644 index 00000000..b42f8036 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/license.go @@ -0,0 +1,56 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + + "github.com/go-openapi/swag" +) + +// License information for the exposed API. +// +// For more information: http://goo.gl/8us55a#licenseObject +type License struct { + LicenseProps + VendorExtensible +} + +// LicenseProps holds the properties of a License object +type LicenseProps struct { + Name string `json:"name,omitempty"` + URL string `json:"url,omitempty"` +} + +// UnmarshalJSON hydrates License from json +func (l *License) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &l.LicenseProps); err != nil { + return err + } + return json.Unmarshal(data, &l.VendorExtensible) +} + +// MarshalJSON produces License as json +func (l License) MarshalJSON() ([]byte, error) { + b1, err := json.Marshal(l.LicenseProps) + if err != nil { + return nil, err + } + b2, err := json.Marshal(l.VendorExtensible) + if err != nil { + return nil, err + } + return swag.ConcatJSON(b1, b2), nil +} diff --git a/vendor/github.com/go-openapi/spec/normalizer.go b/vendor/github.com/go-openapi/spec/normalizer.go new file mode 100644 index 00000000..d6c48397 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/normalizer.go @@ -0,0 +1,203 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "net/url" + "path" + "strings" +) + +const fileScheme = "file" + +// normalizeURI ensures that all $ref paths used internally by the expander are canonicalized. +// +// NOTE(windows): there is a tolerance over the strict URI format on windows. +// +// The normalizer accepts relative file URLs like 'Path\File.JSON' as well as absolute file URLs like +// 'C:\Path\file.Yaml'. +// +// Both are canonicalized with a "file://" scheme, slashes and a lower-cased path: +// 'file:///c:/path/file.yaml' +// +// URLs can be specified with a file scheme, like in 'file:///folder/file.json' or +// 'file:///c:\folder\File.json'. +// +// URLs like file://C:\folder are considered invalid (i.e. there is no host 'c:\folder') and a "repair" +// is attempted. +// +// The base path argument is assumed to be canonicalized (e.g. using normalizeBase()). +func normalizeURI(refPath, base string) string { + refURL, err := url.Parse(refPath) + if err != nil { + specLogger.Printf("warning: invalid URI in $ref %q: %v", refPath, err) + refURL, refPath = repairURI(refPath) + } + + fixWindowsURI(refURL, refPath) // noop on non-windows OS + + refURL.Path = path.Clean(refURL.Path) + if refURL.Path == "." { + refURL.Path = "" + } + + r := MustCreateRef(refURL.String()) + if r.IsCanonical() { + return refURL.String() + } + + baseURL, _ := url.Parse(base) + if path.IsAbs(refURL.Path) { + baseURL.Path = refURL.Path + } else if refURL.Path != "" { + baseURL.Path = path.Join(path.Dir(baseURL.Path), refURL.Path) + } + // copying fragment from ref to base + baseURL.Fragment = refURL.Fragment + + return baseURL.String() +} + +// denormalizeRef returns the simplest notation for a normalized $ref, given the path of the original root document. +// +// When calling this, we assume that: +// * $ref is a canonical URI +// * originalRelativeBase is a canonical URI +// +// denormalizeRef is currently used when we rewrite a $ref after a circular $ref has been detected. +// In this case, expansion stops and normally renders the internal canonical $ref. +// +// This internal $ref is eventually rebased to the original RelativeBase used for the expansion. +// +// There is a special case for schemas that are anchored with an "id": +// in that case, the rebasing is performed // against the id only if this is an anchor for the initial root document. +// All other intermediate "id"'s found along the way are ignored for the purpose of rebasing. +// +func denormalizeRef(ref *Ref, originalRelativeBase, id string) Ref { + debugLog("denormalizeRef called:\n$ref: %q\noriginal: %s\nroot ID:%s", ref.String(), originalRelativeBase, id) + + if ref.String() == "" || ref.IsRoot() || ref.HasFragmentOnly { + // short circuit: $ref to current doc + return *ref + } + + if id != "" { + idBaseURL, err := url.Parse(id) + if err == nil { // if the schema id is not usable as a URI, ignore it + if ref, ok := rebase(ref, idBaseURL, true); ok { // rebase, but keep references to root unchaged (do not want $ref: "") + // $ref relative to the ID of the schema in the root document + return ref + } + } + } + + originalRelativeBaseURL, _ := url.Parse(originalRelativeBase) + + r, _ := rebase(ref, originalRelativeBaseURL, false) + + return r +} + +func rebase(ref *Ref, v *url.URL, notEqual bool) (Ref, bool) { + var newBase url.URL + + u := ref.GetURL() + + if u.Scheme != v.Scheme || u.Host != v.Host { + return *ref, false + } + + docPath := v.Path + v.Path = path.Dir(v.Path) + + if v.Path == "." { + v.Path = "" + } else if !strings.HasSuffix(v.Path, "/") { + v.Path += "/" + } + + newBase.Fragment = u.Fragment + + if strings.HasPrefix(u.Path, docPath) { + newBase.Path = strings.TrimPrefix(u.Path, docPath) + } else { + newBase.Path = strings.TrimPrefix(u.Path, v.Path) + } + + if notEqual && newBase.Path == "" && newBase.Fragment == "" { + // do not want rebasing to end up in an empty $ref + return *ref, false + } + + if path.IsAbs(newBase.Path) { + // whenever we end up with an absolute path, specify the scheme and host + newBase.Scheme = v.Scheme + newBase.Host = v.Host + } + + return MustCreateRef(newBase.String()), true +} + +// normalizeRef canonicalize a Ref, using a canonical relativeBase as its absolute anchor +func normalizeRef(ref *Ref, relativeBase string) *Ref { + r := MustCreateRef(normalizeURI(ref.String(), relativeBase)) + return &r +} + +// normalizeBase performs a normalization of the input base path. +// +// This always yields a canonical URI (absolute), usable for the document cache. +// +// It ensures that all further internal work on basePath may safely assume +// a non-empty, cross-platform, canonical URI (i.e. absolute). +// +// This normalization tolerates windows paths (e.g. C:\x\y\File.dat) and transform this +// in a file:// URL with lower cased drive letter and path. +// +// See also: https://en.wikipedia.org/wiki/File_URI_scheme +func normalizeBase(in string) string { + u, err := url.Parse(in) + if err != nil { + specLogger.Printf("warning: invalid URI in RelativeBase %q: %v", in, err) + u, in = repairURI(in) + } + + u.Fragment = "" // any fragment in the base is irrelevant + + fixWindowsURI(u, in) // noop on non-windows OS + + u.Path = path.Clean(u.Path) + if u.Path == "." { // empty after Clean() + u.Path = "" + } + + if u.Scheme != "" { + if path.IsAbs(u.Path) || u.Scheme != fileScheme { + // this is absolute or explicitly not a local file: we're good + return u.String() + } + } + + // no scheme or file scheme with relative path: assume file and make it absolute + // enforce scheme file://... with absolute path. + // + // If the input path is relative, we anchor the path to the current working directory. + // NOTE: we may end up with a host component. Leave it unchanged: e.g. file://host/folder/file.json + + u.Scheme = fileScheme + u.Path = absPath(u.Path) // platform-dependent + u.RawQuery = "" // any query component is irrelevant for a base + return u.String() +} diff --git a/vendor/github.com/go-openapi/spec/normalizer_nonwindows.go b/vendor/github.com/go-openapi/spec/normalizer_nonwindows.go new file mode 100644 index 00000000..c8a06453 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/normalizer_nonwindows.go @@ -0,0 +1,43 @@ +// +build !windows + +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "net/url" + "path/filepath" +) + +// absPath makes a file path absolute and compatible with a URI path component. +// +// The parameter must be a path, not an URI. +func absPath(in string) string { + anchored, err := filepath.Abs(in) + if err != nil { + specLogger.Printf("warning: could not resolve current working directory: %v", err) + return in + } + return anchored +} + +func repairURI(in string) (*url.URL, string) { + u, _ := url.Parse("") + debugLog("repaired URI: original: %q, repaired: %q", in, "") + return u, "" +} + +func fixWindowsURI(u *url.URL, in string) { +} diff --git a/vendor/github.com/go-openapi/spec/normalizer_windows.go b/vendor/github.com/go-openapi/spec/normalizer_windows.go new file mode 100644 index 00000000..fe2d1ecd --- /dev/null +++ b/vendor/github.com/go-openapi/spec/normalizer_windows.go @@ -0,0 +1,154 @@ +// -build windows + +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "net/url" + "os" + "path" + "path/filepath" + "strings" +) + +// absPath makes a file path absolute and compatible with a URI path component +// +// The parameter must be a path, not an URI. +func absPath(in string) string { + // NOTE(windows): filepath.Abs exhibits a special behavior on windows for empty paths. + // See https://github.com/golang/go/issues/24441 + if in == "" { + in = "." + } + + anchored, err := filepath.Abs(in) + if err != nil { + specLogger.Printf("warning: could not resolve current working directory: %v", err) + return in + } + + pth := strings.ReplaceAll(strings.ToLower(anchored), `\`, `/`) + if !strings.HasPrefix(pth, "/") { + pth = "/" + pth + } + + return path.Clean(pth) +} + +// repairURI tolerates invalid file URIs with common typos +// such as 'file://E:\folder\file', that break the regular URL parser. +// +// Adopting the same defaults as for unixes (e.g. return an empty path) would +// result into a counter-intuitive result for that case (e.g. E:\folder\file is +// eventually resolved as the current directory). The repair will detect the missing "/". +// +// Note that this only works for the file scheme. +func repairURI(in string) (*url.URL, string) { + const prefix = fileScheme + "://" + if !strings.HasPrefix(in, prefix) { + // giving up: resolve to empty path + u, _ := url.Parse("") + + return u, "" + } + + // attempt the repair, stripping the scheme should be sufficient + u, _ := url.Parse(strings.TrimPrefix(in, prefix)) + debugLog("repaired URI: original: %q, repaired: %q", in, u.String()) + + return u, u.String() +} + +// fixWindowsURI tolerates an absolute file path on windows such as C:\Base\File.yaml or \\host\share\Base\File.yaml +// and makes it a canonical URI: file:///c:/base/file.yaml +// +// Catch 22 notes for Windows: +// +// * There may be a drive letter on windows (it is lower-cased) +// * There may be a share UNC, e.g. \\server\folder\data.xml +// * Paths are case insensitive +// * Paths may already contain slashes +// * Paths must be slashed +// +// NOTE: there is no escaping. "/" may be valid separators just like "\". +// We don't use ToSlash() (which escapes everything) because windows now also +// tolerates the use of "/". Hence, both C:\File.yaml and C:/File.yaml will work. +func fixWindowsURI(u *url.URL, in string) { + drive := filepath.VolumeName(in) + + if len(drive) > 0 { + if len(u.Scheme) == 1 && strings.EqualFold(u.Scheme, drive[:1]) { // a path with a drive letter + u.Scheme = fileScheme + u.Host = "" + u.Path = strings.Join([]string{drive, u.Opaque, u.Path}, `/`) // reconstruct the full path component (no fragment, no query) + } else if u.Host == "" && strings.HasPrefix(u.Path, drive) { // a path with a \\host volume + // NOTE: the special host@port syntax for UNC is not supported (yet) + u.Scheme = fileScheme + + // this is a modified version of filepath.Dir() to apply on the VolumeName itself + i := len(drive) - 1 + for i >= 0 && !os.IsPathSeparator(drive[i]) { + i-- + } + host := drive[:i] // \\host\share => host + + u.Path = strings.TrimPrefix(u.Path, host) + u.Host = strings.TrimPrefix(host, `\\`) + } + + u.Opaque = "" + u.Path = strings.ReplaceAll(strings.ToLower(u.Path), `\`, `/`) + + // ensure we form an absolute path + if !strings.HasPrefix(u.Path, "/") { + u.Path = "/" + u.Path + } + + u.Path = path.Clean(u.Path) + + return + } + + if u.Scheme == fileScheme { + // Handle dodgy cases for file://{...} URIs on windows. + // A canonical URI should always be followed by an absolute path. + // + // Examples: + // * file:///folder/file => valid, unchanged + // * file:///c:\folder\file => slashed + // * file:///./folder/file => valid, cleaned to remove the dot + // * file:///.\folder\file => remapped to cwd + // * file:///. => dodgy, remapped to / (consistent with the behavior on unix) + // * file:///.. => dodgy, remapped to / (consistent with the behavior on unix) + if (!path.IsAbs(u.Path) && !filepath.IsAbs(u.Path)) || (strings.HasPrefix(u.Path, `/.`) && strings.Contains(u.Path, `\`)) { + // ensure we form an absolute path + u.Path, _ = filepath.Abs(strings.TrimLeft(u.Path, `/`)) + if !strings.HasPrefix(u.Path, "/") { + u.Path = "/" + u.Path + } + } + u.Path = strings.ToLower(u.Path) + } + + // NOTE: lower case normalization does not propagate to inner resources, + // generated when rebasing: when joining a relative URI with a file to an absolute base, + // only the base is currently lower-cased. + // + // For now, we assume this is good enough for most use cases + // and try not to generate too many differences + // between the output produced on different platforms. + u.Path = path.Clean(strings.ReplaceAll(u.Path, `\`, `/`)) +} diff --git a/vendor/github.com/go-openapi/spec/operation.go b/vendor/github.com/go-openapi/spec/operation.go new file mode 100644 index 00000000..995ce6ac --- /dev/null +++ b/vendor/github.com/go-openapi/spec/operation.go @@ -0,0 +1,397 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "bytes" + "encoding/gob" + "encoding/json" + "sort" + + "github.com/go-openapi/jsonpointer" + "github.com/go-openapi/swag" +) + +func init() { + gob.Register(map[string]interface{}{}) + gob.Register([]interface{}{}) +} + +// OperationProps describes an operation +// +// NOTES: +// - schemes, when present must be from [http, https, ws, wss]: see validate +// - Security is handled as a special case: see MarshalJSON function +type OperationProps struct { + Description string `json:"description,omitempty"` + Consumes []string `json:"consumes,omitempty"` + Produces []string `json:"produces,omitempty"` + Schemes []string `json:"schemes,omitempty"` + Tags []string `json:"tags,omitempty"` + Summary string `json:"summary,omitempty"` + ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"` + ID string `json:"operationId,omitempty"` + Deprecated bool `json:"deprecated,omitempty"` + Security []map[string][]string `json:"security,omitempty"` + Parameters []Parameter `json:"parameters,omitempty"` + Responses *Responses `json:"responses,omitempty"` +} + +// MarshalJSON takes care of serializing operation properties to JSON +// +// We use a custom marhaller here to handle a special cases related to +// the Security field. We need to preserve zero length slice +// while omitting the field when the value is nil/unset. +func (op OperationProps) MarshalJSON() ([]byte, error) { + type Alias OperationProps + if op.Security == nil { + return json.Marshal(&struct { + Security []map[string][]string `json:"security,omitempty"` + *Alias + }{ + Security: op.Security, + Alias: (*Alias)(&op), + }) + } + return json.Marshal(&struct { + Security []map[string][]string `json:"security"` + *Alias + }{ + Security: op.Security, + Alias: (*Alias)(&op), + }) +} + +// Operation describes a single API operation on a path. +// +// For more information: http://goo.gl/8us55a#operationObject +type Operation struct { + VendorExtensible + OperationProps +} + +// SuccessResponse gets a success response model +func (o *Operation) SuccessResponse() (*Response, int, bool) { + if o.Responses == nil { + return nil, 0, false + } + + responseCodes := make([]int, 0, len(o.Responses.StatusCodeResponses)) + for k := range o.Responses.StatusCodeResponses { + if k >= 200 && k < 300 { + responseCodes = append(responseCodes, k) + } + } + if len(responseCodes) > 0 { + sort.Ints(responseCodes) + v := o.Responses.StatusCodeResponses[responseCodes[0]] + return &v, responseCodes[0], true + } + + return o.Responses.Default, 0, false +} + +// JSONLookup look up a value by the json property name +func (o Operation) JSONLookup(token string) (interface{}, error) { + if ex, ok := o.Extensions[token]; ok { + return &ex, nil + } + r, _, err := jsonpointer.GetForToken(o.OperationProps, token) + return r, err +} + +// UnmarshalJSON hydrates this items instance with the data from JSON +func (o *Operation) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &o.OperationProps); err != nil { + return err + } + return json.Unmarshal(data, &o.VendorExtensible) +} + +// MarshalJSON converts this items object to JSON +func (o Operation) MarshalJSON() ([]byte, error) { + b1, err := json.Marshal(o.OperationProps) + if err != nil { + return nil, err + } + b2, err := json.Marshal(o.VendorExtensible) + if err != nil { + return nil, err + } + concated := swag.ConcatJSON(b1, b2) + return concated, nil +} + +// NewOperation creates a new operation instance. +// It expects an ID as parameter but not passing an ID is also valid. +func NewOperation(id string) *Operation { + op := new(Operation) + op.ID = id + return op +} + +// WithID sets the ID property on this operation, allows for chaining. +func (o *Operation) WithID(id string) *Operation { + o.ID = id + return o +} + +// WithDescription sets the description on this operation, allows for chaining +func (o *Operation) WithDescription(description string) *Operation { + o.Description = description + return o +} + +// WithSummary sets the summary on this operation, allows for chaining +func (o *Operation) WithSummary(summary string) *Operation { + o.Summary = summary + return o +} + +// WithExternalDocs sets/removes the external docs for/from this operation. +// When you pass empty strings as params the external documents will be removed. +// When you pass non-empty string as one value then those values will be used on the external docs object. +// So when you pass a non-empty description, you should also pass the url and vice versa. +func (o *Operation) WithExternalDocs(description, url string) *Operation { + if description == "" && url == "" { + o.ExternalDocs = nil + return o + } + + if o.ExternalDocs == nil { + o.ExternalDocs = &ExternalDocumentation{} + } + o.ExternalDocs.Description = description + o.ExternalDocs.URL = url + return o +} + +// Deprecate marks the operation as deprecated +func (o *Operation) Deprecate() *Operation { + o.Deprecated = true + return o +} + +// Undeprecate marks the operation as not deprected +func (o *Operation) Undeprecate() *Operation { + o.Deprecated = false + return o +} + +// WithConsumes adds media types for incoming body values +func (o *Operation) WithConsumes(mediaTypes ...string) *Operation { + o.Consumes = append(o.Consumes, mediaTypes...) + return o +} + +// WithProduces adds media types for outgoing body values +func (o *Operation) WithProduces(mediaTypes ...string) *Operation { + o.Produces = append(o.Produces, mediaTypes...) + return o +} + +// WithTags adds tags for this operation +func (o *Operation) WithTags(tags ...string) *Operation { + o.Tags = append(o.Tags, tags...) + return o +} + +// AddParam adds a parameter to this operation, when a parameter for that location +// and with that name already exists it will be replaced +func (o *Operation) AddParam(param *Parameter) *Operation { + if param == nil { + return o + } + + for i, p := range o.Parameters { + if p.Name == param.Name && p.In == param.In { + params := append(o.Parameters[:i], *param) + params = append(params, o.Parameters[i+1:]...) + o.Parameters = params + return o + } + } + + o.Parameters = append(o.Parameters, *param) + return o +} + +// RemoveParam removes a parameter from the operation +func (o *Operation) RemoveParam(name, in string) *Operation { + for i, p := range o.Parameters { + if p.Name == name && p.In == in { + o.Parameters = append(o.Parameters[:i], o.Parameters[i+1:]...) + return o + } + } + return o +} + +// SecuredWith adds a security scope to this operation. +func (o *Operation) SecuredWith(name string, scopes ...string) *Operation { + o.Security = append(o.Security, map[string][]string{name: scopes}) + return o +} + +// WithDefaultResponse adds a default response to the operation. +// Passing a nil value will remove the response +func (o *Operation) WithDefaultResponse(response *Response) *Operation { + return o.RespondsWith(0, response) +} + +// RespondsWith adds a status code response to the operation. +// When the code is 0 the value of the response will be used as default response value. +// When the value of the response is nil it will be removed from the operation +func (o *Operation) RespondsWith(code int, response *Response) *Operation { + if o.Responses == nil { + o.Responses = new(Responses) + } + if code == 0 { + o.Responses.Default = response + return o + } + if response == nil { + delete(o.Responses.StatusCodeResponses, code) + return o + } + if o.Responses.StatusCodeResponses == nil { + o.Responses.StatusCodeResponses = make(map[int]Response) + } + o.Responses.StatusCodeResponses[code] = *response + return o +} + +type opsAlias OperationProps + +type gobAlias struct { + Security []map[string]struct { + List []string + Pad bool + } + Alias *opsAlias + SecurityIsEmpty bool +} + +// GobEncode provides a safe gob encoder for Operation, including empty security requirements +func (o Operation) GobEncode() ([]byte, error) { + raw := struct { + Ext VendorExtensible + Props OperationProps + }{ + Ext: o.VendorExtensible, + Props: o.OperationProps, + } + var b bytes.Buffer + err := gob.NewEncoder(&b).Encode(raw) + return b.Bytes(), err +} + +// GobDecode provides a safe gob decoder for Operation, including empty security requirements +func (o *Operation) GobDecode(b []byte) error { + var raw struct { + Ext VendorExtensible + Props OperationProps + } + + buf := bytes.NewBuffer(b) + err := gob.NewDecoder(buf).Decode(&raw) + if err != nil { + return err + } + o.VendorExtensible = raw.Ext + o.OperationProps = raw.Props + return nil +} + +// GobEncode provides a safe gob encoder for Operation, including empty security requirements +func (op OperationProps) GobEncode() ([]byte, error) { + raw := gobAlias{ + Alias: (*opsAlias)(&op), + } + + var b bytes.Buffer + if op.Security == nil { + // nil security requirement + err := gob.NewEncoder(&b).Encode(raw) + return b.Bytes(), err + } + + if len(op.Security) == 0 { + // empty, but non-nil security requirement + raw.SecurityIsEmpty = true + raw.Alias.Security = nil + err := gob.NewEncoder(&b).Encode(raw) + return b.Bytes(), err + } + + raw.Security = make([]map[string]struct { + List []string + Pad bool + }, 0, len(op.Security)) + for _, req := range op.Security { + v := make(map[string]struct { + List []string + Pad bool + }, len(req)) + for k, val := range req { + v[k] = struct { + List []string + Pad bool + }{ + List: val, + } + } + raw.Security = append(raw.Security, v) + } + + err := gob.NewEncoder(&b).Encode(raw) + return b.Bytes(), err +} + +// GobDecode provides a safe gob decoder for Operation, including empty security requirements +func (op *OperationProps) GobDecode(b []byte) error { + var raw gobAlias + + buf := bytes.NewBuffer(b) + err := gob.NewDecoder(buf).Decode(&raw) + if err != nil { + return err + } + if raw.Alias == nil { + return nil + } + + switch { + case raw.SecurityIsEmpty: + // empty, but non-nil security requirement + raw.Alias.Security = []map[string][]string{} + case len(raw.Alias.Security) == 0: + // nil security requirement + raw.Alias.Security = nil + default: + raw.Alias.Security = make([]map[string][]string, 0, len(raw.Security)) + for _, req := range raw.Security { + v := make(map[string][]string, len(req)) + for k, val := range req { + v[k] = make([]string, 0, len(val.List)) + v[k] = append(v[k], val.List...) + } + raw.Alias.Security = append(raw.Alias.Security, v) + } + } + + *op = *(*OperationProps)(raw.Alias) + return nil +} diff --git a/vendor/github.com/go-openapi/spec/parameter.go b/vendor/github.com/go-openapi/spec/parameter.go new file mode 100644 index 00000000..2b2b89b6 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/parameter.go @@ -0,0 +1,326 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + "strings" + + "github.com/go-openapi/jsonpointer" + "github.com/go-openapi/swag" +) + +// QueryParam creates a query parameter +func QueryParam(name string) *Parameter { + return &Parameter{ParamProps: ParamProps{Name: name, In: "query"}} +} + +// HeaderParam creates a header parameter, this is always required by default +func HeaderParam(name string) *Parameter { + return &Parameter{ParamProps: ParamProps{Name: name, In: "header", Required: true}} +} + +// PathParam creates a path parameter, this is always required +func PathParam(name string) *Parameter { + return &Parameter{ParamProps: ParamProps{Name: name, In: "path", Required: true}} +} + +// BodyParam creates a body parameter +func BodyParam(name string, schema *Schema) *Parameter { + return &Parameter{ParamProps: ParamProps{Name: name, In: "body", Schema: schema}} +} + +// FormDataParam creates a body parameter +func FormDataParam(name string) *Parameter { + return &Parameter{ParamProps: ParamProps{Name: name, In: "formData"}} +} + +// FileParam creates a body parameter +func FileParam(name string) *Parameter { + return &Parameter{ParamProps: ParamProps{Name: name, In: "formData"}, + SimpleSchema: SimpleSchema{Type: "file"}} +} + +// SimpleArrayParam creates a param for a simple array (string, int, date etc) +func SimpleArrayParam(name, tpe, fmt string) *Parameter { + return &Parameter{ParamProps: ParamProps{Name: name}, + SimpleSchema: SimpleSchema{Type: jsonArray, CollectionFormat: "csv", + Items: &Items{SimpleSchema: SimpleSchema{Type: tpe, Format: fmt}}}} +} + +// ParamRef creates a parameter that's a json reference +func ParamRef(uri string) *Parameter { + p := new(Parameter) + p.Ref = MustCreateRef(uri) + return p +} + +// ParamProps describes the specific attributes of an operation parameter +// +// NOTE: +// - Schema is defined when "in" == "body": see validate +// - AllowEmptyValue is allowed where "in" == "query" || "formData" +type ParamProps struct { + Description string `json:"description,omitempty"` + Name string `json:"name,omitempty"` + In string `json:"in,omitempty"` + Required bool `json:"required,omitempty"` + Schema *Schema `json:"schema,omitempty"` + AllowEmptyValue bool `json:"allowEmptyValue,omitempty"` +} + +// Parameter a unique parameter is defined by a combination of a [name](#parameterName) and [location](#parameterIn). +// +// There are five possible parameter types. +// * Path - Used together with [Path Templating](#pathTemplating), where the parameter value is actually part +// of the operation's URL. This does not include the host or base path of the API. For example, in `/items/{itemId}`, +// the path parameter is `itemId`. +// * Query - Parameters that are appended to the URL. For example, in `/items?id=###`, the query parameter is `id`. +// * Header - Custom headers that are expected as part of the request. +// * Body - The payload that's appended to the HTTP request. Since there can only be one payload, there can only be +// _one_ body parameter. The name of the body parameter has no effect on the parameter itself and is used for +// documentation purposes only. Since Form parameters are also in the payload, body and form parameters cannot exist +// together for the same operation. +// * Form - Used to describe the payload of an HTTP request when either `application/x-www-form-urlencoded` or +// `multipart/form-data` are used as the content type of the request (in Swagger's definition, +// the [`consumes`](#operationConsumes) property of an operation). This is the only parameter type that can be used +// to send files, thus supporting the `file` type. Since form parameters are sent in the payload, they cannot be +// declared together with a body parameter for the same operation. Form parameters have a different format based on +// the content-type used (for further details, consult http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4). +// * `application/x-www-form-urlencoded` - Similar to the format of Query parameters but as a payload. +// For example, `foo=1&bar=swagger` - both `foo` and `bar` are form parameters. This is normally used for simple +// parameters that are being transferred. +// * `multipart/form-data` - each parameter takes a section in the payload with an internal header. +// For example, for the header `Content-Disposition: form-data; name="submit-name"` the name of the parameter is +// `submit-name`. This type of form parameters is more commonly used for file transfers. +// +// For more information: http://goo.gl/8us55a#parameterObject +type Parameter struct { + Refable + CommonValidations + SimpleSchema + VendorExtensible + ParamProps +} + +// JSONLookup look up a value by the json property name +func (p Parameter) JSONLookup(token string) (interface{}, error) { + if ex, ok := p.Extensions[token]; ok { + return &ex, nil + } + if token == jsonRef { + return &p.Ref, nil + } + + r, _, err := jsonpointer.GetForToken(p.CommonValidations, token) + if err != nil && !strings.HasPrefix(err.Error(), "object has no field") { + return nil, err + } + if r != nil { + return r, nil + } + r, _, err = jsonpointer.GetForToken(p.SimpleSchema, token) + if err != nil && !strings.HasPrefix(err.Error(), "object has no field") { + return nil, err + } + if r != nil { + return r, nil + } + r, _, err = jsonpointer.GetForToken(p.ParamProps, token) + return r, err +} + +// WithDescription a fluent builder method for the description of the parameter +func (p *Parameter) WithDescription(description string) *Parameter { + p.Description = description + return p +} + +// Named a fluent builder method to override the name of the parameter +func (p *Parameter) Named(name string) *Parameter { + p.Name = name + return p +} + +// WithLocation a fluent builder method to override the location of the parameter +func (p *Parameter) WithLocation(in string) *Parameter { + p.In = in + return p +} + +// Typed a fluent builder method for the type of the parameter value +func (p *Parameter) Typed(tpe, format string) *Parameter { + p.Type = tpe + p.Format = format + return p +} + +// CollectionOf a fluent builder method for an array parameter +func (p *Parameter) CollectionOf(items *Items, format string) *Parameter { + p.Type = jsonArray + p.Items = items + p.CollectionFormat = format + return p +} + +// WithDefault sets the default value on this parameter +func (p *Parameter) WithDefault(defaultValue interface{}) *Parameter { + p.AsOptional() // with default implies optional + p.Default = defaultValue + return p +} + +// AllowsEmptyValues flags this parameter as being ok with empty values +func (p *Parameter) AllowsEmptyValues() *Parameter { + p.AllowEmptyValue = true + return p +} + +// NoEmptyValues flags this parameter as not liking empty values +func (p *Parameter) NoEmptyValues() *Parameter { + p.AllowEmptyValue = false + return p +} + +// AsOptional flags this parameter as optional +func (p *Parameter) AsOptional() *Parameter { + p.Required = false + return p +} + +// AsRequired flags this parameter as required +func (p *Parameter) AsRequired() *Parameter { + if p.Default != nil { // with a default required makes no sense + return p + } + p.Required = true + return p +} + +// WithMaxLength sets a max length value +func (p *Parameter) WithMaxLength(max int64) *Parameter { + p.MaxLength = &max + return p +} + +// WithMinLength sets a min length value +func (p *Parameter) WithMinLength(min int64) *Parameter { + p.MinLength = &min + return p +} + +// WithPattern sets a pattern value +func (p *Parameter) WithPattern(pattern string) *Parameter { + p.Pattern = pattern + return p +} + +// WithMultipleOf sets a multiple of value +func (p *Parameter) WithMultipleOf(number float64) *Parameter { + p.MultipleOf = &number + return p +} + +// WithMaximum sets a maximum number value +func (p *Parameter) WithMaximum(max float64, exclusive bool) *Parameter { + p.Maximum = &max + p.ExclusiveMaximum = exclusive + return p +} + +// WithMinimum sets a minimum number value +func (p *Parameter) WithMinimum(min float64, exclusive bool) *Parameter { + p.Minimum = &min + p.ExclusiveMinimum = exclusive + return p +} + +// WithEnum sets a the enum values (replace) +func (p *Parameter) WithEnum(values ...interface{}) *Parameter { + p.Enum = append([]interface{}{}, values...) + return p +} + +// WithMaxItems sets the max items +func (p *Parameter) WithMaxItems(size int64) *Parameter { + p.MaxItems = &size + return p +} + +// WithMinItems sets the min items +func (p *Parameter) WithMinItems(size int64) *Parameter { + p.MinItems = &size + return p +} + +// UniqueValues dictates that this array can only have unique items +func (p *Parameter) UniqueValues() *Parameter { + p.UniqueItems = true + return p +} + +// AllowDuplicates this array can have duplicates +func (p *Parameter) AllowDuplicates() *Parameter { + p.UniqueItems = false + return p +} + +// WithValidations is a fluent method to set parameter validations +func (p *Parameter) WithValidations(val CommonValidations) *Parameter { + p.SetValidations(SchemaValidations{CommonValidations: val}) + return p +} + +// UnmarshalJSON hydrates this items instance with the data from JSON +func (p *Parameter) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &p.CommonValidations); err != nil { + return err + } + if err := json.Unmarshal(data, &p.Refable); err != nil { + return err + } + if err := json.Unmarshal(data, &p.SimpleSchema); err != nil { + return err + } + if err := json.Unmarshal(data, &p.VendorExtensible); err != nil { + return err + } + return json.Unmarshal(data, &p.ParamProps) +} + +// MarshalJSON converts this items object to JSON +func (p Parameter) MarshalJSON() ([]byte, error) { + b1, err := json.Marshal(p.CommonValidations) + if err != nil { + return nil, err + } + b2, err := json.Marshal(p.SimpleSchema) + if err != nil { + return nil, err + } + b3, err := json.Marshal(p.Refable) + if err != nil { + return nil, err + } + b4, err := json.Marshal(p.VendorExtensible) + if err != nil { + return nil, err + } + b5, err := json.Marshal(p.ParamProps) + if err != nil { + return nil, err + } + return swag.ConcatJSON(b3, b1, b2, b4, b5), nil +} diff --git a/vendor/github.com/go-openapi/spec/path_item.go b/vendor/github.com/go-openapi/spec/path_item.go new file mode 100644 index 00000000..68fc8e90 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/path_item.go @@ -0,0 +1,87 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + + "github.com/go-openapi/jsonpointer" + "github.com/go-openapi/swag" +) + +// PathItemProps the path item specific properties +type PathItemProps struct { + Get *Operation `json:"get,omitempty"` + Put *Operation `json:"put,omitempty"` + Post *Operation `json:"post,omitempty"` + Delete *Operation `json:"delete,omitempty"` + Options *Operation `json:"options,omitempty"` + Head *Operation `json:"head,omitempty"` + Patch *Operation `json:"patch,omitempty"` + Parameters []Parameter `json:"parameters,omitempty"` +} + +// PathItem describes the operations available on a single path. +// A Path Item may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering). +// The path itself is still exposed to the documentation viewer but they will +// not know which operations and parameters are available. +// +// For more information: http://goo.gl/8us55a#pathItemObject +type PathItem struct { + Refable + VendorExtensible + PathItemProps +} + +// JSONLookup look up a value by the json property name +func (p PathItem) JSONLookup(token string) (interface{}, error) { + if ex, ok := p.Extensions[token]; ok { + return &ex, nil + } + if token == jsonRef { + return &p.Ref, nil + } + r, _, err := jsonpointer.GetForToken(p.PathItemProps, token) + return r, err +} + +// UnmarshalJSON hydrates this items instance with the data from JSON +func (p *PathItem) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &p.Refable); err != nil { + return err + } + if err := json.Unmarshal(data, &p.VendorExtensible); err != nil { + return err + } + return json.Unmarshal(data, &p.PathItemProps) +} + +// MarshalJSON converts this items object to JSON +func (p PathItem) MarshalJSON() ([]byte, error) { + b3, err := json.Marshal(p.Refable) + if err != nil { + return nil, err + } + b4, err := json.Marshal(p.VendorExtensible) + if err != nil { + return nil, err + } + b5, err := json.Marshal(p.PathItemProps) + if err != nil { + return nil, err + } + concated := swag.ConcatJSON(b3, b4, b5) + return concated, nil +} diff --git a/vendor/github.com/go-openapi/spec/paths.go b/vendor/github.com/go-openapi/spec/paths.go new file mode 100644 index 00000000..9dc82a29 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/paths.go @@ -0,0 +1,97 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/go-openapi/swag" +) + +// Paths holds the relative paths to the individual endpoints. +// The path is appended to the [`basePath`](http://goo.gl/8us55a#swaggerBasePath) in order +// to construct the full URL. +// The Paths may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering). +// +// For more information: http://goo.gl/8us55a#pathsObject +type Paths struct { + VendorExtensible + Paths map[string]PathItem `json:"-"` // custom serializer to flatten this, each entry must start with "/" +} + +// JSONLookup look up a value by the json property name +func (p Paths) JSONLookup(token string) (interface{}, error) { + if pi, ok := p.Paths[token]; ok { + return &pi, nil + } + if ex, ok := p.Extensions[token]; ok { + return &ex, nil + } + return nil, fmt.Errorf("object has no field %q", token) +} + +// UnmarshalJSON hydrates this items instance with the data from JSON +func (p *Paths) UnmarshalJSON(data []byte) error { + var res map[string]json.RawMessage + if err := json.Unmarshal(data, &res); err != nil { + return err + } + for k, v := range res { + if strings.HasPrefix(strings.ToLower(k), "x-") { + if p.Extensions == nil { + p.Extensions = make(map[string]interface{}) + } + var d interface{} + if err := json.Unmarshal(v, &d); err != nil { + return err + } + p.Extensions[k] = d + } + if strings.HasPrefix(k, "/") { + if p.Paths == nil { + p.Paths = make(map[string]PathItem) + } + var pi PathItem + if err := json.Unmarshal(v, &pi); err != nil { + return err + } + p.Paths[k] = pi + } + } + return nil +} + +// MarshalJSON converts this items object to JSON +func (p Paths) MarshalJSON() ([]byte, error) { + b1, err := json.Marshal(p.VendorExtensible) + if err != nil { + return nil, err + } + + pths := make(map[string]PathItem) + for k, v := range p.Paths { + if strings.HasPrefix(k, "/") { + pths[k] = v + } + } + b2, err := json.Marshal(pths) + if err != nil { + return nil, err + } + concated := swag.ConcatJSON(b1, b2) + return concated, nil +} diff --git a/vendor/github.com/go-openapi/spec/properties.go b/vendor/github.com/go-openapi/spec/properties.go new file mode 100644 index 00000000..2af13787 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/properties.go @@ -0,0 +1,91 @@ +package spec + +import ( + "bytes" + "encoding/json" + "reflect" + "sort" +) + +// OrderSchemaItem holds a named schema (e.g. from a property of an object) +type OrderSchemaItem struct { + Name string + Schema +} + +// OrderSchemaItems is a sortable slice of named schemas. +// The ordering is defined by the x-order schema extension. +type OrderSchemaItems []OrderSchemaItem + +// MarshalJSON produces a json object with keys defined by the name schemas +// of the OrderSchemaItems slice, keeping the original order of the slice. +func (items OrderSchemaItems) MarshalJSON() ([]byte, error) { + buf := bytes.NewBuffer(nil) + buf.WriteString("{") + for i := range items { + if i > 0 { + buf.WriteString(",") + } + buf.WriteString("\"") + buf.WriteString(items[i].Name) + buf.WriteString("\":") + bs, err := json.Marshal(&items[i].Schema) + if err != nil { + return nil, err + } + buf.Write(bs) + } + buf.WriteString("}") + return buf.Bytes(), nil +} + +func (items OrderSchemaItems) Len() int { return len(items) } +func (items OrderSchemaItems) Swap(i, j int) { items[i], items[j] = items[j], items[i] } +func (items OrderSchemaItems) Less(i, j int) (ret bool) { + ii, oki := items[i].Extensions.GetString("x-order") + ij, okj := items[j].Extensions.GetString("x-order") + if oki { + if okj { + defer func() { + if err := recover(); err != nil { + defer func() { + if err = recover(); err != nil { + ret = items[i].Name < items[j].Name + } + }() + ret = reflect.ValueOf(ii).String() < reflect.ValueOf(ij).String() + } + }() + return reflect.ValueOf(ii).Int() < reflect.ValueOf(ij).Int() + } + return true + } else if okj { + return false + } + return items[i].Name < items[j].Name +} + +// SchemaProperties is a map representing the properties of a Schema object. +// It knows how to transform its keys into an ordered slice. +type SchemaProperties map[string]Schema + +// ToOrderedSchemaItems transforms the map of properties into a sortable slice +func (properties SchemaProperties) ToOrderedSchemaItems() OrderSchemaItems { + items := make(OrderSchemaItems, 0, len(properties)) + for k, v := range properties { + items = append(items, OrderSchemaItem{ + Name: k, + Schema: v, + }) + } + sort.Sort(items) + return items +} + +// MarshalJSON produces properties as json, keeping their order. +func (properties SchemaProperties) MarshalJSON() ([]byte, error) { + if properties == nil { + return []byte("null"), nil + } + return json.Marshal(properties.ToOrderedSchemaItems()) +} diff --git a/vendor/github.com/go-openapi/spec/ref.go b/vendor/github.com/go-openapi/spec/ref.go new file mode 100644 index 00000000..b0ef9bd9 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/ref.go @@ -0,0 +1,193 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "bytes" + "encoding/gob" + "encoding/json" + "net/http" + "os" + "path/filepath" + + "github.com/go-openapi/jsonreference" +) + +// Refable is a struct for things that accept a $ref property +type Refable struct { + Ref Ref +} + +// MarshalJSON marshals the ref to json +func (r Refable) MarshalJSON() ([]byte, error) { + return r.Ref.MarshalJSON() +} + +// UnmarshalJSON unmarshalss the ref from json +func (r *Refable) UnmarshalJSON(d []byte) error { + return json.Unmarshal(d, &r.Ref) +} + +// Ref represents a json reference that is potentially resolved +type Ref struct { + jsonreference.Ref +} + +// RemoteURI gets the remote uri part of the ref +func (r *Ref) RemoteURI() string { + if r.String() == "" { + return "" + } + + u := *r.GetURL() + u.Fragment = "" + return u.String() +} + +// IsValidURI returns true when the url the ref points to can be found +func (r *Ref) IsValidURI(basepaths ...string) bool { + if r.String() == "" { + return true + } + + v := r.RemoteURI() + if v == "" { + return true + } + + if r.HasFullURL { + //nolint:noctx,gosec + rr, err := http.Get(v) + if err != nil { + return false + } + defer rr.Body.Close() + + return rr.StatusCode/100 == 2 + } + + if !(r.HasFileScheme || r.HasFullFilePath || r.HasURLPathOnly) { + return false + } + + // check for local file + pth := v + if r.HasURLPathOnly { + base := "." + if len(basepaths) > 0 { + base = filepath.Dir(filepath.Join(basepaths...)) + } + p, e := filepath.Abs(filepath.ToSlash(filepath.Join(base, pth))) + if e != nil { + return false + } + pth = p + } + + fi, err := os.Stat(filepath.ToSlash(pth)) + if err != nil { + return false + } + + return !fi.IsDir() +} + +// Inherits creates a new reference from a parent and a child +// If the child cannot inherit from the parent, an error is returned +func (r *Ref) Inherits(child Ref) (*Ref, error) { + ref, err := r.Ref.Inherits(child.Ref) + if err != nil { + return nil, err + } + return &Ref{Ref: *ref}, nil +} + +// NewRef creates a new instance of a ref object +// returns an error when the reference uri is an invalid uri +func NewRef(refURI string) (Ref, error) { + ref, err := jsonreference.New(refURI) + if err != nil { + return Ref{}, err + } + return Ref{Ref: ref}, nil +} + +// MustCreateRef creates a ref object but panics when refURI is invalid. +// Use the NewRef method for a version that returns an error. +func MustCreateRef(refURI string) Ref { + return Ref{Ref: jsonreference.MustCreateRef(refURI)} +} + +// MarshalJSON marshals this ref into a JSON object +func (r Ref) MarshalJSON() ([]byte, error) { + str := r.String() + if str == "" { + if r.IsRoot() { + return []byte(`{"$ref":""}`), nil + } + return []byte("{}"), nil + } + v := map[string]interface{}{"$ref": str} + return json.Marshal(v) +} + +// UnmarshalJSON unmarshals this ref from a JSON object +func (r *Ref) UnmarshalJSON(d []byte) error { + var v map[string]interface{} + if err := json.Unmarshal(d, &v); err != nil { + return err + } + return r.fromMap(v) +} + +// GobEncode provides a safe gob encoder for Ref +func (r Ref) GobEncode() ([]byte, error) { + var b bytes.Buffer + raw, err := r.MarshalJSON() + if err != nil { + return nil, err + } + err = gob.NewEncoder(&b).Encode(raw) + return b.Bytes(), err +} + +// GobDecode provides a safe gob decoder for Ref +func (r *Ref) GobDecode(b []byte) error { + var raw []byte + buf := bytes.NewBuffer(b) + err := gob.NewDecoder(buf).Decode(&raw) + if err != nil { + return err + } + return json.Unmarshal(raw, r) +} + +func (r *Ref) fromMap(v map[string]interface{}) error { + if v == nil { + return nil + } + + if vv, ok := v["$ref"]; ok { + if str, ok := vv.(string); ok { + ref, err := jsonreference.New(str) + if err != nil { + return err + } + *r = Ref{Ref: ref} + } + } + + return nil +} diff --git a/vendor/github.com/go-openapi/spec/resolver.go b/vendor/github.com/go-openapi/spec/resolver.go new file mode 100644 index 00000000..47d1ee13 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/resolver.go @@ -0,0 +1,127 @@ +package spec + +import ( + "fmt" + + "github.com/go-openapi/swag" +) + +func resolveAnyWithBase(root interface{}, ref *Ref, result interface{}, options *ExpandOptions) error { + options = optionsOrDefault(options) + resolver := defaultSchemaLoader(root, options, nil, nil) + + if err := resolver.Resolve(ref, result, options.RelativeBase); err != nil { + return err + } + + return nil +} + +// ResolveRefWithBase resolves a reference against a context root with preservation of base path +func ResolveRefWithBase(root interface{}, ref *Ref, options *ExpandOptions) (*Schema, error) { + result := new(Schema) + + if err := resolveAnyWithBase(root, ref, result, options); err != nil { + return nil, err + } + + return result, nil +} + +// ResolveRef resolves a reference for a schema against a context root +// ref is guaranteed to be in root (no need to go to external files) +// +// ResolveRef is ONLY called from the code generation module +func ResolveRef(root interface{}, ref *Ref) (*Schema, error) { + res, _, err := ref.GetPointer().Get(root) + if err != nil { + return nil, err + } + + switch sch := res.(type) { + case Schema: + return &sch, nil + case *Schema: + return sch, nil + case map[string]interface{}: + newSch := new(Schema) + if err = swag.DynamicJSONToStruct(sch, newSch); err != nil { + return nil, err + } + return newSch, nil + default: + return nil, fmt.Errorf("type: %T: %w", sch, ErrUnknownTypeForReference) + } +} + +// ResolveParameterWithBase resolves a parameter reference against a context root and base path +func ResolveParameterWithBase(root interface{}, ref Ref, options *ExpandOptions) (*Parameter, error) { + result := new(Parameter) + + if err := resolveAnyWithBase(root, &ref, result, options); err != nil { + return nil, err + } + + return result, nil +} + +// ResolveParameter resolves a parameter reference against a context root +func ResolveParameter(root interface{}, ref Ref) (*Parameter, error) { + return ResolveParameterWithBase(root, ref, nil) +} + +// ResolveResponseWithBase resolves response a reference against a context root and base path +func ResolveResponseWithBase(root interface{}, ref Ref, options *ExpandOptions) (*Response, error) { + result := new(Response) + + err := resolveAnyWithBase(root, &ref, result, options) + if err != nil { + return nil, err + } + + return result, nil +} + +// ResolveResponse resolves response a reference against a context root +func ResolveResponse(root interface{}, ref Ref) (*Response, error) { + return ResolveResponseWithBase(root, ref, nil) +} + +// ResolvePathItemWithBase resolves response a path item against a context root and base path +func ResolvePathItemWithBase(root interface{}, ref Ref, options *ExpandOptions) (*PathItem, error) { + result := new(PathItem) + + if err := resolveAnyWithBase(root, &ref, result, options); err != nil { + return nil, err + } + + return result, nil +} + +// ResolvePathItem resolves response a path item against a context root and base path +// +// Deprecated: use ResolvePathItemWithBase instead +func ResolvePathItem(root interface{}, ref Ref, options *ExpandOptions) (*PathItem, error) { + return ResolvePathItemWithBase(root, ref, options) +} + +// ResolveItemsWithBase resolves parameter items reference against a context root and base path. +// +// NOTE: stricly speaking, this construct is not supported by Swagger 2.0. +// Similarly, $ref are forbidden in response headers. +func ResolveItemsWithBase(root interface{}, ref Ref, options *ExpandOptions) (*Items, error) { + result := new(Items) + + if err := resolveAnyWithBase(root, &ref, result, options); err != nil { + return nil, err + } + + return result, nil +} + +// ResolveItems resolves parameter items reference against a context root and base path. +// +// Deprecated: use ResolveItemsWithBase instead +func ResolveItems(root interface{}, ref Ref, options *ExpandOptions) (*Items, error) { + return ResolveItemsWithBase(root, ref, options) +} diff --git a/vendor/github.com/go-openapi/spec/response.go b/vendor/github.com/go-openapi/spec/response.go new file mode 100644 index 00000000..0340b60d --- /dev/null +++ b/vendor/github.com/go-openapi/spec/response.go @@ -0,0 +1,152 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + + "github.com/go-openapi/jsonpointer" + "github.com/go-openapi/swag" +) + +// ResponseProps properties specific to a response +type ResponseProps struct { + Description string `json:"description"` + Schema *Schema `json:"schema,omitempty"` + Headers map[string]Header `json:"headers,omitempty"` + Examples map[string]interface{} `json:"examples,omitempty"` +} + +// Response describes a single response from an API Operation. +// +// For more information: http://goo.gl/8us55a#responseObject +type Response struct { + Refable + ResponseProps + VendorExtensible +} + +// JSONLookup look up a value by the json property name +func (r Response) JSONLookup(token string) (interface{}, error) { + if ex, ok := r.Extensions[token]; ok { + return &ex, nil + } + if token == "$ref" { + return &r.Ref, nil + } + ptr, _, err := jsonpointer.GetForToken(r.ResponseProps, token) + return ptr, err +} + +// UnmarshalJSON hydrates this items instance with the data from JSON +func (r *Response) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &r.ResponseProps); err != nil { + return err + } + if err := json.Unmarshal(data, &r.Refable); err != nil { + return err + } + return json.Unmarshal(data, &r.VendorExtensible) +} + +// MarshalJSON converts this items object to JSON +func (r Response) MarshalJSON() ([]byte, error) { + var ( + b1 []byte + err error + ) + + if r.Ref.String() == "" { + // when there is no $ref, empty description is rendered as an empty string + b1, err = json.Marshal(r.ResponseProps) + } else { + // when there is $ref inside the schema, description should be omitempty-ied + b1, err = json.Marshal(struct { + Description string `json:"description,omitempty"` + Schema *Schema `json:"schema,omitempty"` + Headers map[string]Header `json:"headers,omitempty"` + Examples map[string]interface{} `json:"examples,omitempty"` + }{ + Description: r.ResponseProps.Description, + Schema: r.ResponseProps.Schema, + Examples: r.ResponseProps.Examples, + }) + } + if err != nil { + return nil, err + } + + b2, err := json.Marshal(r.Refable) + if err != nil { + return nil, err + } + b3, err := json.Marshal(r.VendorExtensible) + if err != nil { + return nil, err + } + return swag.ConcatJSON(b1, b2, b3), nil +} + +// NewResponse creates a new response instance +func NewResponse() *Response { + return new(Response) +} + +// ResponseRef creates a response as a json reference +func ResponseRef(url string) *Response { + resp := NewResponse() + resp.Ref = MustCreateRef(url) + return resp +} + +// WithDescription sets the description on this response, allows for chaining +func (r *Response) WithDescription(description string) *Response { + r.Description = description + return r +} + +// WithSchema sets the schema on this response, allows for chaining. +// Passing a nil argument removes the schema from this response +func (r *Response) WithSchema(schema *Schema) *Response { + r.Schema = schema + return r +} + +// AddHeader adds a header to this response +func (r *Response) AddHeader(name string, header *Header) *Response { + if header == nil { + return r.RemoveHeader(name) + } + if r.Headers == nil { + r.Headers = make(map[string]Header) + } + r.Headers[name] = *header + return r +} + +// RemoveHeader removes a header from this response +func (r *Response) RemoveHeader(name string) *Response { + delete(r.Headers, name) + return r +} + +// AddExample adds an example to this response +func (r *Response) AddExample(mediaType string, example interface{}) *Response { + if r.Examples == nil { + r.Examples = make(map[string]interface{}) + } + r.Examples[mediaType] = example + return r +} diff --git a/vendor/github.com/go-openapi/spec/responses.go b/vendor/github.com/go-openapi/spec/responses.go new file mode 100644 index 00000000..4efb6f86 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/responses.go @@ -0,0 +1,127 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + "fmt" + "reflect" + "strconv" + + "github.com/go-openapi/swag" +) + +// Responses is a container for the expected responses of an operation. +// The container maps a HTTP response code to the expected response. +// It is not expected from the documentation to necessarily cover all possible HTTP response codes, +// since they may not be known in advance. However, it is expected from the documentation to cover +// a successful operation response and any known errors. +// +// The `default` can be used a default response object for all HTTP codes that are not covered +// individually by the specification. +// +// The `Responses Object` MUST contain at least one response code, and it SHOULD be the response +// for a successful operation call. +// +// For more information: http://goo.gl/8us55a#responsesObject +type Responses struct { + VendorExtensible + ResponsesProps +} + +// JSONLookup implements an interface to customize json pointer lookup +func (r Responses) JSONLookup(token string) (interface{}, error) { + if token == "default" { + return r.Default, nil + } + if ex, ok := r.Extensions[token]; ok { + return &ex, nil + } + if i, err := strconv.Atoi(token); err == nil { + if scr, ok := r.StatusCodeResponses[i]; ok { + return scr, nil + } + } + return nil, fmt.Errorf("object has no field %q", token) +} + +// UnmarshalJSON hydrates this items instance with the data from JSON +func (r *Responses) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &r.ResponsesProps); err != nil { + return err + } + if err := json.Unmarshal(data, &r.VendorExtensible); err != nil { + return err + } + if reflect.DeepEqual(ResponsesProps{}, r.ResponsesProps) { + r.ResponsesProps = ResponsesProps{} + } + return nil +} + +// MarshalJSON converts this items object to JSON +func (r Responses) MarshalJSON() ([]byte, error) { + b1, err := json.Marshal(r.ResponsesProps) + if err != nil { + return nil, err + } + b2, err := json.Marshal(r.VendorExtensible) + if err != nil { + return nil, err + } + concated := swag.ConcatJSON(b1, b2) + return concated, nil +} + +// ResponsesProps describes all responses for an operation. +// It tells what is the default response and maps all responses with a +// HTTP status code. +type ResponsesProps struct { + Default *Response + StatusCodeResponses map[int]Response +} + +// MarshalJSON marshals responses as JSON +func (r ResponsesProps) MarshalJSON() ([]byte, error) { + toser := map[string]Response{} + if r.Default != nil { + toser["default"] = *r.Default + } + for k, v := range r.StatusCodeResponses { + toser[strconv.Itoa(k)] = v + } + return json.Marshal(toser) +} + +// UnmarshalJSON unmarshals responses from JSON +func (r *ResponsesProps) UnmarshalJSON(data []byte) error { + var res map[string]Response + if err := json.Unmarshal(data, &res); err != nil { + return nil + } + if v, ok := res["default"]; ok { + r.Default = &v + delete(res, "default") + } + for k, v := range res { + if nk, err := strconv.Atoi(k); err == nil { + if r.StatusCodeResponses == nil { + r.StatusCodeResponses = map[int]Response{} + } + r.StatusCodeResponses[nk] = v + } + } + return nil +} diff --git a/vendor/github.com/go-openapi/spec/schema.go b/vendor/github.com/go-openapi/spec/schema.go new file mode 100644 index 00000000..a8d0f737 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/schema.go @@ -0,0 +1,646 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + "fmt" + "net/url" + "strings" + + "github.com/go-openapi/jsonpointer" + "github.com/go-openapi/swag" +) + +// BooleanProperty creates a boolean property +func BooleanProperty() *Schema { + return &Schema{SchemaProps: SchemaProps{Type: []string{"boolean"}}} +} + +// BoolProperty creates a boolean property +func BoolProperty() *Schema { return BooleanProperty() } + +// StringProperty creates a string property +func StringProperty() *Schema { + return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}}} +} + +// CharProperty creates a string property +func CharProperty() *Schema { + return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}}} +} + +// Float64Property creates a float64/double property +func Float64Property() *Schema { + return &Schema{SchemaProps: SchemaProps{Type: []string{"number"}, Format: "double"}} +} + +// Float32Property creates a float32/float property +func Float32Property() *Schema { + return &Schema{SchemaProps: SchemaProps{Type: []string{"number"}, Format: "float"}} +} + +// Int8Property creates an int8 property +func Int8Property() *Schema { + return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int8"}} +} + +// Int16Property creates an int16 property +func Int16Property() *Schema { + return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int16"}} +} + +// Int32Property creates an int32 property +func Int32Property() *Schema { + return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int32"}} +} + +// Int64Property creates an int64 property +func Int64Property() *Schema { + return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int64"}} +} + +// StrFmtProperty creates a property for the named string format +func StrFmtProperty(format string) *Schema { + return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}, Format: format}} +} + +// DateProperty creates a date property +func DateProperty() *Schema { + return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}, Format: "date"}} +} + +// DateTimeProperty creates a date time property +func DateTimeProperty() *Schema { + return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}, Format: "date-time"}} +} + +// MapProperty creates a map property +func MapProperty(property *Schema) *Schema { + return &Schema{SchemaProps: SchemaProps{Type: []string{"object"}, + AdditionalProperties: &SchemaOrBool{Allows: true, Schema: property}}} +} + +// RefProperty creates a ref property +func RefProperty(name string) *Schema { + return &Schema{SchemaProps: SchemaProps{Ref: MustCreateRef(name)}} +} + +// RefSchema creates a ref property +func RefSchema(name string) *Schema { + return &Schema{SchemaProps: SchemaProps{Ref: MustCreateRef(name)}} +} + +// ArrayProperty creates an array property +func ArrayProperty(items *Schema) *Schema { + if items == nil { + return &Schema{SchemaProps: SchemaProps{Type: []string{"array"}}} + } + return &Schema{SchemaProps: SchemaProps{Items: &SchemaOrArray{Schema: items}, Type: []string{"array"}}} +} + +// ComposedSchema creates a schema with allOf +func ComposedSchema(schemas ...Schema) *Schema { + s := new(Schema) + s.AllOf = schemas + return s +} + +// SchemaURL represents a schema url +type SchemaURL string + +// MarshalJSON marshal this to JSON +func (r SchemaURL) MarshalJSON() ([]byte, error) { + if r == "" { + return []byte("{}"), nil + } + v := map[string]interface{}{"$schema": string(r)} + return json.Marshal(v) +} + +// UnmarshalJSON unmarshal this from JSON +func (r *SchemaURL) UnmarshalJSON(data []byte) error { + var v map[string]interface{} + if err := json.Unmarshal(data, &v); err != nil { + return err + } + return r.fromMap(v) +} + +func (r *SchemaURL) fromMap(v map[string]interface{}) error { + if v == nil { + return nil + } + if vv, ok := v["$schema"]; ok { + if str, ok := vv.(string); ok { + u, err := url.Parse(str) + if err != nil { + return err + } + + *r = SchemaURL(u.String()) + } + } + return nil +} + +// SchemaProps describes a JSON schema (draft 4) +type SchemaProps struct { + ID string `json:"id,omitempty"` + Ref Ref `json:"-"` + Schema SchemaURL `json:"-"` + Description string `json:"description,omitempty"` + Type StringOrArray `json:"type,omitempty"` + Nullable bool `json:"nullable,omitempty"` + Format string `json:"format,omitempty"` + Title string `json:"title,omitempty"` + Default interface{} `json:"default,omitempty"` + Maximum *float64 `json:"maximum,omitempty"` + ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"` + Minimum *float64 `json:"minimum,omitempty"` + ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"` + MaxLength *int64 `json:"maxLength,omitempty"` + MinLength *int64 `json:"minLength,omitempty"` + Pattern string `json:"pattern,omitempty"` + MaxItems *int64 `json:"maxItems,omitempty"` + MinItems *int64 `json:"minItems,omitempty"` + UniqueItems bool `json:"uniqueItems,omitempty"` + MultipleOf *float64 `json:"multipleOf,omitempty"` + Enum []interface{} `json:"enum,omitempty"` + MaxProperties *int64 `json:"maxProperties,omitempty"` + MinProperties *int64 `json:"minProperties,omitempty"` + Required []string `json:"required,omitempty"` + Items *SchemaOrArray `json:"items,omitempty"` + AllOf []Schema `json:"allOf,omitempty"` + OneOf []Schema `json:"oneOf,omitempty"` + AnyOf []Schema `json:"anyOf,omitempty"` + Not *Schema `json:"not,omitempty"` + Properties SchemaProperties `json:"properties,omitempty"` + AdditionalProperties *SchemaOrBool `json:"additionalProperties,omitempty"` + PatternProperties SchemaProperties `json:"patternProperties,omitempty"` + Dependencies Dependencies `json:"dependencies,omitempty"` + AdditionalItems *SchemaOrBool `json:"additionalItems,omitempty"` + Definitions Definitions `json:"definitions,omitempty"` +} + +// SwaggerSchemaProps are additional properties supported by swagger schemas, but not JSON-schema (draft 4) +type SwaggerSchemaProps struct { + Discriminator string `json:"discriminator,omitempty"` + ReadOnly bool `json:"readOnly,omitempty"` + XML *XMLObject `json:"xml,omitempty"` + ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"` + Example interface{} `json:"example,omitempty"` +} + +// Schema the schema object allows the definition of input and output data types. +// These types can be objects, but also primitives and arrays. +// This object is based on the [JSON Schema Specification Draft 4](http://json-schema.org/) +// and uses a predefined subset of it. +// On top of this subset, there are extensions provided by this specification to allow for more complete documentation. +// +// For more information: http://goo.gl/8us55a#schemaObject +type Schema struct { + VendorExtensible + SchemaProps + SwaggerSchemaProps + ExtraProps map[string]interface{} `json:"-"` +} + +// JSONLookup implements an interface to customize json pointer lookup +func (s Schema) JSONLookup(token string) (interface{}, error) { + if ex, ok := s.Extensions[token]; ok { + return &ex, nil + } + + if ex, ok := s.ExtraProps[token]; ok { + return &ex, nil + } + + r, _, err := jsonpointer.GetForToken(s.SchemaProps, token) + if r != nil || (err != nil && !strings.HasPrefix(err.Error(), "object has no field")) { + return r, err + } + r, _, err = jsonpointer.GetForToken(s.SwaggerSchemaProps, token) + return r, err +} + +// WithID sets the id for this schema, allows for chaining +func (s *Schema) WithID(id string) *Schema { + s.ID = id + return s +} + +// WithTitle sets the title for this schema, allows for chaining +func (s *Schema) WithTitle(title string) *Schema { + s.Title = title + return s +} + +// WithDescription sets the description for this schema, allows for chaining +func (s *Schema) WithDescription(description string) *Schema { + s.Description = description + return s +} + +// WithProperties sets the properties for this schema +func (s *Schema) WithProperties(schemas map[string]Schema) *Schema { + s.Properties = schemas + return s +} + +// SetProperty sets a property on this schema +func (s *Schema) SetProperty(name string, schema Schema) *Schema { + if s.Properties == nil { + s.Properties = make(map[string]Schema) + } + s.Properties[name] = schema + return s +} + +// WithAllOf sets the all of property +func (s *Schema) WithAllOf(schemas ...Schema) *Schema { + s.AllOf = schemas + return s +} + +// WithMaxProperties sets the max number of properties an object can have +func (s *Schema) WithMaxProperties(max int64) *Schema { + s.MaxProperties = &max + return s +} + +// WithMinProperties sets the min number of properties an object must have +func (s *Schema) WithMinProperties(min int64) *Schema { + s.MinProperties = &min + return s +} + +// Typed sets the type of this schema for a single value item +func (s *Schema) Typed(tpe, format string) *Schema { + s.Type = []string{tpe} + s.Format = format + return s +} + +// AddType adds a type with potential format to the types for this schema +func (s *Schema) AddType(tpe, format string) *Schema { + s.Type = append(s.Type, tpe) + if format != "" { + s.Format = format + } + return s +} + +// AsNullable flags this schema as nullable. +func (s *Schema) AsNullable() *Schema { + s.Nullable = true + return s +} + +// CollectionOf a fluent builder method for an array parameter +func (s *Schema) CollectionOf(items Schema) *Schema { + s.Type = []string{jsonArray} + s.Items = &SchemaOrArray{Schema: &items} + return s +} + +// WithDefault sets the default value on this parameter +func (s *Schema) WithDefault(defaultValue interface{}) *Schema { + s.Default = defaultValue + return s +} + +// WithRequired flags this parameter as required +func (s *Schema) WithRequired(items ...string) *Schema { + s.Required = items + return s +} + +// AddRequired adds field names to the required properties array +func (s *Schema) AddRequired(items ...string) *Schema { + s.Required = append(s.Required, items...) + return s +} + +// WithMaxLength sets a max length value +func (s *Schema) WithMaxLength(max int64) *Schema { + s.MaxLength = &max + return s +} + +// WithMinLength sets a min length value +func (s *Schema) WithMinLength(min int64) *Schema { + s.MinLength = &min + return s +} + +// WithPattern sets a pattern value +func (s *Schema) WithPattern(pattern string) *Schema { + s.Pattern = pattern + return s +} + +// WithMultipleOf sets a multiple of value +func (s *Schema) WithMultipleOf(number float64) *Schema { + s.MultipleOf = &number + return s +} + +// WithMaximum sets a maximum number value +func (s *Schema) WithMaximum(max float64, exclusive bool) *Schema { + s.Maximum = &max + s.ExclusiveMaximum = exclusive + return s +} + +// WithMinimum sets a minimum number value +func (s *Schema) WithMinimum(min float64, exclusive bool) *Schema { + s.Minimum = &min + s.ExclusiveMinimum = exclusive + return s +} + +// WithEnum sets a the enum values (replace) +func (s *Schema) WithEnum(values ...interface{}) *Schema { + s.Enum = append([]interface{}{}, values...) + return s +} + +// WithMaxItems sets the max items +func (s *Schema) WithMaxItems(size int64) *Schema { + s.MaxItems = &size + return s +} + +// WithMinItems sets the min items +func (s *Schema) WithMinItems(size int64) *Schema { + s.MinItems = &size + return s +} + +// UniqueValues dictates that this array can only have unique items +func (s *Schema) UniqueValues() *Schema { + s.UniqueItems = true + return s +} + +// AllowDuplicates this array can have duplicates +func (s *Schema) AllowDuplicates() *Schema { + s.UniqueItems = false + return s +} + +// AddToAllOf adds a schema to the allOf property +func (s *Schema) AddToAllOf(schemas ...Schema) *Schema { + s.AllOf = append(s.AllOf, schemas...) + return s +} + +// WithDiscriminator sets the name of the discriminator field +func (s *Schema) WithDiscriminator(discriminator string) *Schema { + s.Discriminator = discriminator + return s +} + +// AsReadOnly flags this schema as readonly +func (s *Schema) AsReadOnly() *Schema { + s.ReadOnly = true + return s +} + +// AsWritable flags this schema as writeable (not read-only) +func (s *Schema) AsWritable() *Schema { + s.ReadOnly = false + return s +} + +// WithExample sets the example for this schema +func (s *Schema) WithExample(example interface{}) *Schema { + s.Example = example + return s +} + +// WithExternalDocs sets/removes the external docs for/from this schema. +// When you pass empty strings as params the external documents will be removed. +// When you pass non-empty string as one value then those values will be used on the external docs object. +// So when you pass a non-empty description, you should also pass the url and vice versa. +func (s *Schema) WithExternalDocs(description, url string) *Schema { + if description == "" && url == "" { + s.ExternalDocs = nil + return s + } + + if s.ExternalDocs == nil { + s.ExternalDocs = &ExternalDocumentation{} + } + s.ExternalDocs.Description = description + s.ExternalDocs.URL = url + return s +} + +// WithXMLName sets the xml name for the object +func (s *Schema) WithXMLName(name string) *Schema { + if s.XML == nil { + s.XML = new(XMLObject) + } + s.XML.Name = name + return s +} + +// WithXMLNamespace sets the xml namespace for the object +func (s *Schema) WithXMLNamespace(namespace string) *Schema { + if s.XML == nil { + s.XML = new(XMLObject) + } + s.XML.Namespace = namespace + return s +} + +// WithXMLPrefix sets the xml prefix for the object +func (s *Schema) WithXMLPrefix(prefix string) *Schema { + if s.XML == nil { + s.XML = new(XMLObject) + } + s.XML.Prefix = prefix + return s +} + +// AsXMLAttribute flags this object as xml attribute +func (s *Schema) AsXMLAttribute() *Schema { + if s.XML == nil { + s.XML = new(XMLObject) + } + s.XML.Attribute = true + return s +} + +// AsXMLElement flags this object as an xml node +func (s *Schema) AsXMLElement() *Schema { + if s.XML == nil { + s.XML = new(XMLObject) + } + s.XML.Attribute = false + return s +} + +// AsWrappedXML flags this object as wrapped, this is mostly useful for array types +func (s *Schema) AsWrappedXML() *Schema { + if s.XML == nil { + s.XML = new(XMLObject) + } + s.XML.Wrapped = true + return s +} + +// AsUnwrappedXML flags this object as an xml node +func (s *Schema) AsUnwrappedXML() *Schema { + if s.XML == nil { + s.XML = new(XMLObject) + } + s.XML.Wrapped = false + return s +} + +// SetValidations defines all schema validations. +// +// NOTE: Required, ReadOnly, AllOf, AnyOf, OneOf and Not are not considered. +func (s *Schema) SetValidations(val SchemaValidations) { + s.Maximum = val.Maximum + s.ExclusiveMaximum = val.ExclusiveMaximum + s.Minimum = val.Minimum + s.ExclusiveMinimum = val.ExclusiveMinimum + s.MaxLength = val.MaxLength + s.MinLength = val.MinLength + s.Pattern = val.Pattern + s.MaxItems = val.MaxItems + s.MinItems = val.MinItems + s.UniqueItems = val.UniqueItems + s.MultipleOf = val.MultipleOf + s.Enum = val.Enum + s.MinProperties = val.MinProperties + s.MaxProperties = val.MaxProperties + s.PatternProperties = val.PatternProperties +} + +// WithValidations is a fluent method to set schema validations +func (s *Schema) WithValidations(val SchemaValidations) *Schema { + s.SetValidations(val) + return s +} + +// Validations returns a clone of the validations for this schema +func (s Schema) Validations() SchemaValidations { + return SchemaValidations{ + CommonValidations: CommonValidations{ + Maximum: s.Maximum, + ExclusiveMaximum: s.ExclusiveMaximum, + Minimum: s.Minimum, + ExclusiveMinimum: s.ExclusiveMinimum, + MaxLength: s.MaxLength, + MinLength: s.MinLength, + Pattern: s.Pattern, + MaxItems: s.MaxItems, + MinItems: s.MinItems, + UniqueItems: s.UniqueItems, + MultipleOf: s.MultipleOf, + Enum: s.Enum, + }, + MinProperties: s.MinProperties, + MaxProperties: s.MaxProperties, + PatternProperties: s.PatternProperties, + } +} + +// MarshalJSON marshal this to JSON +func (s Schema) MarshalJSON() ([]byte, error) { + b1, err := json.Marshal(s.SchemaProps) + if err != nil { + return nil, fmt.Errorf("schema props %v", err) + } + b2, err := json.Marshal(s.VendorExtensible) + if err != nil { + return nil, fmt.Errorf("vendor props %v", err) + } + b3, err := s.Ref.MarshalJSON() + if err != nil { + return nil, fmt.Errorf("ref prop %v", err) + } + b4, err := s.Schema.MarshalJSON() + if err != nil { + return nil, fmt.Errorf("schema prop %v", err) + } + b5, err := json.Marshal(s.SwaggerSchemaProps) + if err != nil { + return nil, fmt.Errorf("common validations %v", err) + } + var b6 []byte + if s.ExtraProps != nil { + jj, err := json.Marshal(s.ExtraProps) + if err != nil { + return nil, fmt.Errorf("extra props %v", err) + } + b6 = jj + } + return swag.ConcatJSON(b1, b2, b3, b4, b5, b6), nil +} + +// UnmarshalJSON marshal this from JSON +func (s *Schema) UnmarshalJSON(data []byte) error { + props := struct { + SchemaProps + SwaggerSchemaProps + }{} + if err := json.Unmarshal(data, &props); err != nil { + return err + } + + sch := Schema{ + SchemaProps: props.SchemaProps, + SwaggerSchemaProps: props.SwaggerSchemaProps, + } + + var d map[string]interface{} + if err := json.Unmarshal(data, &d); err != nil { + return err + } + + _ = sch.Ref.fromMap(d) + _ = sch.Schema.fromMap(d) + + delete(d, "$ref") + delete(d, "$schema") + for _, pn := range swag.DefaultJSONNameProvider.GetJSONNames(s) { + delete(d, pn) + } + + for k, vv := range d { + lk := strings.ToLower(k) + if strings.HasPrefix(lk, "x-") { + if sch.Extensions == nil { + sch.Extensions = map[string]interface{}{} + } + sch.Extensions[k] = vv + continue + } + if sch.ExtraProps == nil { + sch.ExtraProps = map[string]interface{}{} + } + sch.ExtraProps[k] = vv + } + + *s = sch + + return nil +} diff --git a/vendor/github.com/go-openapi/spec/schema_loader.go b/vendor/github.com/go-openapi/spec/schema_loader.go new file mode 100644 index 00000000..b81175af --- /dev/null +++ b/vendor/github.com/go-openapi/spec/schema_loader.go @@ -0,0 +1,338 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + "fmt" + "log" + "net/url" + "reflect" + "strings" + + "github.com/go-openapi/swag" +) + +// PathLoader is a function to use when loading remote refs. +// +// This is a package level default. It may be overridden or bypassed by +// specifying the loader in ExpandOptions. +// +// NOTE: if you are using the go-openapi/loads package, it will override +// this value with its own default (a loader to retrieve YAML documents as +// well as JSON ones). +var PathLoader = func(pth string) (json.RawMessage, error) { + data, err := swag.LoadFromFileOrHTTP(pth) + if err != nil { + return nil, err + } + return json.RawMessage(data), nil +} + +// resolverContext allows to share a context during spec processing. +// At the moment, it just holds the index of circular references found. +type resolverContext struct { + // circulars holds all visited circular references, to shortcircuit $ref resolution. + // + // This structure is privately instantiated and needs not be locked against + // concurrent access, unless we chose to implement a parallel spec walking. + circulars map[string]bool + basePath string + loadDoc func(string) (json.RawMessage, error) + rootID string +} + +func newResolverContext(options *ExpandOptions) *resolverContext { + expandOptions := optionsOrDefault(options) + + // path loader may be overridden by options + var loader func(string) (json.RawMessage, error) + if expandOptions.PathLoader == nil { + loader = PathLoader + } else { + loader = expandOptions.PathLoader + } + + return &resolverContext{ + circulars: make(map[string]bool), + basePath: expandOptions.RelativeBase, // keep the root base path in context + loadDoc: loader, + } +} + +type schemaLoader struct { + root interface{} + options *ExpandOptions + cache ResolutionCache + context *resolverContext +} + +func (r *schemaLoader) transitiveResolver(basePath string, ref Ref) *schemaLoader { + if ref.IsRoot() || ref.HasFragmentOnly { + return r + } + + baseRef := MustCreateRef(basePath) + currentRef := normalizeRef(&ref, basePath) + if strings.HasPrefix(currentRef.String(), baseRef.String()) { + return r + } + + // set a new root against which to resolve + rootURL := currentRef.GetURL() + rootURL.Fragment = "" + root, _ := r.cache.Get(rootURL.String()) + + // shallow copy of resolver options to set a new RelativeBase when + // traversing multiple documents + newOptions := r.options + newOptions.RelativeBase = rootURL.String() + + return defaultSchemaLoader(root, newOptions, r.cache, r.context) +} + +func (r *schemaLoader) updateBasePath(transitive *schemaLoader, basePath string) string { + if transitive != r { + if transitive.options != nil && transitive.options.RelativeBase != "" { + return normalizeBase(transitive.options.RelativeBase) + } + } + + return basePath +} + +func (r *schemaLoader) resolveRef(ref *Ref, target interface{}, basePath string) error { + tgt := reflect.ValueOf(target) + if tgt.Kind() != reflect.Ptr { + return ErrResolveRefNeedsAPointer + } + + if ref.GetURL() == nil { + return nil + } + + var ( + res interface{} + data interface{} + err error + ) + + // Resolve against the root if it isn't nil, and if ref is pointing at the root, or has a fragment only which means + // it is pointing somewhere in the root. + root := r.root + if (ref.IsRoot() || ref.HasFragmentOnly) && root == nil && basePath != "" { + if baseRef, erb := NewRef(basePath); erb == nil { + root, _, _, _ = r.load(baseRef.GetURL()) + } + } + + if (ref.IsRoot() || ref.HasFragmentOnly) && root != nil { + data = root + } else { + baseRef := normalizeRef(ref, basePath) + data, _, _, err = r.load(baseRef.GetURL()) + if err != nil { + return err + } + } + + res = data + if ref.String() != "" { + res, _, err = ref.GetPointer().Get(data) + if err != nil { + return err + } + } + return swag.DynamicJSONToStruct(res, target) +} + +func (r *schemaLoader) load(refURL *url.URL) (interface{}, url.URL, bool, error) { + debugLog("loading schema from url: %s", refURL) + toFetch := *refURL + toFetch.Fragment = "" + + var err error + pth := toFetch.String() + normalized := normalizeBase(pth) + debugLog("loading doc from: %s", normalized) + + 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()) + if fromCache { + return data, toFetch, fromCache, nil + } + + b, err := r.context.loadDoc(normalized) + if err != nil { + return nil, url.URL{}, false, err + } + + var doc interface{} + if err := json.Unmarshal(b, &doc); err != nil { + return nil, url.URL{}, false, err + } + r.cache.Set(normalized, doc) + + return doc, toFetch, fromCache, nil +} + +// isCircular detects cycles in sequences of $ref. +// +// It relies on a private context (which needs not be locked). +func (r *schemaLoader) isCircular(ref *Ref, basePath string, parentRefs ...string) (foundCycle bool) { + normalizedRef := normalizeURI(ref.String(), basePath) + if _, ok := r.context.circulars[normalizedRef]; ok { + // circular $ref has been already detected in another explored cycle + foundCycle = true + return + } + foundCycle = swag.ContainsStrings(parentRefs, normalizedRef) // normalized windows url's are lower cased + if foundCycle { + r.context.circulars[normalizedRef] = true + } + return +} + +// Resolve resolves a reference against basePath and stores the result in target. +// +// Resolve is not in charge of following references: it only resolves ref by following its URL. +// +// If the schema the ref is referring to holds nested refs, Resolve doesn't resolve them. +// +// If basePath is an empty string, ref is resolved against the root schema stored in the schemaLoader struct +func (r *schemaLoader) Resolve(ref *Ref, target interface{}, basePath string) error { + return r.resolveRef(ref, target, basePath) +} + +func (r *schemaLoader) deref(input interface{}, parentRefs []string, basePath string) error { + var ref *Ref + switch refable := input.(type) { + case *Schema: + ref = &refable.Ref + case *Parameter: + ref = &refable.Ref + case *Response: + ref = &refable.Ref + case *PathItem: + ref = &refable.Ref + default: + return fmt.Errorf("unsupported type: %T: %w", input, ErrDerefUnsupportedType) + } + + curRef := ref.String() + if curRef == "" { + return nil + } + + normalizedRef := normalizeRef(ref, basePath) + normalizedBasePath := normalizedRef.RemoteURI() + + if r.isCircular(normalizedRef, basePath, parentRefs...) { + return nil + } + + if err := r.resolveRef(ref, input, basePath); r.shouldStopOnError(err) { + return err + } + + if ref.String() == "" || ref.String() == curRef { + // done with rereferencing + return nil + } + + parentRefs = append(parentRefs, normalizedRef.String()) + return r.deref(input, parentRefs, normalizedBasePath) +} + +func (r *schemaLoader) shouldStopOnError(err error) bool { + if err != nil && !r.options.ContinueOnError { + return true + } + + if err != nil { + log.Println(err) + } + + return false +} + +func (r *schemaLoader) setSchemaID(target interface{}, id, basePath string) (string, string) { + debugLog("schema has ID: %s", id) + + // handling the case when id is a folder + // remember that basePath has to point to a file + var refPath string + if strings.HasSuffix(id, "/") { + // ensure this is detected as a file, not a folder + refPath = fmt.Sprintf("%s%s", id, "placeholder.json") + } else { + refPath = id + } + + // updates the current base path + // * important: ID can be a relative path + // * registers target to be fetchable from the new base proposed by this id + newBasePath := normalizeURI(refPath, basePath) + + // store found IDs for possible future reuse in $ref + r.cache.Set(newBasePath, target) + + // the root document has an ID: all $ref relative to that ID may + // be rebased relative to the root document + if basePath == r.context.basePath { + debugLog("root document is a schema with ID: %s (normalized as:%s)", id, newBasePath) + r.context.rootID = newBasePath + } + + return newBasePath, refPath +} + +func defaultSchemaLoader( + root interface{}, + expandOptions *ExpandOptions, + cache ResolutionCache, + context *resolverContext) *schemaLoader { + + if expandOptions == nil { + expandOptions = &ExpandOptions{} + } + + cache = cacheOrDefault(cache) + + if expandOptions.RelativeBase == "" { + // if no relative base is provided, assume the root document + // contains all $ref, or at least, that the relative documents + // may be resolved from the current working directory. + expandOptions.RelativeBase = baseForRoot(root, cache) + } + debugLog("effective expander options: %#v", expandOptions) + + if context == nil { + context = newResolverContext(expandOptions) + } + + return &schemaLoader{ + root: root, + options: expandOptions, + cache: cache, + context: context, + } +} diff --git a/vendor/github.com/go-openapi/spec/security_scheme.go b/vendor/github.com/go-openapi/spec/security_scheme.go new file mode 100644 index 00000000..9d0bdae9 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/security_scheme.go @@ -0,0 +1,170 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + + "github.com/go-openapi/jsonpointer" + "github.com/go-openapi/swag" +) + +const ( + basic = "basic" + apiKey = "apiKey" + oauth2 = "oauth2" + implicit = "implicit" + password = "password" + application = "application" + accessCode = "accessCode" +) + +// BasicAuth creates a basic auth security scheme +func BasicAuth() *SecurityScheme { + return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{Type: basic}} +} + +// APIKeyAuth creates an api key auth security scheme +func APIKeyAuth(fieldName, valueSource string) *SecurityScheme { + return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{Type: apiKey, Name: fieldName, In: valueSource}} +} + +// OAuth2Implicit creates an implicit flow oauth2 security scheme +func OAuth2Implicit(authorizationURL string) *SecurityScheme { + return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{ + Type: oauth2, + Flow: implicit, + AuthorizationURL: authorizationURL, + }} +} + +// OAuth2Password creates a password flow oauth2 security scheme +func OAuth2Password(tokenURL string) *SecurityScheme { + return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{ + Type: oauth2, + Flow: password, + TokenURL: tokenURL, + }} +} + +// OAuth2Application creates an application flow oauth2 security scheme +func OAuth2Application(tokenURL string) *SecurityScheme { + return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{ + Type: oauth2, + Flow: application, + TokenURL: tokenURL, + }} +} + +// OAuth2AccessToken creates an access token flow oauth2 security scheme +func OAuth2AccessToken(authorizationURL, tokenURL string) *SecurityScheme { + return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{ + Type: oauth2, + Flow: accessCode, + AuthorizationURL: authorizationURL, + TokenURL: tokenURL, + }} +} + +// SecuritySchemeProps describes a swagger security scheme in the securityDefinitions section +type SecuritySchemeProps struct { + Description string `json:"description,omitempty"` + Type string `json:"type"` + Name string `json:"name,omitempty"` // api key + In string `json:"in,omitempty"` // api key + Flow string `json:"flow,omitempty"` // oauth2 + AuthorizationURL string `json:"authorizationUrl"` // oauth2 + TokenURL string `json:"tokenUrl,omitempty"` // oauth2 + Scopes map[string]string `json:"scopes,omitempty"` // oauth2 +} + +// AddScope adds a scope to this security scheme +func (s *SecuritySchemeProps) AddScope(scope, description string) { + if s.Scopes == nil { + s.Scopes = make(map[string]string) + } + s.Scopes[scope] = description +} + +// SecurityScheme allows the definition of a security scheme that can be used by the operations. +// Supported schemes are basic authentication, an API key (either as a header or as a query parameter) +// and OAuth2's common flows (implicit, password, application and access code). +// +// For more information: http://goo.gl/8us55a#securitySchemeObject +type SecurityScheme struct { + VendorExtensible + SecuritySchemeProps +} + +// JSONLookup implements an interface to customize json pointer lookup +func (s SecurityScheme) JSONLookup(token string) (interface{}, error) { + if ex, ok := s.Extensions[token]; ok { + return &ex, nil + } + + r, _, err := jsonpointer.GetForToken(s.SecuritySchemeProps, token) + return r, err +} + +// MarshalJSON marshal this to JSON +func (s SecurityScheme) MarshalJSON() ([]byte, error) { + var ( + b1 []byte + err error + ) + + if s.Type == oauth2 && (s.Flow == "implicit" || s.Flow == "accessCode") { + // when oauth2 for implicit or accessCode flows, empty AuthorizationURL is added as empty string + b1, err = json.Marshal(s.SecuritySchemeProps) + } else { + // when not oauth2, empty AuthorizationURL should be omitted + b1, err = json.Marshal(struct { + Description string `json:"description,omitempty"` + Type string `json:"type"` + Name string `json:"name,omitempty"` // api key + In string `json:"in,omitempty"` // api key + Flow string `json:"flow,omitempty"` // oauth2 + AuthorizationURL string `json:"authorizationUrl,omitempty"` // oauth2 + TokenURL string `json:"tokenUrl,omitempty"` // oauth2 + Scopes map[string]string `json:"scopes,omitempty"` // oauth2 + }{ + Description: s.Description, + Type: s.Type, + Name: s.Name, + In: s.In, + Flow: s.Flow, + AuthorizationURL: s.AuthorizationURL, + TokenURL: s.TokenURL, + Scopes: s.Scopes, + }) + } + if err != nil { + return nil, err + } + + b2, err := json.Marshal(s.VendorExtensible) + if err != nil { + return nil, err + } + return swag.ConcatJSON(b1, b2), nil +} + +// UnmarshalJSON marshal this from JSON +func (s *SecurityScheme) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &s.SecuritySchemeProps); err != nil { + return err + } + return json.Unmarshal(data, &s.VendorExtensible) +} diff --git a/vendor/github.com/go-openapi/spec/spec.go b/vendor/github.com/go-openapi/spec/spec.go new file mode 100644 index 00000000..7d38b6e6 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/spec.go @@ -0,0 +1,78 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" +) + +//go:generate curl -L --progress -o ./schemas/v2/schema.json http://swagger.io/v2/schema.json +//go:generate curl -L --progress -o ./schemas/jsonschema-draft-04.json http://json-schema.org/draft-04/schema +//go:generate go-bindata -pkg=spec -prefix=./schemas -ignore=.*\.md ./schemas/... +//go:generate perl -pi -e s,Json,JSON,g bindata.go + +const ( + // SwaggerSchemaURL the url for the swagger 2.0 schema to validate specs + SwaggerSchemaURL = "http://swagger.io/v2/schema.json#" + // JSONSchemaURL the url for the json schema schema + JSONSchemaURL = "http://json-schema.org/draft-04/schema#" +) + +// MustLoadJSONSchemaDraft04 panics when Swagger20Schema returns an error +func MustLoadJSONSchemaDraft04() *Schema { + d, e := JSONSchemaDraft04() + if e != nil { + panic(e) + } + return d +} + +// JSONSchemaDraft04 loads the json schema document for json shema draft04 +func JSONSchemaDraft04() (*Schema, error) { + b, err := Asset("jsonschema-draft-04.json") + if err != nil { + return nil, err + } + + schema := new(Schema) + if err := json.Unmarshal(b, schema); err != nil { + return nil, err + } + return schema, nil +} + +// MustLoadSwagger20Schema panics when Swagger20Schema returns an error +func MustLoadSwagger20Schema() *Schema { + d, e := Swagger20Schema() + if e != nil { + panic(e) + } + return d +} + +// Swagger20Schema loads the swagger 2.0 schema from the embedded assets +func Swagger20Schema() (*Schema, error) { + + b, err := Asset("v2/schema.json") + if err != nil { + return nil, err + } + + schema := new(Schema) + if err := json.Unmarshal(b, schema); err != nil { + return nil, err + } + return schema, nil +} diff --git a/vendor/github.com/go-openapi/spec/swagger.go b/vendor/github.com/go-openapi/spec/swagger.go new file mode 100644 index 00000000..44722ffd --- /dev/null +++ b/vendor/github.com/go-openapi/spec/swagger.go @@ -0,0 +1,448 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "bytes" + "encoding/gob" + "encoding/json" + "fmt" + "strconv" + + "github.com/go-openapi/jsonpointer" + "github.com/go-openapi/swag" +) + +// Swagger this is the root document object for the API specification. +// It combines what previously was the Resource Listing and API Declaration (version 1.2 and earlier) +// together into one document. +// +// For more information: http://goo.gl/8us55a#swagger-object- +type Swagger struct { + VendorExtensible + SwaggerProps +} + +// JSONLookup look up a value by the json property name +func (s Swagger) JSONLookup(token string) (interface{}, error) { + if ex, ok := s.Extensions[token]; ok { + return &ex, nil + } + r, _, err := jsonpointer.GetForToken(s.SwaggerProps, token) + return r, err +} + +// MarshalJSON marshals this swagger structure to json +func (s Swagger) MarshalJSON() ([]byte, error) { + b1, err := json.Marshal(s.SwaggerProps) + if err != nil { + return nil, err + } + b2, err := json.Marshal(s.VendorExtensible) + if err != nil { + return nil, err + } + return swag.ConcatJSON(b1, b2), nil +} + +// UnmarshalJSON unmarshals a swagger spec from json +func (s *Swagger) UnmarshalJSON(data []byte) error { + var sw Swagger + if err := json.Unmarshal(data, &sw.SwaggerProps); err != nil { + return err + } + if err := json.Unmarshal(data, &sw.VendorExtensible); err != nil { + return err + } + *s = sw + return nil +} + +// GobEncode provides a safe gob encoder for Swagger, including extensions +func (s Swagger) GobEncode() ([]byte, error) { + var b bytes.Buffer + raw := struct { + Props SwaggerProps + Ext VendorExtensible + }{ + Props: s.SwaggerProps, + Ext: s.VendorExtensible, + } + err := gob.NewEncoder(&b).Encode(raw) + return b.Bytes(), err +} + +// GobDecode provides a safe gob decoder for Swagger, including extensions +func (s *Swagger) GobDecode(b []byte) error { + var raw struct { + Props SwaggerProps + Ext VendorExtensible + } + buf := bytes.NewBuffer(b) + err := gob.NewDecoder(buf).Decode(&raw) + if err != nil { + return err + } + s.SwaggerProps = raw.Props + s.VendorExtensible = raw.Ext + return nil +} + +// SwaggerProps captures the top-level properties of an Api specification +// +// NOTE: validation rules +// - the scheme, when present must be from [http, https, ws, wss] +// - BasePath must start with a leading "/" +// - Paths is required +type SwaggerProps struct { + ID string `json:"id,omitempty"` + Consumes []string `json:"consumes,omitempty"` + Produces []string `json:"produces,omitempty"` + Schemes []string `json:"schemes,omitempty"` + Swagger string `json:"swagger,omitempty"` + Info *Info `json:"info,omitempty"` + Host string `json:"host,omitempty"` + BasePath string `json:"basePath,omitempty"` + Paths *Paths `json:"paths"` + Definitions Definitions `json:"definitions,omitempty"` + Parameters map[string]Parameter `json:"parameters,omitempty"` + Responses map[string]Response `json:"responses,omitempty"` + SecurityDefinitions SecurityDefinitions `json:"securityDefinitions,omitempty"` + Security []map[string][]string `json:"security,omitempty"` + Tags []Tag `json:"tags,omitempty"` + ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"` +} + +type swaggerPropsAlias SwaggerProps + +type gobSwaggerPropsAlias struct { + Security []map[string]struct { + List []string + Pad bool + } + Alias *swaggerPropsAlias + SecurityIsEmpty bool +} + +// GobEncode provides a safe gob encoder for SwaggerProps, including empty security requirements +func (o SwaggerProps) GobEncode() ([]byte, error) { + raw := gobSwaggerPropsAlias{ + Alias: (*swaggerPropsAlias)(&o), + } + + var b bytes.Buffer + if o.Security == nil { + // nil security requirement + err := gob.NewEncoder(&b).Encode(raw) + return b.Bytes(), err + } + + if len(o.Security) == 0 { + // empty, but non-nil security requirement + raw.SecurityIsEmpty = true + raw.Alias.Security = nil + err := gob.NewEncoder(&b).Encode(raw) + return b.Bytes(), err + } + + raw.Security = make([]map[string]struct { + List []string + Pad bool + }, 0, len(o.Security)) + for _, req := range o.Security { + v := make(map[string]struct { + List []string + Pad bool + }, len(req)) + for k, val := range req { + v[k] = struct { + List []string + Pad bool + }{ + List: val, + } + } + raw.Security = append(raw.Security, v) + } + + err := gob.NewEncoder(&b).Encode(raw) + return b.Bytes(), err +} + +// GobDecode provides a safe gob decoder for SwaggerProps, including empty security requirements +func (o *SwaggerProps) GobDecode(b []byte) error { + var raw gobSwaggerPropsAlias + + buf := bytes.NewBuffer(b) + err := gob.NewDecoder(buf).Decode(&raw) + if err != nil { + return err + } + if raw.Alias == nil { + return nil + } + + switch { + case raw.SecurityIsEmpty: + // empty, but non-nil security requirement + raw.Alias.Security = []map[string][]string{} + case len(raw.Alias.Security) == 0: + // nil security requirement + raw.Alias.Security = nil + default: + raw.Alias.Security = make([]map[string][]string, 0, len(raw.Security)) + for _, req := range raw.Security { + v := make(map[string][]string, len(req)) + for k, val := range req { + v[k] = make([]string, 0, len(val.List)) + v[k] = append(v[k], val.List...) + } + raw.Alias.Security = append(raw.Alias.Security, v) + } + } + + *o = *(*SwaggerProps)(raw.Alias) + return nil +} + +// Dependencies represent a dependencies property +type Dependencies map[string]SchemaOrStringArray + +// SchemaOrBool represents a schema or boolean value, is biased towards true for the boolean property +type SchemaOrBool struct { + Allows bool + Schema *Schema +} + +// JSONLookup implements an interface to customize json pointer lookup +func (s SchemaOrBool) JSONLookup(token string) (interface{}, error) { + if token == "allows" { + return s.Allows, nil + } + r, _, err := jsonpointer.GetForToken(s.Schema, token) + return r, err +} + +var jsTrue = []byte("true") +var jsFalse = []byte("false") + +// MarshalJSON convert this object to JSON +func (s SchemaOrBool) MarshalJSON() ([]byte, error) { + if s.Schema != nil { + return json.Marshal(s.Schema) + } + + if s.Schema == nil && !s.Allows { + return jsFalse, nil + } + return jsTrue, nil +} + +// UnmarshalJSON converts this bool or schema object from a JSON structure +func (s *SchemaOrBool) UnmarshalJSON(data []byte) error { + var nw SchemaOrBool + if len(data) >= 4 { + if data[0] == '{' { + var sch Schema + if err := json.Unmarshal(data, &sch); err != nil { + return err + } + nw.Schema = &sch + } + nw.Allows = !(data[0] == 'f' && data[1] == 'a' && data[2] == 'l' && data[3] == 's' && data[4] == 'e') + } + *s = nw + return nil +} + +// SchemaOrStringArray represents a schema or a string array +type SchemaOrStringArray struct { + Schema *Schema + Property []string +} + +// JSONLookup implements an interface to customize json pointer lookup +func (s SchemaOrStringArray) JSONLookup(token string) (interface{}, error) { + r, _, err := jsonpointer.GetForToken(s.Schema, token) + return r, err +} + +// MarshalJSON converts this schema object or array into JSON structure +func (s SchemaOrStringArray) MarshalJSON() ([]byte, error) { + if len(s.Property) > 0 { + return json.Marshal(s.Property) + } + if s.Schema != nil { + return json.Marshal(s.Schema) + } + return []byte("null"), nil +} + +// UnmarshalJSON converts this schema object or array from a JSON structure +func (s *SchemaOrStringArray) UnmarshalJSON(data []byte) error { + var first byte + if len(data) > 1 { + first = data[0] + } + var nw SchemaOrStringArray + if first == '{' { + var sch Schema + if err := json.Unmarshal(data, &sch); err != nil { + return err + } + nw.Schema = &sch + } + if first == '[' { + if err := json.Unmarshal(data, &nw.Property); err != nil { + return err + } + } + *s = nw + return nil +} + +// Definitions contains the models explicitly defined in this spec +// An object to hold data types that can be consumed and produced by operations. +// These data types can be primitives, arrays or models. +// +// For more information: http://goo.gl/8us55a#definitionsObject +type Definitions map[string]Schema + +// SecurityDefinitions a declaration of the security schemes available to be used in the specification. +// This does not enforce the security schemes on the operations and only serves to provide +// the relevant details for each scheme. +// +// For more information: http://goo.gl/8us55a#securityDefinitionsObject +type SecurityDefinitions map[string]*SecurityScheme + +// StringOrArray represents a value that can either be a string +// or an array of strings. Mainly here for serialization purposes +type StringOrArray []string + +// Contains returns true when the value is contained in the slice +func (s StringOrArray) Contains(value string) bool { + for _, str := range s { + if str == value { + return true + } + } + return false +} + +// JSONLookup implements an interface to customize json pointer lookup +func (s SchemaOrArray) JSONLookup(token string) (interface{}, error) { + if _, err := strconv.Atoi(token); err == nil { + r, _, err := jsonpointer.GetForToken(s.Schemas, token) + return r, err + } + r, _, err := jsonpointer.GetForToken(s.Schema, token) + return r, err +} + +// UnmarshalJSON unmarshals this string or array object from a JSON array or JSON string +func (s *StringOrArray) UnmarshalJSON(data []byte) error { + var first byte + if len(data) > 1 { + first = data[0] + } + + if first == '[' { + var parsed []string + if err := json.Unmarshal(data, &parsed); err != nil { + return err + } + *s = StringOrArray(parsed) + return nil + } + + var single interface{} + if err := json.Unmarshal(data, &single); err != nil { + return err + } + if single == nil { + return nil + } + switch v := single.(type) { + case string: + *s = StringOrArray([]string{v}) + return nil + default: + return fmt.Errorf("only string or array is allowed, not %T", single) + } +} + +// MarshalJSON converts this string or array to a JSON array or JSON string +func (s StringOrArray) MarshalJSON() ([]byte, error) { + if len(s) == 1 { + return json.Marshal([]string(s)[0]) + } + return json.Marshal([]string(s)) +} + +// SchemaOrArray represents a value that can either be a Schema +// or an array of Schema. Mainly here for serialization purposes +type SchemaOrArray struct { + Schema *Schema + Schemas []Schema +} + +// Len returns the number of schemas in this property +func (s SchemaOrArray) Len() int { + if s.Schema != nil { + return 1 + } + return len(s.Schemas) +} + +// ContainsType returns true when one of the schemas is of the specified type +func (s *SchemaOrArray) ContainsType(name string) bool { + if s.Schema != nil { + return s.Schema.Type != nil && s.Schema.Type.Contains(name) + } + return false +} + +// MarshalJSON converts this schema object or array into JSON structure +func (s SchemaOrArray) MarshalJSON() ([]byte, error) { + if len(s.Schemas) > 0 { + return json.Marshal(s.Schemas) + } + return json.Marshal(s.Schema) +} + +// UnmarshalJSON converts this schema object or array from a JSON structure +func (s *SchemaOrArray) UnmarshalJSON(data []byte) error { + var nw SchemaOrArray + var first byte + if len(data) > 1 { + first = data[0] + } + if first == '{' { + var sch Schema + if err := json.Unmarshal(data, &sch); err != nil { + return err + } + nw.Schema = &sch + } + if first == '[' { + if err := json.Unmarshal(data, &nw.Schemas); err != nil { + return err + } + } + *s = nw + return nil +} + +// vim:set ft=go noet sts=2 sw=2 ts=2: diff --git a/vendor/github.com/go-openapi/spec/tag.go b/vendor/github.com/go-openapi/spec/tag.go new file mode 100644 index 00000000..faa3d3de --- /dev/null +++ b/vendor/github.com/go-openapi/spec/tag.go @@ -0,0 +1,75 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + + "github.com/go-openapi/jsonpointer" + "github.com/go-openapi/swag" +) + +// TagProps describe a tag entry in the top level tags section of a swagger spec +type TagProps struct { + Description string `json:"description,omitempty"` + Name string `json:"name,omitempty"` + ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"` +} + +// NewTag creates a new tag +func NewTag(name, description string, externalDocs *ExternalDocumentation) Tag { + return Tag{TagProps: TagProps{Description: description, Name: name, ExternalDocs: externalDocs}} +} + +// Tag allows adding meta data to a single tag that is used by the +// [Operation Object](http://goo.gl/8us55a#operationObject). +// It is not mandatory to have a Tag Object per tag used there. +// +// For more information: http://goo.gl/8us55a#tagObject +type Tag struct { + VendorExtensible + TagProps +} + +// JSONLookup implements an interface to customize json pointer lookup +func (t Tag) JSONLookup(token string) (interface{}, error) { + if ex, ok := t.Extensions[token]; ok { + return &ex, nil + } + + r, _, err := jsonpointer.GetForToken(t.TagProps, token) + return r, err +} + +// MarshalJSON marshal this to JSON +func (t Tag) MarshalJSON() ([]byte, error) { + b1, err := json.Marshal(t.TagProps) + if err != nil { + return nil, err + } + b2, err := json.Marshal(t.VendorExtensible) + if err != nil { + return nil, err + } + return swag.ConcatJSON(b1, b2), nil +} + +// UnmarshalJSON marshal this from JSON +func (t *Tag) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &t.TagProps); err != nil { + return err + } + return json.Unmarshal(data, &t.VendorExtensible) +} diff --git a/vendor/github.com/go-openapi/spec/validations.go b/vendor/github.com/go-openapi/spec/validations.go new file mode 100644 index 00000000..6360a8ea --- /dev/null +++ b/vendor/github.com/go-openapi/spec/validations.go @@ -0,0 +1,215 @@ +package spec + +// CommonValidations describe common JSON-schema validations +type CommonValidations struct { + Maximum *float64 `json:"maximum,omitempty"` + ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"` + Minimum *float64 `json:"minimum,omitempty"` + ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"` + MaxLength *int64 `json:"maxLength,omitempty"` + MinLength *int64 `json:"minLength,omitempty"` + Pattern string `json:"pattern,omitempty"` + MaxItems *int64 `json:"maxItems,omitempty"` + MinItems *int64 `json:"minItems,omitempty"` + UniqueItems bool `json:"uniqueItems,omitempty"` + MultipleOf *float64 `json:"multipleOf,omitempty"` + Enum []interface{} `json:"enum,omitempty"` +} + +// SetValidations defines all validations for a simple schema. +// +// NOTE: the input is the larger set of validations available for schemas. +// For simple schemas, MinProperties and MaxProperties are ignored. +func (v *CommonValidations) SetValidations(val SchemaValidations) { + v.Maximum = val.Maximum + v.ExclusiveMaximum = val.ExclusiveMaximum + v.Minimum = val.Minimum + v.ExclusiveMinimum = val.ExclusiveMinimum + v.MaxLength = val.MaxLength + v.MinLength = val.MinLength + v.Pattern = val.Pattern + v.MaxItems = val.MaxItems + v.MinItems = val.MinItems + v.UniqueItems = val.UniqueItems + v.MultipleOf = val.MultipleOf + v.Enum = val.Enum +} + +type clearedValidation struct { + Validation string + Value interface{} +} + +type clearedValidations []clearedValidation + +func (c clearedValidations) apply(cbs []func(string, interface{})) { + for _, cb := range cbs { + for _, cleared := range c { + cb(cleared.Validation, cleared.Value) + } + } +} + +// ClearNumberValidations clears all number validations. +// +// Some callbacks may be set by the caller to capture changed values. +func (v *CommonValidations) ClearNumberValidations(cbs ...func(string, interface{})) { + done := make(clearedValidations, 0, 5) + defer func() { + done.apply(cbs) + }() + + if v.Minimum != nil { + done = append(done, clearedValidation{Validation: "minimum", Value: v.Minimum}) + v.Minimum = nil + } + if v.Maximum != nil { + done = append(done, clearedValidation{Validation: "maximum", Value: v.Maximum}) + v.Maximum = nil + } + if v.ExclusiveMaximum { + done = append(done, clearedValidation{Validation: "exclusiveMaximum", Value: v.ExclusiveMaximum}) + v.ExclusiveMaximum = false + } + if v.ExclusiveMinimum { + done = append(done, clearedValidation{Validation: "exclusiveMinimum", Value: v.ExclusiveMinimum}) + v.ExclusiveMinimum = false + } + if v.MultipleOf != nil { + done = append(done, clearedValidation{Validation: "multipleOf", Value: v.MultipleOf}) + v.MultipleOf = nil + } +} + +// ClearStringValidations clears all string validations. +// +// Some callbacks may be set by the caller to capture changed values. +func (v *CommonValidations) ClearStringValidations(cbs ...func(string, interface{})) { + done := make(clearedValidations, 0, 3) + defer func() { + done.apply(cbs) + }() + + if v.Pattern != "" { + done = append(done, clearedValidation{Validation: "pattern", Value: v.Pattern}) + v.Pattern = "" + } + if v.MinLength != nil { + done = append(done, clearedValidation{Validation: "minLength", Value: v.MinLength}) + v.MinLength = nil + } + if v.MaxLength != nil { + done = append(done, clearedValidation{Validation: "maxLength", Value: v.MaxLength}) + v.MaxLength = nil + } +} + +// ClearArrayValidations clears all array validations. +// +// Some callbacks may be set by the caller to capture changed values. +func (v *CommonValidations) ClearArrayValidations(cbs ...func(string, interface{})) { + done := make(clearedValidations, 0, 3) + defer func() { + done.apply(cbs) + }() + + if v.MaxItems != nil { + done = append(done, clearedValidation{Validation: "maxItems", Value: v.MaxItems}) + v.MaxItems = nil + } + if v.MinItems != nil { + done = append(done, clearedValidation{Validation: "minItems", Value: v.MinItems}) + v.MinItems = nil + } + if v.UniqueItems { + done = append(done, clearedValidation{Validation: "uniqueItems", Value: v.UniqueItems}) + v.UniqueItems = false + } +} + +// Validations returns a clone of the validations for a simple schema. +// +// NOTE: in the context of simple schema objects, MinProperties, MaxProperties +// and PatternProperties remain unset. +func (v CommonValidations) Validations() SchemaValidations { + return SchemaValidations{ + CommonValidations: v, + } +} + +// HasNumberValidations indicates if the validations are for numbers or integers +func (v CommonValidations) HasNumberValidations() bool { + return v.Maximum != nil || v.Minimum != nil || v.MultipleOf != nil +} + +// HasStringValidations indicates if the validations are for strings +func (v CommonValidations) HasStringValidations() bool { + return v.MaxLength != nil || v.MinLength != nil || v.Pattern != "" +} + +// HasArrayValidations indicates if the validations are for arrays +func (v CommonValidations) HasArrayValidations() bool { + return v.MaxItems != nil || v.MinItems != nil || v.UniqueItems +} + +// HasEnum indicates if the validation includes some enum constraint +func (v CommonValidations) HasEnum() bool { + return len(v.Enum) > 0 +} + +// SchemaValidations describes the validation properties of a schema +// +// NOTE: at this moment, this is not embedded in SchemaProps because this would induce a breaking change +// in the exported members: all initializers using litterals would fail. +type SchemaValidations struct { + CommonValidations + + PatternProperties SchemaProperties `json:"patternProperties,omitempty"` + MaxProperties *int64 `json:"maxProperties,omitempty"` + MinProperties *int64 `json:"minProperties,omitempty"` +} + +// HasObjectValidations indicates if the validations are for objects +func (v SchemaValidations) HasObjectValidations() bool { + return v.MaxProperties != nil || v.MinProperties != nil || v.PatternProperties != nil +} + +// SetValidations for schema validations +func (v *SchemaValidations) SetValidations(val SchemaValidations) { + v.CommonValidations.SetValidations(val) + v.PatternProperties = val.PatternProperties + v.MaxProperties = val.MaxProperties + v.MinProperties = val.MinProperties +} + +// Validations for a schema +func (v SchemaValidations) Validations() SchemaValidations { + val := v.CommonValidations.Validations() + val.PatternProperties = v.PatternProperties + val.MinProperties = v.MinProperties + val.MaxProperties = v.MaxProperties + return val +} + +// ClearObjectValidations returns a clone of the validations with all object validations cleared. +// +// Some callbacks may be set by the caller to capture changed values. +func (v *SchemaValidations) ClearObjectValidations(cbs ...func(string, interface{})) { + done := make(clearedValidations, 0, 3) + defer func() { + done.apply(cbs) + }() + + if v.MaxProperties != nil { + done = append(done, clearedValidation{Validation: "maxProperties", Value: v.MaxProperties}) + v.MaxProperties = nil + } + if v.MinProperties != nil { + done = append(done, clearedValidation{Validation: "minProperties", Value: v.MinProperties}) + v.MinProperties = nil + } + if v.PatternProperties != nil { + done = append(done, clearedValidation{Validation: "patternProperties", Value: v.PatternProperties}) + v.PatternProperties = nil + } +} diff --git a/vendor/github.com/go-openapi/spec/xml_object.go b/vendor/github.com/go-openapi/spec/xml_object.go new file mode 100644 index 00000000..945a4670 --- /dev/null +++ b/vendor/github.com/go-openapi/spec/xml_object.go @@ -0,0 +1,68 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +// XMLObject a metadata object that allows for more fine-tuned XML model definitions. +// +// For more information: http://goo.gl/8us55a#xmlObject +type XMLObject struct { + Name string `json:"name,omitempty"` + Namespace string `json:"namespace,omitempty"` + Prefix string `json:"prefix,omitempty"` + Attribute bool `json:"attribute,omitempty"` + Wrapped bool `json:"wrapped,omitempty"` +} + +// WithName sets the xml name for the object +func (x *XMLObject) WithName(name string) *XMLObject { + x.Name = name + return x +} + +// WithNamespace sets the xml namespace for the object +func (x *XMLObject) WithNamespace(namespace string) *XMLObject { + x.Namespace = namespace + return x +} + +// WithPrefix sets the xml prefix for the object +func (x *XMLObject) WithPrefix(prefix string) *XMLObject { + x.Prefix = prefix + return x +} + +// AsAttribute flags this object as xml attribute +func (x *XMLObject) AsAttribute() *XMLObject { + x.Attribute = true + return x +} + +// AsElement flags this object as an xml node +func (x *XMLObject) AsElement() *XMLObject { + x.Attribute = false + return x +} + +// AsWrapped flags this object as wrapped, this is mostly useful for array types +func (x *XMLObject) AsWrapped() *XMLObject { + x.Wrapped = true + return x +} + +// AsUnwrapped flags this object as an xml node +func (x *XMLObject) AsUnwrapped() *XMLObject { + x.Wrapped = false + return x +} diff --git a/vendor/github.com/go-openapi/swag/.editorconfig b/vendor/github.com/go-openapi/swag/.editorconfig new file mode 100644 index 00000000..3152da69 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/.editorconfig @@ -0,0 +1,26 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +# Set default charset +[*.{js,py,go,scala,rb,java,html,css,less,sass,md}] +charset = utf-8 + +# Tab indentation (no size specified) +[*.go] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 diff --git a/vendor/github.com/go-openapi/swag/.gitattributes b/vendor/github.com/go-openapi/swag/.gitattributes new file mode 100644 index 00000000..49ad5276 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/.gitattributes @@ -0,0 +1,2 @@ +# gofmt always uses LF, whereas Git uses CRLF on Windows. +*.go text eol=lf diff --git a/vendor/github.com/go-openapi/swag/.gitignore b/vendor/github.com/go-openapi/swag/.gitignore new file mode 100644 index 00000000..c4b1b64f --- /dev/null +++ b/vendor/github.com/go-openapi/swag/.gitignore @@ -0,0 +1,5 @@ +secrets.yml +vendor +Godeps +.idea +*.out diff --git a/vendor/github.com/go-openapi/swag/.golangci.yml b/vendor/github.com/go-openapi/swag/.golangci.yml new file mode 100644 index 00000000..80e2be00 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/.golangci.yml @@ -0,0 +1,60 @@ +linters-settings: + govet: + check-shadowing: true + golint: + min-confidence: 0 + gocyclo: + min-complexity: 45 + maligned: + suggest-new: true + dupl: + threshold: 200 + goconst: + min-len: 3 + min-occurrences: 3 + +linters: + enable-all: true + disable: + - maligned + - lll + - gochecknoinits + - gochecknoglobals + - funlen + - godox + - gocognit + - whitespace + - wsl + - wrapcheck + - testpackage + - nlreturn + - gomnd + - exhaustivestruct + - goerr113 + - errorlint + - nestif + - godot + - gofumpt + - paralleltest + - tparallel + - thelper + - ifshort + - exhaustruct + - varnamelen + - gci + - depguard + - errchkjson + - inamedparam + - nonamedreturns + - musttag + - ireturn + - forcetypeassert + - cyclop + # deprecated linters + - deadcode + - interfacer + - scopelint + - varcheck + - structcheck + - golint + - nosnakecase diff --git a/vendor/github.com/go-openapi/swag/BENCHMARK.md b/vendor/github.com/go-openapi/swag/BENCHMARK.md new file mode 100644 index 00000000..e7f28ed6 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/BENCHMARK.md @@ -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 +``` diff --git a/vendor/github.com/go-openapi/swag/CODE_OF_CONDUCT.md b/vendor/github.com/go-openapi/swag/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..9322b065 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at ivan+abuse@flanders.co.nz. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/vendor/github.com/go-openapi/swag/LICENSE b/vendor/github.com/go-openapi/swag/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/go-openapi/swag/README.md b/vendor/github.com/go-openapi/swag/README.md new file mode 100644 index 00000000..a7292229 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/README.md @@ -0,0 +1,23 @@ +# 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) +[![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. + +You may also use it standalone for your projects. + +* convert between value and pointers for builtin types +* convert from string to builtin types (wraps strconv) +* fast json concatenation +* search in path +* load from file or http +* name mangling + + +This repo has only few dependencies outside of the standard library: + +* YAML utilities depend on `gopkg.in/yaml.v3` +* `github.com/mailru/easyjson v0.7.7` diff --git a/vendor/github.com/go-openapi/swag/convert.go b/vendor/github.com/go-openapi/swag/convert.go new file mode 100644 index 00000000..fc085aeb --- /dev/null +++ b/vendor/github.com/go-openapi/swag/convert.go @@ -0,0 +1,208 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swag + +import ( + "math" + "strconv" + "strings" +) + +// same as ECMA Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER +const ( + maxJSONFloat = float64(1<<53 - 1) // 9007199254740991.0 2^53 - 1 + minJSONFloat = -float64(1<<53 - 1) //-9007199254740991.0 -2^53 - 1 + epsilon float64 = 1e-9 +) + +// IsFloat64AJSONInteger allow for integers [-2^53, 2^53-1] inclusive +func IsFloat64AJSONInteger(f float64) bool { + if math.IsNaN(f) || math.IsInf(f, 0) || f < minJSONFloat || f > maxJSONFloat { + return false + } + fa := math.Abs(f) + g := float64(uint64(f)) + ga := math.Abs(g) + + diff := math.Abs(f - g) + + // more info: https://floating-point-gui.de/errors/comparison/#look-out-for-edge-cases + switch { + case f == g: // best case + return true + case f == float64(int64(f)) || f == float64(uint64(f)): // optimistic case + return true + case f == 0 || g == 0 || diff < math.SmallestNonzeroFloat64: // very close to 0 values + return diff < (epsilon * math.SmallestNonzeroFloat64) + } + // check the relative error + return diff/math.Min(fa+ga, math.MaxFloat64) < epsilon +} + +var evaluatesAsTrue map[string]struct{} + +func init() { + evaluatesAsTrue = map[string]struct{}{ + "true": {}, + "1": {}, + "yes": {}, + "ok": {}, + "y": {}, + "on": {}, + "selected": {}, + "checked": {}, + "t": {}, + "enabled": {}, + } +} + +// ConvertBool turn a string into a boolean +func ConvertBool(str string) (bool, error) { + _, ok := evaluatesAsTrue[strings.ToLower(str)] + return ok, nil +} + +// ConvertFloat32 turn a string into a float32 +func ConvertFloat32(str string) (float32, error) { + f, err := strconv.ParseFloat(str, 32) + if err != nil { + return 0, err + } + return float32(f), nil +} + +// ConvertFloat64 turn a string into a float64 +func ConvertFloat64(str string) (float64, error) { + return strconv.ParseFloat(str, 64) +} + +// ConvertInt8 turn a string into an int8 +func ConvertInt8(str string) (int8, error) { + i, err := strconv.ParseInt(str, 10, 8) + if err != nil { + return 0, err + } + return int8(i), nil +} + +// ConvertInt16 turn a string into an int16 +func ConvertInt16(str string) (int16, error) { + i, err := strconv.ParseInt(str, 10, 16) + if err != nil { + return 0, err + } + return int16(i), nil +} + +// ConvertInt32 turn a string into an int32 +func ConvertInt32(str string) (int32, error) { + i, err := strconv.ParseInt(str, 10, 32) + if err != nil { + return 0, err + } + return int32(i), nil +} + +// ConvertInt64 turn a string into an int64 +func ConvertInt64(str string) (int64, error) { + return strconv.ParseInt(str, 10, 64) +} + +// ConvertUint8 turn a string into an uint8 +func ConvertUint8(str string) (uint8, error) { + i, err := strconv.ParseUint(str, 10, 8) + if err != nil { + return 0, err + } + return uint8(i), nil +} + +// ConvertUint16 turn a string into an uint16 +func ConvertUint16(str string) (uint16, error) { + i, err := strconv.ParseUint(str, 10, 16) + if err != nil { + return 0, err + } + return uint16(i), nil +} + +// ConvertUint32 turn a string into an uint32 +func ConvertUint32(str string) (uint32, error) { + i, err := strconv.ParseUint(str, 10, 32) + if err != nil { + return 0, err + } + return uint32(i), nil +} + +// ConvertUint64 turn a string into an uint64 +func ConvertUint64(str string) (uint64, error) { + return strconv.ParseUint(str, 10, 64) +} + +// FormatBool turns a boolean into a string +func FormatBool(value bool) string { + return strconv.FormatBool(value) +} + +// FormatFloat32 turns a float32 into a string +func FormatFloat32(value float32) string { + return strconv.FormatFloat(float64(value), 'f', -1, 32) +} + +// FormatFloat64 turns a float64 into a string +func FormatFloat64(value float64) string { + return strconv.FormatFloat(value, 'f', -1, 64) +} + +// FormatInt8 turns an int8 into a string +func FormatInt8(value int8) string { + return strconv.FormatInt(int64(value), 10) +} + +// FormatInt16 turns an int16 into a string +func FormatInt16(value int16) string { + return strconv.FormatInt(int64(value), 10) +} + +// FormatInt32 turns an int32 into a string +func FormatInt32(value int32) string { + return strconv.Itoa(int(value)) +} + +// FormatInt64 turns an int64 into a string +func FormatInt64(value int64) string { + return strconv.FormatInt(value, 10) +} + +// FormatUint8 turns an uint8 into a string +func FormatUint8(value uint8) string { + return strconv.FormatUint(uint64(value), 10) +} + +// FormatUint16 turns an uint16 into a string +func FormatUint16(value uint16) string { + return strconv.FormatUint(uint64(value), 10) +} + +// FormatUint32 turns an uint32 into a string +func FormatUint32(value uint32) string { + return strconv.FormatUint(uint64(value), 10) +} + +// FormatUint64 turns an uint64 into a string +func FormatUint64(value uint64) string { + return strconv.FormatUint(value, 10) +} diff --git a/vendor/github.com/go-openapi/swag/convert_types.go b/vendor/github.com/go-openapi/swag/convert_types.go new file mode 100644 index 00000000..c49cc473 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/convert_types.go @@ -0,0 +1,730 @@ +package swag + +import "time" + +// This file was taken from the aws go sdk + +// String returns a pointer to of the string value passed in. +func String(v string) *string { + return &v +} + +// StringValue returns the value of the string pointer passed in or +// "" if the pointer is nil. +func StringValue(v *string) string { + if v != nil { + return *v + } + return "" +} + +// StringSlice converts a slice of string values into a slice of +// string pointers +func StringSlice(src []string) []*string { + dst := make([]*string, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// StringValueSlice converts a slice of string pointers into a slice of +// string values +func StringValueSlice(src []*string) []string { + dst := make([]string, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// StringMap converts a string map of string values into a string +// map of string pointers +func StringMap(src map[string]string) map[string]*string { + dst := make(map[string]*string) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// StringValueMap converts a string map of string pointers into a string +// map of string values +func StringValueMap(src map[string]*string) map[string]string { + dst := make(map[string]string) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Bool returns a pointer to of the bool value passed in. +func Bool(v bool) *bool { + return &v +} + +// BoolValue returns the value of the bool pointer passed in or +// false if the pointer is nil. +func BoolValue(v *bool) bool { + if v != nil { + return *v + } + return false +} + +// BoolSlice converts a slice of bool values into a slice of +// bool pointers +func BoolSlice(src []bool) []*bool { + dst := make([]*bool, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// BoolValueSlice converts a slice of bool pointers into a slice of +// bool values +func BoolValueSlice(src []*bool) []bool { + dst := make([]bool, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// BoolMap converts a string map of bool values into a string +// map of bool pointers +func BoolMap(src map[string]bool) map[string]*bool { + dst := make(map[string]*bool) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// BoolValueMap converts a string map of bool pointers into a string +// map of bool values +func BoolValueMap(src map[string]*bool) map[string]bool { + dst := make(map[string]bool) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Int returns a pointer to of the int value passed in. +func Int(v int) *int { + return &v +} + +// IntValue returns the value of the int pointer passed in or +// 0 if the pointer is nil. +func IntValue(v *int) int { + if v != nil { + return *v + } + return 0 +} + +// IntSlice converts a slice of int values into a slice of +// int pointers +func IntSlice(src []int) []*int { + dst := make([]*int, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// IntValueSlice converts a slice of int pointers into a slice of +// int values +func IntValueSlice(src []*int) []int { + dst := make([]int, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// IntMap converts a string map of int values into a string +// map of int pointers +func IntMap(src map[string]int) map[string]*int { + dst := make(map[string]*int) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// IntValueMap converts a string map of int pointers into a string +// map of int values +func IntValueMap(src map[string]*int) map[string]int { + dst := make(map[string]int) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Int32 returns a pointer to of the int32 value passed in. +func Int32(v int32) *int32 { + return &v +} + +// Int32Value returns the value of the int32 pointer passed in or +// 0 if the pointer is nil. +func Int32Value(v *int32) int32 { + if v != nil { + return *v + } + return 0 +} + +// Int32Slice converts a slice of int32 values into a slice of +// int32 pointers +func Int32Slice(src []int32) []*int32 { + dst := make([]*int32, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Int32ValueSlice converts a slice of int32 pointers into a slice of +// int32 values +func Int32ValueSlice(src []*int32) []int32 { + dst := make([]int32, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Int32Map converts a string map of int32 values into a string +// map of int32 pointers +func Int32Map(src map[string]int32) map[string]*int32 { + dst := make(map[string]*int32) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Int32ValueMap converts a string map of int32 pointers into a string +// map of int32 values +func Int32ValueMap(src map[string]*int32) map[string]int32 { + dst := make(map[string]int32) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Int64 returns a pointer to of the int64 value passed in. +func Int64(v int64) *int64 { + return &v +} + +// Int64Value returns the value of the int64 pointer passed in or +// 0 if the pointer is nil. +func Int64Value(v *int64) int64 { + if v != nil { + return *v + } + return 0 +} + +// Int64Slice converts a slice of int64 values into a slice of +// int64 pointers +func Int64Slice(src []int64) []*int64 { + dst := make([]*int64, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Int64ValueSlice converts a slice of int64 pointers into a slice of +// int64 values +func Int64ValueSlice(src []*int64) []int64 { + dst := make([]int64, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Int64Map converts a string map of int64 values into a string +// map of int64 pointers +func Int64Map(src map[string]int64) map[string]*int64 { + dst := make(map[string]*int64) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Int64ValueMap converts a string map of int64 pointers into a string +// map of int64 values +func Int64ValueMap(src map[string]*int64) map[string]int64 { + dst := make(map[string]int64) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Uint16 returns a pointer to of the uint16 value passed in. +func Uint16(v uint16) *uint16 { + return &v +} + +// Uint16Value returns the value of the uint16 pointer passed in or +// 0 if the pointer is nil. +func Uint16Value(v *uint16) uint16 { + if v != nil { + return *v + } + + return 0 +} + +// Uint16Slice converts a slice of uint16 values into a slice of +// uint16 pointers +func Uint16Slice(src []uint16) []*uint16 { + dst := make([]*uint16, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + + return dst +} + +// Uint16ValueSlice converts a slice of uint16 pointers into a slice of +// uint16 values +func Uint16ValueSlice(src []*uint16) []uint16 { + dst := make([]uint16, len(src)) + + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + + return dst +} + +// Uint16Map converts a string map of uint16 values into a string +// map of uint16 pointers +func Uint16Map(src map[string]uint16) map[string]*uint16 { + dst := make(map[string]*uint16) + + for k, val := range src { + v := val + dst[k] = &v + } + + return dst +} + +// Uint16ValueMap converts a string map of uint16 pointers into a string +// map of uint16 values +func Uint16ValueMap(src map[string]*uint16) map[string]uint16 { + dst := make(map[string]uint16) + + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + + return dst +} + +// Uint returns a pointer to of the uint value passed in. +func Uint(v uint) *uint { + return &v +} + +// UintValue returns the value of the uint pointer passed in or +// 0 if the pointer is nil. +func UintValue(v *uint) uint { + if v != nil { + return *v + } + return 0 +} + +// UintSlice converts a slice of uint values into a slice of +// uint pointers +func UintSlice(src []uint) []*uint { + dst := make([]*uint, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// UintValueSlice converts a slice of uint pointers into a slice of +// uint values +func UintValueSlice(src []*uint) []uint { + dst := make([]uint, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// UintMap converts a string map of uint values into a string +// map of uint pointers +func UintMap(src map[string]uint) map[string]*uint { + dst := make(map[string]*uint) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// UintValueMap converts a string map of uint pointers into a string +// map of uint values +func UintValueMap(src map[string]*uint) map[string]uint { + dst := make(map[string]uint) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Uint32 returns a pointer to of the uint32 value passed in. +func Uint32(v uint32) *uint32 { + return &v +} + +// Uint32Value returns the value of the uint32 pointer passed in or +// 0 if the pointer is nil. +func Uint32Value(v *uint32) uint32 { + if v != nil { + return *v + } + return 0 +} + +// Uint32Slice converts a slice of uint32 values into a slice of +// uint32 pointers +func Uint32Slice(src []uint32) []*uint32 { + dst := make([]*uint32, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Uint32ValueSlice converts a slice of uint32 pointers into a slice of +// uint32 values +func Uint32ValueSlice(src []*uint32) []uint32 { + dst := make([]uint32, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Uint32Map converts a string map of uint32 values into a string +// map of uint32 pointers +func Uint32Map(src map[string]uint32) map[string]*uint32 { + dst := make(map[string]*uint32) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Uint32ValueMap converts a string map of uint32 pointers into a string +// map of uint32 values +func Uint32ValueMap(src map[string]*uint32) map[string]uint32 { + dst := make(map[string]uint32) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Uint64 returns a pointer to of the uint64 value passed in. +func Uint64(v uint64) *uint64 { + return &v +} + +// Uint64Value returns the value of the uint64 pointer passed in or +// 0 if the pointer is nil. +func Uint64Value(v *uint64) uint64 { + if v != nil { + return *v + } + return 0 +} + +// Uint64Slice converts a slice of uint64 values into a slice of +// uint64 pointers +func Uint64Slice(src []uint64) []*uint64 { + dst := make([]*uint64, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Uint64ValueSlice converts a slice of uint64 pointers into a slice of +// uint64 values +func Uint64ValueSlice(src []*uint64) []uint64 { + dst := make([]uint64, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Uint64Map converts a string map of uint64 values into a string +// map of uint64 pointers +func Uint64Map(src map[string]uint64) map[string]*uint64 { + dst := make(map[string]*uint64) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Uint64ValueMap converts a string map of uint64 pointers into a string +// map of uint64 values +func Uint64ValueMap(src map[string]*uint64) map[string]uint64 { + dst := make(map[string]uint64) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Float32 returns a pointer to of the float32 value passed in. +func Float32(v float32) *float32 { + return &v +} + +// Float32Value returns the value of the float32 pointer passed in or +// 0 if the pointer is nil. +func Float32Value(v *float32) float32 { + if v != nil { + return *v + } + + return 0 +} + +// Float32Slice converts a slice of float32 values into a slice of +// float32 pointers +func Float32Slice(src []float32) []*float32 { + dst := make([]*float32, len(src)) + + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + + return dst +} + +// Float32ValueSlice converts a slice of float32 pointers into a slice of +// float32 values +func Float32ValueSlice(src []*float32) []float32 { + dst := make([]float32, len(src)) + + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + + return dst +} + +// Float32Map converts a string map of float32 values into a string +// map of float32 pointers +func Float32Map(src map[string]float32) map[string]*float32 { + dst := make(map[string]*float32) + + for k, val := range src { + v := val + dst[k] = &v + } + + return dst +} + +// Float32ValueMap converts a string map of float32 pointers into a string +// map of float32 values +func Float32ValueMap(src map[string]*float32) map[string]float32 { + dst := make(map[string]float32) + + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + + return dst +} + +// Float64 returns a pointer to of the float64 value passed in. +func Float64(v float64) *float64 { + return &v +} + +// Float64Value returns the value of the float64 pointer passed in or +// 0 if the pointer is nil. +func Float64Value(v *float64) float64 { + if v != nil { + return *v + } + return 0 +} + +// Float64Slice converts a slice of float64 values into a slice of +// float64 pointers +func Float64Slice(src []float64) []*float64 { + dst := make([]*float64, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Float64ValueSlice converts a slice of float64 pointers into a slice of +// float64 values +func Float64ValueSlice(src []*float64) []float64 { + dst := make([]float64, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Float64Map converts a string map of float64 values into a string +// map of float64 pointers +func Float64Map(src map[string]float64) map[string]*float64 { + dst := make(map[string]*float64) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Float64ValueMap converts a string map of float64 pointers into a string +// map of float64 values +func Float64ValueMap(src map[string]*float64) map[string]float64 { + dst := make(map[string]float64) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Time returns a pointer to of the time.Time value passed in. +func Time(v time.Time) *time.Time { + return &v +} + +// TimeValue returns the value of the time.Time pointer passed in or +// time.Time{} if the pointer is nil. +func TimeValue(v *time.Time) time.Time { + if v != nil { + return *v + } + return time.Time{} +} + +// TimeSlice converts a slice of time.Time values into a slice of +// time.Time pointers +func TimeSlice(src []time.Time) []*time.Time { + dst := make([]*time.Time, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// TimeValueSlice converts a slice of time.Time pointers into a slice of +// time.Time values +func TimeValueSlice(src []*time.Time) []time.Time { + dst := make([]time.Time, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// TimeMap converts a string map of time.Time values into a string +// map of time.Time pointers +func TimeMap(src map[string]time.Time) map[string]*time.Time { + dst := make(map[string]*time.Time) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// TimeValueMap converts a string map of time.Time pointers into a string +// map of time.Time values +func TimeValueMap(src map[string]*time.Time) map[string]time.Time { + dst := make(map[string]time.Time) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} diff --git a/vendor/github.com/go-openapi/swag/doc.go b/vendor/github.com/go-openapi/swag/doc.go new file mode 100644 index 00000000..55094cb7 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/doc.go @@ -0,0 +1,31 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/* +Package swag contains a bunch of helper functions for go-openapi and go-swagger projects. + +You may also use it standalone for your projects. + + - convert between value and pointers for builtin types + - convert from string to builtin types (wraps strconv) + - fast json concatenation + - search in path + - load from file or http + - name mangling + +This repo has only few dependencies outside of the standard library: + + - YAML utilities depend on gopkg.in/yaml.v2 +*/ +package swag diff --git a/vendor/github.com/go-openapi/swag/file.go b/vendor/github.com/go-openapi/swag/file.go new file mode 100644 index 00000000..16accc55 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/file.go @@ -0,0 +1,33 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swag + +import "mime/multipart" + +// File represents an uploaded file. +type File struct { + Data multipart.File + Header *multipart.FileHeader +} + +// Read bytes from the file +func (f *File) Read(p []byte) (n int, err error) { + return f.Data.Read(p) +} + +// Close the file +func (f *File) Close() error { + return f.Data.Close() +} diff --git a/vendor/github.com/go-openapi/swag/initialism_index.go b/vendor/github.com/go-openapi/swag/initialism_index.go new file mode 100644 index 00000000..20a359bb --- /dev/null +++ b/vendor/github.com/go-openapi/swag/initialism_index.go @@ -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 +} diff --git a/vendor/github.com/go-openapi/swag/json.go b/vendor/github.com/go-openapi/swag/json.go new file mode 100644 index 00000000..7e9902ca --- /dev/null +++ b/vendor/github.com/go-openapi/swag/json.go @@ -0,0 +1,312 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swag + +import ( + "bytes" + "encoding/json" + "log" + "reflect" + "strings" + "sync" + + "github.com/mailru/easyjson/jlexer" + "github.com/mailru/easyjson/jwriter" +) + +// nullJSON represents a JSON object with null type +var nullJSON = []byte("null") + +// DefaultJSONNameProvider the default cache for types +var DefaultJSONNameProvider = NewNameProvider() + +const comma = byte(',') + +var closers map[byte]byte + +func init() { + closers = map[byte]byte{ + '{': '}', + '[': ']', + } +} + +type ejMarshaler interface { + MarshalEasyJSON(w *jwriter.Writer) +} + +type ejUnmarshaler interface { + UnmarshalEasyJSON(w *jlexer.Lexer) +} + +// WriteJSON writes json data, prefers finding an appropriate interface to short-circuit the marshaler +// so it takes the fastest option available. +func WriteJSON(data interface{}) ([]byte, error) { + if d, ok := data.(ejMarshaler); ok { + jw := new(jwriter.Writer) + d.MarshalEasyJSON(jw) + return jw.BuildBytes() + } + if d, ok := data.(json.Marshaler); ok { + return d.MarshalJSON() + } + return json.Marshal(data) +} + +// ReadJSON reads json data, prefers finding an appropriate interface to short-circuit the unmarshaler +// so it takes the fastest option available +func ReadJSON(data []byte, value interface{}) error { + trimmedData := bytes.Trim(data, "\x00") + if d, ok := value.(ejUnmarshaler); ok { + jl := &jlexer.Lexer{Data: trimmedData} + d.UnmarshalEasyJSON(jl) + return jl.Error() + } + if d, ok := value.(json.Unmarshaler); ok { + return d.UnmarshalJSON(trimmedData) + } + return json.Unmarshal(trimmedData, value) +} + +// DynamicJSONToStruct converts an untyped json structure into a struct +func DynamicJSONToStruct(data interface{}, target interface{}) error { + // TODO: convert straight to a json typed map (mergo + iterate?) + b, err := WriteJSON(data) + if err != nil { + return err + } + return ReadJSON(b, target) +} + +// ConcatJSON concatenates multiple json objects efficiently +func ConcatJSON(blobs ...[]byte) []byte { + if len(blobs) == 0 { + return nil + } + + last := len(blobs) - 1 + for blobs[last] == nil || bytes.Equal(blobs[last], nullJSON) { + // strips trailing null objects + last-- + if last < 0 { + // there was nothing but "null"s or nil... + return nil + } + } + if last == 0 { + return blobs[0] + } + + var opening, closing byte + var idx, a int + buf := bytes.NewBuffer(nil) + + for i, b := range blobs[:last+1] { + if b == nil || bytes.Equal(b, nullJSON) { + // a null object is in the list: skip it + continue + } + if len(b) > 0 && opening == 0 { // is this an array or an object? + opening, closing = b[0], closers[b[0]] + } + + if opening != '{' && opening != '[' { + continue // don't know how to concatenate non container objects + } + + if len(b) < 3 { // yep empty but also the last one, so closing this thing + if i == last && a > 0 { + if err := buf.WriteByte(closing); err != nil { + log.Println(err) + } + } + continue + } + + idx = 0 + if a > 0 { // we need to join with a comma for everything beyond the first non-empty item + if err := buf.WriteByte(comma); err != nil { + log.Println(err) + } + idx = 1 // this is not the first or the last so we want to drop the leading bracket + } + + if i != last { // not the last one, strip brackets + if _, err := buf.Write(b[idx : len(b)-1]); err != nil { + log.Println(err) + } + } else { // last one, strip only the leading bracket + if _, err := buf.Write(b[idx:]); err != nil { + log.Println(err) + } + } + a++ + } + // somehow it ended up being empty, so provide a default value + if buf.Len() == 0 { + if err := buf.WriteByte(opening); err != nil { + log.Println(err) + } + if err := buf.WriteByte(closing); err != nil { + log.Println(err) + } + } + return buf.Bytes() +} + +// ToDynamicJSON turns an object into a properly JSON typed structure +func ToDynamicJSON(data interface{}) interface{} { + // TODO: convert straight to a json typed map (mergo + iterate?) + b, err := json.Marshal(data) + if err != nil { + log.Println(err) + } + var res interface{} + if err := json.Unmarshal(b, &res); err != nil { + log.Println(err) + } + return res +} + +// FromDynamicJSON turns an object into a properly JSON typed structure +func FromDynamicJSON(data, target interface{}) error { + b, err := json.Marshal(data) + if err != nil { + log.Println(err) + } + return json.Unmarshal(b, target) +} + +// NameProvider represents an object capable of translating from go property names +// to json property names +// This type is thread-safe. +type NameProvider struct { + lock *sync.Mutex + index map[reflect.Type]nameIndex +} + +type nameIndex struct { + jsonNames map[string]string + goNames map[string]string +} + +// NewNameProvider creates a new name provider +func NewNameProvider() *NameProvider { + return &NameProvider{ + lock: &sync.Mutex{}, + index: make(map[reflect.Type]nameIndex), + } +} + +func buildnameIndex(tpe reflect.Type, idx, reverseIdx map[string]string) { + for i := 0; i < tpe.NumField(); i++ { + targetDes := tpe.Field(i) + + if targetDes.PkgPath != "" { // unexported + continue + } + + if targetDes.Anonymous { // walk embedded structures tree down first + buildnameIndex(targetDes.Type, idx, reverseIdx) + continue + } + + if tag := targetDes.Tag.Get("json"); tag != "" { + + parts := strings.Split(tag, ",") + if len(parts) == 0 { + continue + } + + nm := parts[0] + if nm == "-" { + continue + } + if nm == "" { // empty string means we want to use the Go name + nm = targetDes.Name + } + + idx[nm] = targetDes.Name + reverseIdx[targetDes.Name] = nm + } + } +} + +func newNameIndex(tpe reflect.Type) nameIndex { + var idx = make(map[string]string, tpe.NumField()) + var reverseIdx = make(map[string]string, tpe.NumField()) + + buildnameIndex(tpe, idx, reverseIdx) + return nameIndex{jsonNames: idx, goNames: reverseIdx} +} + +// GetJSONNames gets all the json property names for a type +func (n *NameProvider) GetJSONNames(subject interface{}) []string { + n.lock.Lock() + defer n.lock.Unlock() + tpe := reflect.Indirect(reflect.ValueOf(subject)).Type() + names, ok := n.index[tpe] + if !ok { + names = n.makeNameIndex(tpe) + } + + res := make([]string, 0, len(names.jsonNames)) + for k := range names.jsonNames { + res = append(res, k) + } + return res +} + +// GetJSONName gets the json name for a go property name +func (n *NameProvider) GetJSONName(subject interface{}, name string) (string, bool) { + tpe := reflect.Indirect(reflect.ValueOf(subject)).Type() + return n.GetJSONNameForType(tpe, name) +} + +// GetJSONNameForType gets the json name for a go property name on a given type +func (n *NameProvider) GetJSONNameForType(tpe reflect.Type, name string) (string, bool) { + n.lock.Lock() + defer n.lock.Unlock() + names, ok := n.index[tpe] + if !ok { + names = n.makeNameIndex(tpe) + } + nme, ok := names.goNames[name] + return nme, ok +} + +func (n *NameProvider) makeNameIndex(tpe reflect.Type) nameIndex { + names := newNameIndex(tpe) + n.index[tpe] = names + return names +} + +// GetGoName gets the go name for a json property name +func (n *NameProvider) GetGoName(subject interface{}, name string) (string, bool) { + tpe := reflect.Indirect(reflect.ValueOf(subject)).Type() + return n.GetGoNameForType(tpe, name) +} + +// GetGoNameForType gets the go name for a given type for a json property name +func (n *NameProvider) GetGoNameForType(tpe reflect.Type, name string) (string, bool) { + n.lock.Lock() + defer n.lock.Unlock() + names, ok := n.index[tpe] + if !ok { + names = n.makeNameIndex(tpe) + } + nme, ok := names.jsonNames[name] + return nme, ok +} diff --git a/vendor/github.com/go-openapi/swag/loading.go b/vendor/github.com/go-openapi/swag/loading.go new file mode 100644 index 00000000..783442fd --- /dev/null +++ b/vendor/github.com/go-openapi/swag/loading.go @@ -0,0 +1,176 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swag + +import ( + "fmt" + "io" + "log" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "runtime" + "strings" + "time" +) + +// LoadHTTPTimeout the default timeout for load requests +var LoadHTTPTimeout = 30 * time.Second + +// LoadHTTPBasicAuthUsername the username to use when load requests require basic auth +var LoadHTTPBasicAuthUsername = "" + +// LoadHTTPBasicAuthPassword the password to use when load requests require basic auth +var LoadHTTPBasicAuthPassword = "" + +// LoadHTTPCustomHeaders an optional collection of custom HTTP headers for load requests +var LoadHTTPCustomHeaders = map[string]string{} + +// LoadFromFileOrHTTP loads the bytes from a file or a remote http server based on the path passed in +func LoadFromFileOrHTTP(pth string) ([]byte, error) { + return LoadStrategy(pth, os.ReadFile, loadHTTPBytes(LoadHTTPTimeout))(pth) +} + +// LoadFromFileOrHTTPWithTimeout loads the bytes from a file or a remote http server based on the path passed in +// timeout arg allows for per request overriding of the request timeout +func LoadFromFileOrHTTPWithTimeout(pth string, timeout time.Duration) ([]byte, error) { + return LoadStrategy(pth, os.ReadFile, loadHTTPBytes(timeout))(pth) +} + +// LoadStrategy returns a loader function for a given path or URI. +// +// The load strategy returns the remote load for any path starting with `http`. +// So this works for any URI with a scheme `http` or `https`. +// +// The fallback strategy is to call the local loader. +// +// The local loader takes a local file system path (absolute or relative) as argument, +// or alternatively a `file://...` URI, **without host** (see also below for windows). +// +// There are a few liberalities, initially intended to be tolerant regarding the URI syntax, +// especially on windows. +// +// Before the local loader is called, the given path is transformed: +// - percent-encoded characters are unescaped +// - simple paths (e.g. `./folder/file`) are passed as-is +// - on windows, occurrences of `/` are replaced by `\`, so providing a relative path such a `folder/file` works too. +// +// For paths provided as URIs with the "file" scheme, please note that: +// - `file://` is simply stripped. +// This means that the host part of the URI is not parsed at all. +// For example, `file:///folder/file" becomes "/folder/file`, +// but `file://localhost/folder/file` becomes `localhost/folder/file` on unix systems. +// Similarly, `file://./folder/file` yields `./folder/file`. +// - on windows, `file://...` can take a host so as to specify an UNC share location. +// +// Reminder about windows-specifics: +// - `file://host/folder/file` becomes an UNC path like `\\host\folder\file` (no port specification is supported) +// - `file:///c:/folder/file` becomes `C:\folder\file` +// - `file://c:/folder/file` is tolerated (without leading `/`) and becomes `c:\folder\file` +func LoadStrategy(pth string, local, remote func(string) ([]byte, error)) func(string) ([]byte, error) { + if strings.HasPrefix(pth, "http") { + return remote + } + + return func(p string) ([]byte, error) { + upth, err := url.PathUnescape(p) + if err != nil { + return nil, err + } + + if !strings.HasPrefix(p, `file://`) { + // regular file path provided: just normalize slashes + return local(filepath.FromSlash(upth)) + } + + if runtime.GOOS != "windows" { + // crude processing: this leaves full URIs with a host with a (mostly) unexpected result + upth = strings.TrimPrefix(upth, `file://`) + + return local(filepath.FromSlash(upth)) + } + + // windows-only pre-processing of file://... URIs + + // support for canonical file URIs on windows. + u, err := url.Parse(filepath.ToSlash(upth)) + if err != nil { + return nil, err + } + + if u.Host != "" { + // assume UNC name (volume share) + // NOTE: UNC port not yet supported + + // when the "host" segment is a drive letter: + // file://C:/folder/... => C:\folder + upth = path.Clean(strings.Join([]string{u.Host, u.Path}, `/`)) + if !strings.HasSuffix(u.Host, ":") && u.Host[0] != '.' { + // tolerance: if we have a leading dot, this can't be a host + // file://host/share/folder\... ==> \\host\share\path\folder + upth = "//" + upth + } + } else { + // no host, let's figure out if this is a drive letter + upth = strings.TrimPrefix(upth, `file://`) + first, _, _ := strings.Cut(strings.TrimPrefix(u.Path, "/"), "/") + if strings.HasSuffix(first, ":") { + // drive letter in the first segment: + // file:///c:/folder/... ==> strip the leading slash + upth = strings.TrimPrefix(upth, `/`) + } + } + + return local(filepath.FromSlash(upth)) + } +} + +func loadHTTPBytes(timeout time.Duration) func(path string) ([]byte, error) { + return func(path string) ([]byte, error) { + client := &http.Client{Timeout: timeout} + req, err := http.NewRequest(http.MethodGet, path, nil) //nolint:noctx + if err != nil { + return nil, err + } + + if LoadHTTPBasicAuthUsername != "" && LoadHTTPBasicAuthPassword != "" { + req.SetBasicAuth(LoadHTTPBasicAuthUsername, LoadHTTPBasicAuthPassword) + } + + for key, val := range LoadHTTPCustomHeaders { + req.Header.Set(key, val) + } + + resp, err := client.Do(req) + defer func() { + if resp != nil { + if e := resp.Body.Close(); e != nil { + log.Println(e) + } + } + }() + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("could not access document at %q [%s] ", path, resp.Status) + } + + return io.ReadAll(resp.Body) + } +} diff --git a/vendor/github.com/go-openapi/swag/name_lexem.go b/vendor/github.com/go-openapi/swag/name_lexem.go new file mode 100644 index 00000000..8bb64ac3 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/name_lexem.go @@ -0,0 +1,93 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swag + +import ( + "unicode" + "unicode/utf8" +) + +type ( + lexemKind uint8 + + nameLexem struct { + original string + matchedInitialism string + kind lexemKind + } +) + +const ( + lexemKindCasualName lexemKind = iota + lexemKindInitialismName +) + +func newInitialismNameLexem(original, matchedInitialism string) nameLexem { + return nameLexem{ + kind: lexemKindInitialismName, + original: original, + matchedInitialism: matchedInitialism, + } +} + +func newCasualNameLexem(original string) nameLexem { + return nameLexem{ + kind: lexemKindCasualName, + original: original, + } +} + +func (l nameLexem) GetUnsafeGoName() string { + if l.kind == lexemKindInitialismName { + return l.matchedInitialism + } + + var ( + first rune + rest string + ) + + for i, orig := range l.original { + if i == 0 { + first = orig + continue + } + + if i > 0 { + rest = l.original[i:] + break + } + } + + if len(l.original) > 1 { + b := poolOfBuffers.BorrowBuffer(utf8.UTFMax + len(rest)) + defer func() { + poolOfBuffers.RedeemBuffer(b) + }() + b.WriteRune(unicode.ToUpper(first)) + b.WriteString(lower(rest)) + return b.String() + } + + return l.original +} + +func (l nameLexem) GetOriginal() string { + return l.original +} + +func (l nameLexem) IsInitialism() bool { + return l.kind == lexemKindInitialismName +} diff --git a/vendor/github.com/go-openapi/swag/net.go b/vendor/github.com/go-openapi/swag/net.go new file mode 100644 index 00000000..821235f8 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/net.go @@ -0,0 +1,38 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swag + +import ( + "net" + "strconv" +) + +// SplitHostPort splits a network address into a host and a port. +// The port is -1 when there is no port to be found +func SplitHostPort(addr string) (host string, port int, err error) { + h, p, err := net.SplitHostPort(addr) + if err != nil { + return "", -1, err + } + if p == "" { + return "", -1, &net.AddrError{Err: "missing port in address", Addr: addr} + } + + pi, err := strconv.Atoi(p) + if err != nil { + return "", -1, err + } + return h, pi, nil +} diff --git a/vendor/github.com/go-openapi/swag/path.go b/vendor/github.com/go-openapi/swag/path.go new file mode 100644 index 00000000..941bd017 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/path.go @@ -0,0 +1,59 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swag + +import ( + "os" + "path/filepath" + "runtime" + "strings" +) + +const ( + // GOPATHKey represents the env key for gopath + GOPATHKey = "GOPATH" +) + +// FindInSearchPath finds a package in a provided lists of paths +func FindInSearchPath(searchPath, pkg string) string { + pathsList := filepath.SplitList(searchPath) + for _, path := range pathsList { + if evaluatedPath, err := filepath.EvalSymlinks(filepath.Join(path, "src", pkg)); err == nil { + if _, err := os.Stat(evaluatedPath); err == nil { + return evaluatedPath + } + } + } + return "" +} + +// FindInGoSearchPath finds a package in the $GOPATH:$GOROOT +func FindInGoSearchPath(pkg string) string { + return FindInSearchPath(FullGoSearchPath(), pkg) +} + +// FullGoSearchPath gets the search paths for finding packages +func FullGoSearchPath() string { + allPaths := os.Getenv(GOPATHKey) + if allPaths == "" { + allPaths = filepath.Join(os.Getenv("HOME"), "go") + } + if allPaths != "" { + allPaths = strings.Join([]string{allPaths, runtime.GOROOT()}, ":") + } else { + allPaths = runtime.GOROOT() + } + return allPaths +} diff --git a/vendor/github.com/go-openapi/swag/split.go b/vendor/github.com/go-openapi/swag/split.go new file mode 100644 index 00000000..274727a8 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/split.go @@ -0,0 +1,508 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swag + +import ( + "bytes" + "sync" + "unicode" + "unicode/utf8" +) + +type ( + splitter struct { + initialisms []string + initialismsRunes [][]rune + initialismsUpperCased [][]rune // initialisms cached in their trimmed, upper-cased version + postSplitInitialismCheck bool + } + + splitterOption func(*splitter) + + initialismMatch struct { + body []rune + start, end int + complete bool + } + initialismMatches []initialismMatch +) + +type ( + // memory pools of temporary objects. + // + // These are used to recycle temporarily allocated objects + // and relieve the GC from undue pressure. + + matchesPool struct { + *sync.Pool + } + + buffersPool struct { + *sync.Pool + } + + lexemsPool struct { + *sync.Pool + } + + splittersPool struct { + *sync.Pool + } +) + +var ( + // poolOfMatches holds temporary slices for recycling during the initialism match process + poolOfMatches = matchesPool{ + Pool: &sync.Pool{ + New: func() any { + s := make(initialismMatches, 0, maxAllocMatches) + + return &s + }, + }, + } + + poolOfBuffers = buffersPool{ + Pool: &sync.Pool{ + New: func() any { + return new(bytes.Buffer) + }, + }, + } + + poolOfLexems = lexemsPool{ + Pool: &sync.Pool{ + New: func() any { + s := make([]nameLexem, 0, maxAllocMatches) + + return &s + }, + }, + } + + poolOfSplitters = splittersPool{ + Pool: &sync.Pool{ + New: func() any { + s := newSplitter() + + return &s + }, + }, + } +) + +// nameReplaceTable finds a word representation for special characters. +func nameReplaceTable(r rune) (string, bool) { + switch r { + case '@': + return "At ", true + case '&': + return "And ", true + case '|': + return "Pipe ", true + case '$': + return "Dollar ", true + case '!': + return "Bang ", true + case '-': + return "", true + case '_': + return "", true + default: + return "", false + } +} + +// split calls the splitter. +// +// Use newSplitter for more control and options +func split(str string) []string { + s := poolOfSplitters.BorrowSplitter() + lexems := s.split(str) + result := make([]string, 0, len(*lexems)) + + for _, lexem := range *lexems { + result = append(result, lexem.GetOriginal()) + } + poolOfLexems.RedeemLexems(lexems) + poolOfSplitters.RedeemSplitter(s) + + return result + +} + +func newSplitter(options ...splitterOption) splitter { + s := splitter{ + postSplitInitialismCheck: false, + initialisms: initialisms, + initialismsRunes: initialismsRunes, + initialismsUpperCased: initialismsUpperCased, + } + + for _, option := range options { + option(&s) + } + + return s +} + +// withPostSplitInitialismCheck allows to catch initialisms after main split process +func withPostSplitInitialismCheck(s *splitter) { + s.postSplitInitialismCheck = true +} + +func (p matchesPool) BorrowMatches() *initialismMatches { + s := p.Get().(*initialismMatches) + *s = (*s)[:0] // reset slice, keep allocated capacity + + return s +} + +func (p buffersPool) BorrowBuffer(size int) *bytes.Buffer { + s := p.Get().(*bytes.Buffer) + s.Reset() + + if s.Cap() < size { + s.Grow(size) + } + + return s +} + +func (p lexemsPool) BorrowLexems() *[]nameLexem { + s := p.Get().(*[]nameLexem) + *s = (*s)[:0] // reset slice, keep allocated capacity + + return s +} + +func (p splittersPool) BorrowSplitter(options ...splitterOption) *splitter { + s := p.Get().(*splitter) + s.postSplitInitialismCheck = false // reset options + for _, apply := range options { + apply(s) + } + + return s +} + +func (p matchesPool) RedeemMatches(s *initialismMatches) { + p.Put(s) +} + +func (p buffersPool) RedeemBuffer(s *bytes.Buffer) { + p.Put(s) +} + +func (p lexemsPool) RedeemLexems(s *[]nameLexem) { + p.Put(s) +} + +func (p splittersPool) RedeemSplitter(s *splitter) { + p.Put(s) +} + +func (m initialismMatch) isZero() bool { + return m.start == 0 && m.end == 0 +} + +func (s splitter) split(name string) *[]nameLexem { + nameRunes := []rune(name) + matches := s.gatherInitialismMatches(nameRunes) + if matches == nil { + return poolOfLexems.BorrowLexems() + } + + return s.mapMatchesToNameLexems(nameRunes, matches) +} + +func (s splitter) gatherInitialismMatches(nameRunes []rune) *initialismMatches { + var matches *initialismMatches + + for currentRunePosition, currentRune := range nameRunes { + // recycle these allocations as we loop over runes + // with such recycling, only 2 slices should be allocated per call + // instead of o(n). + newMatches := poolOfMatches.BorrowMatches() + + // check current initialism matches + if matches != nil { // skip first iteration + for _, match := range *matches { + if keepCompleteMatch := match.complete; keepCompleteMatch { + *newMatches = append(*newMatches, match) + continue + } + + // drop failed match + currentMatchRune := match.body[currentRunePosition-match.start] + if currentMatchRune != currentRune { + continue + } + + // try to complete ongoing match + if currentRunePosition-match.start == len(match.body)-1 { + // we are close; the next step is to check the symbol ahead + // if it is a small letter, then it is not the end of match + // but beginning of the next word + + if currentRunePosition < len(nameRunes)-1 { + nextRune := nameRunes[currentRunePosition+1] + if newWord := unicode.IsLower(nextRune); newWord { + // oh ok, it was the start of a new word + continue + } + } + + match.complete = true + match.end = currentRunePosition + } + + *newMatches = append(*newMatches, match) + } + } + + // check for new initialism matches + for i := range s.initialisms { + initialismRunes := s.initialismsRunes[i] + if initialismRunes[0] == currentRune { + *newMatches = append(*newMatches, initialismMatch{ + start: currentRunePosition, + body: initialismRunes, + complete: false, + }) + } + } + + if matches != nil { + poolOfMatches.RedeemMatches(matches) + } + matches = newMatches + } + + // up to the caller to redeem this last slice + return matches +} + +func (s splitter) mapMatchesToNameLexems(nameRunes []rune, matches *initialismMatches) *[]nameLexem { + nameLexems := poolOfLexems.BorrowLexems() + + var lastAcceptedMatch initialismMatch + for _, match := range *matches { + if !match.complete { + continue + } + + if firstMatch := lastAcceptedMatch.isZero(); firstMatch { + s.appendBrokenDownCasualString(nameLexems, nameRunes[:match.start]) + *nameLexems = append(*nameLexems, s.breakInitialism(string(match.body))) + + lastAcceptedMatch = match + + continue + } + + if overlappedMatch := match.start <= lastAcceptedMatch.end; overlappedMatch { + continue + } + + middle := nameRunes[lastAcceptedMatch.end+1 : match.start] + s.appendBrokenDownCasualString(nameLexems, middle) + *nameLexems = append(*nameLexems, s.breakInitialism(string(match.body))) + + lastAcceptedMatch = match + } + + // we have not found any accepted matches + if lastAcceptedMatch.isZero() { + *nameLexems = (*nameLexems)[:0] + s.appendBrokenDownCasualString(nameLexems, nameRunes) + } else if lastAcceptedMatch.end+1 != len(nameRunes) { + rest := nameRunes[lastAcceptedMatch.end+1:] + s.appendBrokenDownCasualString(nameLexems, rest) + } + + poolOfMatches.RedeemMatches(matches) + + return nameLexems +} + +func (s splitter) breakInitialism(original string) nameLexem { + return newInitialismNameLexem(original, original) +} + +func (s splitter) appendBrokenDownCasualString(segments *[]nameLexem, str []rune) { + currentSegment := poolOfBuffers.BorrowBuffer(len(str)) // unlike strings.Builder, bytes.Buffer initial storage can reused + defer func() { + poolOfBuffers.RedeemBuffer(currentSegment) + }() + + addCasualNameLexem := func(original string) { + *segments = append(*segments, newCasualNameLexem(original)) + } + + addInitialismNameLexem := func(original, match string) { + *segments = append(*segments, newInitialismNameLexem(original, match)) + } + + var addNameLexem func(string) + if s.postSplitInitialismCheck { + addNameLexem = func(original string) { + for i := range s.initialisms { + if isEqualFoldIgnoreSpace(s.initialismsUpperCased[i], original) { + addInitialismNameLexem(original, s.initialisms[i]) + + return + } + } + + addCasualNameLexem(original) + } + } else { + addNameLexem = addCasualNameLexem + } + + for _, rn := range str { + if replace, found := nameReplaceTable(rn); found { + if currentSegment.Len() > 0 { + addNameLexem(currentSegment.String()) + currentSegment.Reset() + } + + if replace != "" { + addNameLexem(replace) + } + + continue + } + + if !unicode.In(rn, unicode.L, unicode.M, unicode.N, unicode.Pc) { + if currentSegment.Len() > 0 { + addNameLexem(currentSegment.String()) + currentSegment.Reset() + } + + continue + } + + if unicode.IsUpper(rn) { + if currentSegment.Len() > 0 { + addNameLexem(currentSegment.String()) + } + currentSegment.Reset() + } + + currentSegment.WriteRune(rn) + } + + if currentSegment.Len() > 0 { + addNameLexem(currentSegment.String()) + } +} + +// isEqualFoldIgnoreSpace is the same as strings.EqualFold, but +// it ignores leading and trailing blank spaces in the compared +// string. +// +// base is assumed to be composed of upper-cased runes, and be already +// trimmed. +// +// This code is heavily inspired from strings.EqualFold. +func isEqualFoldIgnoreSpace(base []rune, str string) bool { + var i, baseIndex int + // equivalent to b := []byte(str), but without data copy + b := hackStringBytes(str) + + for i < len(b) { + if c := b[i]; c < utf8.RuneSelf { + // fast path for ASCII + if c != ' ' && c != '\t' { + break + } + i++ + + continue + } + + // unicode case + r, size := utf8.DecodeRune(b[i:]) + if !unicode.IsSpace(r) { + break + } + i += size + } + + if i >= len(b) { + return len(base) == 0 + } + + for _, baseRune := range base { + if i >= len(b) { + break + } + + if c := b[i]; c < utf8.RuneSelf { + // single byte rune case (ASCII) + if baseRune >= utf8.RuneSelf { + return false + } + + baseChar := byte(baseRune) + if c != baseChar && + !('a' <= c && c <= 'z' && c-'a'+'A' == baseChar) { + return false + } + + baseIndex++ + i++ + + continue + } + + // unicode case + r, size := utf8.DecodeRune(b[i:]) + if unicode.ToUpper(r) != baseRune { + return false + } + baseIndex++ + i += size + } + + if baseIndex != len(base) { + return false + } + + // all passed: now we should only have blanks + for i < len(b) { + if c := b[i]; c < utf8.RuneSelf { + // fast path for ASCII + if c != ' ' && c != '\t' { + return false + } + i++ + + continue + } + + // unicode case + r, size := utf8.DecodeRune(b[i:]) + if !unicode.IsSpace(r) { + return false + } + + i += size + } + + return true +} diff --git a/vendor/github.com/go-openapi/swag/string_bytes.go b/vendor/github.com/go-openapi/swag/string_bytes.go new file mode 100644 index 00000000..90745d5c --- /dev/null +++ b/vendor/github.com/go-openapi/swag/string_bytes.go @@ -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)) +} diff --git a/vendor/github.com/go-openapi/swag/util.go b/vendor/github.com/go-openapi/swag/util.go new file mode 100644 index 00000000..5051401c --- /dev/null +++ b/vendor/github.com/go-openapi/swag/util.go @@ -0,0 +1,364 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swag + +import ( + "reflect" + "strings" + "unicode" + "unicode/utf8" +) + +// GoNamePrefixFunc sets an optional rule to prefix go names +// which do not start with a letter. +// +// The prefix function is assumed to return a string that starts with an upper case letter. +// +// e.g. to help convert "123" into "{prefix}123" +// +// The default is to prefix with "X" +var GoNamePrefixFunc func(string) string + +func prefixFunc(name, in string) string { + if GoNamePrefixFunc == nil { + return "X" + in + } + + return GoNamePrefixFunc(name) + in +} + +const ( + // collectionFormatComma = "csv" + collectionFormatSpace = "ssv" + collectionFormatTab = "tsv" + collectionFormatPipe = "pipes" + collectionFormatMulti = "multi" +) + +// JoinByFormat joins a string array by a known format (e.g. swagger's collectionFormat attribute): +// +// ssv: space separated value +// tsv: tab separated value +// pipes: pipe (|) separated value +// csv: comma separated value (default) +func JoinByFormat(data []string, format string) []string { + if len(data) == 0 { + return data + } + var sep string + switch format { + case collectionFormatSpace: + sep = " " + case collectionFormatTab: + sep = "\t" + case collectionFormatPipe: + sep = "|" + case collectionFormatMulti: + return data + default: + sep = "," + } + return []string{strings.Join(data, sep)} +} + +// SplitByFormat splits a string by a known format: +// +// ssv: space separated value +// tsv: tab separated value +// pipes: pipe (|) separated value +// csv: comma separated value (default) +func SplitByFormat(data, format string) []string { + if data == "" { + return nil + } + var sep string + switch format { + case collectionFormatSpace: + sep = " " + case collectionFormatTab: + sep = "\t" + case collectionFormatPipe: + sep = "|" + case collectionFormatMulti: + return nil + default: + sep = "," + } + var result []string + for _, s := range strings.Split(data, sep) { + if ts := strings.TrimSpace(s); ts != "" { + result = append(result, ts) + } + } + return result +} + +// Removes leading whitespaces +func trim(str string) string { + return strings.TrimSpace(str) +} + +// Shortcut to strings.ToUpper() +func upper(str string) string { + return strings.ToUpper(trim(str)) +} + +// Shortcut to strings.ToLower() +func lower(str string) string { + return strings.ToLower(trim(str)) +} + +// Camelize an uppercased word +func Camelize(word string) string { + camelized := poolOfBuffers.BorrowBuffer(len(word)) + defer func() { + poolOfBuffers.RedeemBuffer(camelized) + }() + + for pos, ru := range []rune(word) { + if pos > 0 { + camelized.WriteRune(unicode.ToLower(ru)) + } else { + camelized.WriteRune(unicode.ToUpper(ru)) + } + } + return camelized.String() +} + +// ToFileName lowercases and underscores a go type name +func ToFileName(name string) string { + in := split(name) + out := make([]string, 0, len(in)) + + for _, w := range in { + out = append(out, lower(w)) + } + + return strings.Join(out, "_") +} + +// ToCommandName lowercases and underscores a go type name +func ToCommandName(name string) string { + in := split(name) + out := make([]string, 0, len(in)) + + for _, w := range in { + out = append(out, lower(w)) + } + return strings.Join(out, "-") +} + +// ToHumanNameLower represents a code name as a human series of words +func ToHumanNameLower(name string) string { + s := poolOfSplitters.BorrowSplitter(withPostSplitInitialismCheck) + in := s.split(name) + poolOfSplitters.RedeemSplitter(s) + out := make([]string, 0, len(*in)) + + for _, w := range *in { + if !w.IsInitialism() { + out = append(out, lower(w.GetOriginal())) + } else { + out = append(out, trim(w.GetOriginal())) + } + } + poolOfLexems.RedeemLexems(in) + + return strings.Join(out, " ") +} + +// ToHumanNameTitle represents a code name as a human series of words with the first letters titleized +func ToHumanNameTitle(name string) string { + s := poolOfSplitters.BorrowSplitter(withPostSplitInitialismCheck) + in := s.split(name) + poolOfSplitters.RedeemSplitter(s) + + out := make([]string, 0, len(*in)) + for _, w := range *in { + original := trim(w.GetOriginal()) + if !w.IsInitialism() { + out = append(out, Camelize(original)) + } else { + out = append(out, original) + } + } + poolOfLexems.RedeemLexems(in) + + return strings.Join(out, " ") +} + +// ToJSONName camelcases a name which can be underscored or pascal cased +func ToJSONName(name string) string { + in := split(name) + out := make([]string, 0, len(in)) + + for i, w := range in { + if i == 0 { + out = append(out, lower(w)) + continue + } + out = append(out, Camelize(trim(w))) + } + return strings.Join(out, "") +} + +// ToVarName camelcases a name which can be underscored or pascal cased +func ToVarName(name string) string { + res := ToGoName(name) + if isInitialism(res) { + return lower(res) + } + if len(res) <= 1 { + return lower(res) + } + return lower(res[:1]) + res[1:] +} + +// ToGoName translates a swagger name which can be underscored or camel cased to a name that golint likes +func ToGoName(name string) string { + s := poolOfSplitters.BorrowSplitter(withPostSplitInitialismCheck) + lexems := s.split(name) + poolOfSplitters.RedeemSplitter(s) + defer func() { + poolOfLexems.RedeemLexems(lexems) + }() + lexemes := *lexems + + if len(lexemes) == 0 { + return "" + } + + result := poolOfBuffers.BorrowBuffer(len(name)) + defer func() { + poolOfBuffers.RedeemBuffer(result) + }() + + // check if not starting with a letter, upper case + firstPart := lexemes[0].GetUnsafeGoName() + if lexemes[0].IsInitialism() { + firstPart = upper(firstPart) + } + + if c := firstPart[0]; c < utf8.RuneSelf { + // ASCII + switch { + case 'A' <= c && c <= 'Z': + result.WriteString(firstPart) + case 'a' <= c && c <= 'z': + result.WriteByte(c - 'a' + 'A') + result.WriteString(firstPart[1:]) + default: + result.WriteString(prefixFunc(name, firstPart)) + // NOTE: no longer check if prefixFunc returns a string that starts with uppercase: + // assume this is always the case + } + } else { + // unicode + firstRune, _ := utf8.DecodeRuneInString(firstPart) + switch { + case !unicode.IsLetter(firstRune): + result.WriteString(prefixFunc(name, firstPart)) + case !unicode.IsUpper(firstRune): + result.WriteString(prefixFunc(name, firstPart)) + /* + result.WriteRune(unicode.ToUpper(firstRune)) + result.WriteString(firstPart[offset:]) + */ + default: + result.WriteString(firstPart) + } + } + + for _, lexem := range lexemes[1:] { + goName := lexem.GetUnsafeGoName() + + // to support old behavior + if lexem.IsInitialism() { + goName = upper(goName) + } + result.WriteString(goName) + } + + return result.String() +} + +// ContainsStrings searches a slice of strings for a case-sensitive match +func ContainsStrings(coll []string, item string) bool { + for _, a := range coll { + if a == item { + return true + } + } + return false +} + +// ContainsStringsCI searches a slice of strings for a case-insensitive match +func ContainsStringsCI(coll []string, item string) bool { + for _, a := range coll { + if strings.EqualFold(a, item) { + return true + } + } + return false +} + +type zeroable interface { + IsZero() bool +} + +// IsZero returns true when the value passed into the function is a zero value. +// This allows for safer checking of interface values. +func IsZero(data interface{}) bool { + v := reflect.ValueOf(data) + // check for nil data + switch v.Kind() { //nolint:exhaustive + case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + if v.IsNil() { + return true + } + } + + // check for things that have an IsZero method instead + if vv, ok := data.(zeroable); ok { + return vv.IsZero() + } + + // continue with slightly more complex reflection + switch v.Kind() { //nolint:exhaustive + case reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Struct, reflect.Array: + return reflect.DeepEqual(data, reflect.Zero(v.Type()).Interface()) + case reflect.Invalid: + return true + default: + return false + } +} + +// CommandLineOptionsGroup represents a group of user-defined command line options +type CommandLineOptionsGroup struct { + ShortDescription string + LongDescription string + Options interface{} +} diff --git a/vendor/github.com/go-openapi/swag/yaml.go b/vendor/github.com/go-openapi/swag/yaml.go new file mode 100644 index 00000000..f59e0259 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/yaml.go @@ -0,0 +1,481 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swag + +import ( + "encoding/json" + "errors" + "fmt" + "path/filepath" + "reflect" + "sort" + "strconv" + + "github.com/mailru/easyjson/jlexer" + "github.com/mailru/easyjson/jwriter" + yaml "gopkg.in/yaml.v3" +) + +// YAMLMatcher matches yaml +func YAMLMatcher(path string) bool { + ext := filepath.Ext(path) + return ext == ".yaml" || ext == ".yml" +} + +// YAMLToJSON converts YAML unmarshaled data into json compatible data +func YAMLToJSON(data interface{}) (json.RawMessage, error) { + jm, err := transformData(data) + if err != nil { + return nil, err + } + b, err := WriteJSON(jm) + return json.RawMessage(b), err +} + +// BytesToYAMLDoc converts a byte slice into a YAML document +func BytesToYAMLDoc(data []byte) (interface{}, error) { + var document yaml.Node // preserve order that is present in the document + if err := yaml.Unmarshal(data, &document); err != nil { + return nil, err + } + if document.Kind != yaml.DocumentNode || len(document.Content) != 1 || document.Content[0].Kind != yaml.MappingNode { + return nil, errors.New("only YAML documents that are objects are supported") + } + return &document, nil +} + +func yamlNode(root *yaml.Node) (interface{}, error) { + switch root.Kind { + case yaml.DocumentNode: + return yamlDocument(root) + case yaml.SequenceNode: + return yamlSequence(root) + case yaml.MappingNode: + return yamlMapping(root) + case yaml.ScalarNode: + return yamlScalar(root) + case yaml.AliasNode: + return yamlNode(root.Alias) + default: + return nil, fmt.Errorf("unsupported YAML node type: %v", root.Kind) + } +} + +func yamlDocument(node *yaml.Node) (interface{}, error) { + if len(node.Content) != 1 { + return nil, fmt.Errorf("unexpected YAML Document node content length: %d", len(node.Content)) + } + return yamlNode(node.Content[0]) +} + +func yamlMapping(node *yaml.Node) (interface{}, error) { + m := make(JSONMapSlice, len(node.Content)/2) + + var j int + for i := 0; i < len(node.Content); i += 2 { + var nmi JSONMapItem + k, err := yamlStringScalarC(node.Content[i]) + if err != nil { + return nil, fmt.Errorf("unable to decode YAML map key: %w", err) + } + nmi.Key = k + v, err := yamlNode(node.Content[i+1]) + if err != nil { + return nil, fmt.Errorf("unable to process YAML map value for key %q: %w", k, err) + } + nmi.Value = v + m[j] = nmi + j++ + } + return m, nil +} + +func yamlSequence(node *yaml.Node) (interface{}, error) { + s := make([]interface{}, 0) + + for i := 0; i < len(node.Content); i++ { + + v, err := yamlNode(node.Content[i]) + if err != nil { + return nil, fmt.Errorf("unable to decode YAML sequence value: %w", err) + } + s = append(s, v) + } + return s, nil +} + +const ( // See https://yaml.org/type/ + yamlStringScalar = "tag:yaml.org,2002:str" + yamlIntScalar = "tag:yaml.org,2002:int" + yamlBoolScalar = "tag:yaml.org,2002:bool" + yamlFloatScalar = "tag:yaml.org,2002:float" + yamlTimestamp = "tag:yaml.org,2002:timestamp" + yamlNull = "tag:yaml.org,2002:null" +) + +func yamlScalar(node *yaml.Node) (interface{}, error) { + switch node.LongTag() { + case yamlStringScalar: + return node.Value, nil + case yamlBoolScalar: + b, err := strconv.ParseBool(node.Value) + if err != nil { + return nil, fmt.Errorf("unable to process scalar node. Got %q. Expecting bool content: %w", node.Value, err) + } + return b, nil + case yamlIntScalar: + i, err := strconv.ParseInt(node.Value, 10, 64) + if err != nil { + return nil, fmt.Errorf("unable to process scalar node. Got %q. Expecting integer content: %w", node.Value, err) + } + return i, nil + case yamlFloatScalar: + f, err := strconv.ParseFloat(node.Value, 64) + if err != nil { + return nil, fmt.Errorf("unable to process scalar node. Got %q. Expecting float content: %w", node.Value, err) + } + return f, nil + case yamlTimestamp: + return node.Value, nil + case yamlNull: + return nil, nil //nolint:nilnil + default: + return nil, fmt.Errorf("YAML tag %q is not supported", node.LongTag()) + } +} + +func yamlStringScalarC(node *yaml.Node) (string, error) { + if node.Kind != yaml.ScalarNode { + return "", fmt.Errorf("expecting a string scalar but got %q", node.Kind) + } + switch node.LongTag() { + case yamlStringScalar, yamlIntScalar, yamlFloatScalar: + return node.Value, nil + default: + return "", fmt.Errorf("YAML tag %q is not supported as map key", node.LongTag()) + } +} + +// JSONMapSlice represent a JSON object, with the order of keys maintained +type JSONMapSlice []JSONMapItem + +// MarshalJSON renders a JSONMapSlice as JSON +func (s JSONMapSlice) MarshalJSON() ([]byte, error) { + w := &jwriter.Writer{Flags: jwriter.NilMapAsEmpty | jwriter.NilSliceAsEmpty} + s.MarshalEasyJSON(w) + return w.BuildBytes() +} + +// MarshalEasyJSON renders a JSONMapSlice as JSON, using easyJSON +func (s JSONMapSlice) MarshalEasyJSON(w *jwriter.Writer) { + w.RawByte('{') + + ln := len(s) + last := ln - 1 + for i := 0; i < ln; i++ { + s[i].MarshalEasyJSON(w) + if i != last { // last item + w.RawByte(',') + } + } + + w.RawByte('}') +} + +// UnmarshalJSON makes a JSONMapSlice from JSON +func (s *JSONMapSlice) UnmarshalJSON(data []byte) error { + l := jlexer.Lexer{Data: data} + s.UnmarshalEasyJSON(&l) + return l.Error() +} + +// UnmarshalEasyJSON makes a JSONMapSlice from JSON, using easyJSON +func (s *JSONMapSlice) UnmarshalEasyJSON(in *jlexer.Lexer) { + if in.IsNull() { + in.Skip() + return + } + + var result JSONMapSlice + in.Delim('{') + for !in.IsDelim('}') { + var mi JSONMapItem + mi.UnmarshalEasyJSON(in) + result = append(result, mi) + } + *s = result +} + +func (s JSONMapSlice) MarshalYAML() (interface{}, error) { + var n yaml.Node + n.Kind = yaml.DocumentNode + var nodes []*yaml.Node + for _, item := range s { + nn, err := json2yaml(item.Value) + if err != nil { + return nil, err + } + ns := []*yaml.Node{ + { + Kind: yaml.ScalarNode, + Tag: yamlStringScalar, + Value: item.Key, + }, + nn, + } + nodes = append(nodes, ns...) + } + + n.Content = []*yaml.Node{ + { + Kind: yaml.MappingNode, + Content: nodes, + }, + } + + return yaml.Marshal(&n) +} + +func isNil(input interface{}) bool { + if input == nil { + return true + } + kind := reflect.TypeOf(input).Kind() + switch kind { //nolint:exhaustive + case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan: + return reflect.ValueOf(input).IsNil() + default: + return false + } +} + +func json2yaml(item interface{}) (*yaml.Node, error) { + if isNil(item) { + return &yaml.Node{ + Kind: yaml.ScalarNode, + Value: "null", + }, nil + } + + switch val := item.(type) { + case JSONMapSlice: + var n yaml.Node + n.Kind = yaml.MappingNode + for i := range val { + childNode, err := json2yaml(&val[i].Value) + if err != nil { + return nil, err + } + n.Content = append(n.Content, &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: yamlStringScalar, + Value: val[i].Key, + }, childNode) + } + return &n, nil + case map[string]interface{}: + var n yaml.Node + n.Kind = yaml.MappingNode + keys := make([]string, 0, len(val)) + for k := range val { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + v := val[k] + childNode, err := json2yaml(v) + if err != nil { + return nil, err + } + n.Content = append(n.Content, &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: yamlStringScalar, + Value: k, + }, childNode) + } + return &n, nil + case []interface{}: + var n yaml.Node + n.Kind = yaml.SequenceNode + for i := range val { + childNode, err := json2yaml(val[i]) + if err != nil { + return nil, err + } + n.Content = append(n.Content, childNode) + } + return &n, nil + case string: + return &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: yamlStringScalar, + Value: val, + }, nil + case float64: + return &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: yamlFloatScalar, + Value: strconv.FormatFloat(val, 'f', -1, 64), + }, nil + case int64: + return &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: yamlIntScalar, + Value: strconv.FormatInt(val, 10), + }, nil + case uint64: + return &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: yamlIntScalar, + Value: strconv.FormatUint(val, 10), + }, nil + case bool: + return &yaml.Node{ + Kind: yaml.ScalarNode, + Tag: yamlBoolScalar, + Value: strconv.FormatBool(val), + }, nil + default: + return nil, fmt.Errorf("unhandled type: %T", val) + } +} + +// JSONMapItem represents the value of a key in a JSON object held by JSONMapSlice +type JSONMapItem struct { + Key string + Value interface{} +} + +// MarshalJSON renders a JSONMapItem as JSON +func (s JSONMapItem) MarshalJSON() ([]byte, error) { + w := &jwriter.Writer{Flags: jwriter.NilMapAsEmpty | jwriter.NilSliceAsEmpty} + s.MarshalEasyJSON(w) + return w.BuildBytes() +} + +// MarshalEasyJSON renders a JSONMapItem as JSON, using easyJSON +func (s JSONMapItem) MarshalEasyJSON(w *jwriter.Writer) { + w.String(s.Key) + w.RawByte(':') + w.Raw(WriteJSON(s.Value)) +} + +// UnmarshalJSON makes a JSONMapItem from JSON +func (s *JSONMapItem) UnmarshalJSON(data []byte) error { + l := jlexer.Lexer{Data: data} + s.UnmarshalEasyJSON(&l) + return l.Error() +} + +// UnmarshalEasyJSON makes a JSONMapItem from JSON, using easyJSON +func (s *JSONMapItem) UnmarshalEasyJSON(in *jlexer.Lexer) { + key := in.UnsafeString() + in.WantColon() + value := in.Interface() + in.WantComma() + s.Key = key + s.Value = value +} + +func transformData(input interface{}) (out interface{}, err error) { + format := func(t interface{}) (string, error) { + switch k := t.(type) { + case string: + return k, nil + case uint: + return strconv.FormatUint(uint64(k), 10), nil + case uint8: + return strconv.FormatUint(uint64(k), 10), nil + case uint16: + return strconv.FormatUint(uint64(k), 10), nil + case uint32: + return strconv.FormatUint(uint64(k), 10), nil + case uint64: + return strconv.FormatUint(k, 10), nil + case int: + return strconv.Itoa(k), nil + case int8: + return strconv.FormatInt(int64(k), 10), nil + case int16: + return strconv.FormatInt(int64(k), 10), nil + case int32: + return strconv.FormatInt(int64(k), 10), nil + case int64: + return strconv.FormatInt(k, 10), nil + default: + return "", fmt.Errorf("unexpected map key type, got: %T", k) + } + } + + switch in := input.(type) { + case yaml.Node: + return yamlNode(&in) + case *yaml.Node: + return yamlNode(in) + case map[interface{}]interface{}: + o := make(JSONMapSlice, 0, len(in)) + for ke, va := range in { + var nmi JSONMapItem + if nmi.Key, err = format(ke); err != nil { + return nil, err + } + + v, ert := transformData(va) + if ert != nil { + return nil, ert + } + nmi.Value = v + o = append(o, nmi) + } + return o, nil + case []interface{}: + len1 := len(in) + o := make([]interface{}, len1) + for i := 0; i < len1; i++ { + o[i], err = transformData(in[i]) + if err != nil { + return nil, err + } + } + return o, nil + } + return input, nil +} + +// YAMLDoc loads a yaml document from either http or a file and converts it to json +func YAMLDoc(path string) (json.RawMessage, error) { + yamlDoc, err := YAMLData(path) + if err != nil { + return nil, err + } + + data, err := YAMLToJSON(yamlDoc) + if err != nil { + return nil, err + } + + return data, nil +} + +// YAMLData loads a yaml document from either http or a file +func YAMLData(path string) (interface{}, error) { + data, err := LoadFromFileOrHTTP(path) + if err != nil { + return nil, err + } + + return BytesToYAMLDoc(data) +} diff --git a/vendor/github.com/invopop/yaml/.gitignore b/vendor/github.com/invopop/yaml/.gitignore new file mode 100644 index 00000000..e256a31e --- /dev/null +++ b/vendor/github.com/invopop/yaml/.gitignore @@ -0,0 +1,20 @@ +# OSX leaves these everywhere on SMB shares +._* + +# Eclipse files +.classpath +.project +.settings/** + +# Emacs save files +*~ + +# Vim-related files +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist + +# Go test binaries +*.test diff --git a/vendor/github.com/invopop/yaml/.golangci.toml b/vendor/github.com/invopop/yaml/.golangci.toml new file mode 100644 index 00000000..61b2b79a --- /dev/null +++ b/vendor/github.com/invopop/yaml/.golangci.toml @@ -0,0 +1,16 @@ +[run] +timeout = "120s" + +[output] +format = "colored-line-number" + +[linters] +enable = [ + "gocyclo", "unconvert", "goimports", "unused", "unused", + "vetshadow", "nakedret", "errcheck", "revive", "ineffassign", + "goconst", "vet", "unparam", "gofmt" +] + +[issues] +exclude-use-default = false + diff --git a/vendor/github.com/invopop/yaml/LICENSE b/vendor/github.com/invopop/yaml/LICENSE new file mode 100644 index 00000000..7805d36d --- /dev/null +++ b/vendor/github.com/invopop/yaml/LICENSE @@ -0,0 +1,50 @@ +The MIT License (MIT) + +Copyright (c) 2014 Sam Ghods + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/invopop/yaml/README.md b/vendor/github.com/invopop/yaml/README.md new file mode 100644 index 00000000..2c33dfe5 --- /dev/null +++ b/vendor/github.com/invopop/yaml/README.md @@ -0,0 +1,128 @@ +# YAML marshaling and unmarshaling support for Go + +[![Lint](https://github.com/invopop/yaml/actions/workflows/lint.yaml/badge.svg)](https://github.com/invopop/yaml/actions/workflows/lint.yaml) +[![Test Go](https://github.com/invopop/yaml/actions/workflows/test.yaml/badge.svg)](https://github.com/invopop/yaml/actions/workflows/test.yaml) +[![Go Report Card](https://goreportcard.com/badge/github.com/invopop/yaml)](https://goreportcard.com/report/github.com/invopop/yaml) +![Latest Tag](https://img.shields.io/github/v/tag/invopop/yaml) + +## Introduction + +A wrapper around [go-yaml](https://github.com/go-yaml/yaml) designed to enable a better way of handling YAML when marshaling to and from structs. + +This is a fork and split of the original [ghodss/yaml](https://github.com/ghodss/yaml) repository which no longer appears to be maintained. + +In short, this library first converts YAML to JSON using go-yaml and then uses `json.Marshal` and `json.Unmarshal` to convert to or from the struct. This means that it effectively reuses the JSON struct tags as well as the custom JSON methods `MarshalJSON` and `UnmarshalJSON` unlike go-yaml. For a detailed overview of the rationale behind this method, [see this blog post](https://web.archive.org/web/20150812020634/http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/). + +## Compatibility + +This package uses [go-yaml](https://github.com/go-yaml/yaml) and therefore supports [everything go-yaml supports](https://github.com/go-yaml/yaml#compatibility). + +Tested against Go versions 1.14 and onwards. + +## Caveats + +**Caveat #1:** When using `yaml.Marshal` and `yaml.Unmarshal`, binary data should NOT be preceded with the `!!binary` YAML tag. If you do, go-yaml will convert the binary data from base64 to native binary data, which is not compatible with JSON. You can still use binary in your YAML files though - just store them without the `!!binary` tag and decode the base64 in your code (e.g. in the custom JSON methods `MarshalJSON` and `UnmarshalJSON`). This also has the benefit that your YAML and your JSON binary data will be decoded exactly the same way. As an example: + +``` +BAD: + exampleKey: !!binary gIGC + +GOOD: + exampleKey: gIGC +... and decode the base64 data in your code. +``` + +**Caveat #2:** When using `YAMLToJSON` directly, maps with keys that are maps will result in an error since this is not supported by JSON. This error will occur in `Unmarshal` as well since you can't unmarshal map keys anyways since struct fields can't be keys. + +## Installation and usage + +To install, run: + +``` +$ go get github.com/invopop/yaml +``` + +And import using: + +``` +import "github.com/invopop/yaml" +``` + +Usage is very similar to the JSON library: + +```go +package main + +import ( + "fmt" + + "github.com/invopop/yaml" +) + +type Person struct { + Name string `json:"name"` // Affects YAML field names too. + Age int `json:"age"` +} + +func main() { + // Marshal a Person struct to YAML. + p := Person{"John", 30} + y, err := yaml.Marshal(p) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(string(y)) + /* Output: + age: 30 + name: John + */ + + // Unmarshal the YAML back into a Person struct. + var p2 Person + err = yaml.Unmarshal(y, &p2) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(p2) + /* Output: + {John 30} + */ +} +``` + +`yaml.YAMLToJSON` and `yaml.JSONToYAML` methods are also available: + +```go +package main + +import ( + "fmt" + + "github.com/invopop/yaml" +) + +func main() { + j := []byte(`{"name": "John", "age": 30}`) + y, err := yaml.JSONToYAML(j) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(string(y)) + /* Output: + name: John + age: 30 + */ + j2, err := yaml.YAMLToJSON(y) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(string(j2)) + /* Output: + {"age":30,"name":"John"} + */ +} +``` diff --git a/vendor/github.com/invopop/yaml/fields.go b/vendor/github.com/invopop/yaml/fields.go new file mode 100644 index 00000000..3fe5f12f --- /dev/null +++ b/vendor/github.com/invopop/yaml/fields.go @@ -0,0 +1,499 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package yaml + +import ( + "bytes" + "encoding" + "encoding/json" + "reflect" + "sort" + "strings" + "sync" + "unicode" + "unicode/utf8" +) + +// indirect walks down v allocating pointers as needed, +// until it gets to a non-pointer. +// if it encounters an Unmarshaler, indirect stops and returns that. +// if decodingNull is true, indirect stops at the last pointer so it can be set to nil. +func indirect(v reflect.Value, decodingNull bool) (json.Unmarshaler, encoding.TextUnmarshaler, reflect.Value) { + // If v is a named type and is addressable, + // start with its address, so that if the type has pointer methods, + // we find them. + if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() { + v = v.Addr() + } + for { + // Load value from interface, but only if the result will be + // usefully addressable. + if v.Kind() == reflect.Interface && !v.IsNil() { + e := v.Elem() + if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) { + v = e + continue + } + } + + if v.Kind() != reflect.Ptr { + break + } + + if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() { + break + } + if v.IsNil() { + if v.CanSet() { + v.Set(reflect.New(v.Type().Elem())) + } else { + v = reflect.New(v.Type().Elem()) + } + } + if v.Type().NumMethod() > 0 { + if u, ok := v.Interface().(json.Unmarshaler); ok { + return u, nil, reflect.Value{} + } + if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { + return nil, u, reflect.Value{} + } + } + v = v.Elem() + } + return nil, nil, v +} + +// A field represents a single field found in a struct. +type field struct { + name string + nameBytes []byte // []byte(name) + equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent + + tag bool + index []int + typ reflect.Type + omitEmpty bool + quoted bool +} + +func fillField(f field) field { + f.nameBytes = []byte(f.name) + f.equalFold = foldFunc(f.nameBytes) + return f +} + +// byName sorts field by name, breaking ties with depth, +// then breaking ties with "name came from json tag", then +// breaking ties with index sequence. +type byName []field + +func (x byName) Len() int { return len(x) } + +func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byName) Less(i, j int) bool { + if x[i].name != x[j].name { + return x[i].name < x[j].name + } + if len(x[i].index) != len(x[j].index) { + return len(x[i].index) < len(x[j].index) + } + if x[i].tag != x[j].tag { + return x[i].tag + } + return byIndex(x).Less(i, j) +} + +// byIndex sorts field by index sequence. +type byIndex []field + +func (x byIndex) Len() int { return len(x) } + +func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byIndex) Less(i, j int) bool { + for k, xik := range x[i].index { + if k >= len(x[j].index) { + return false + } + if xik != x[j].index[k] { + return xik < x[j].index[k] + } + } + return len(x[i].index) < len(x[j].index) +} + +// typeFields returns a list of fields that JSON should recognize for the given type. +// The algorithm is breadth-first search over the set of structs to include - the top struct +// and then any reachable anonymous structs. +func typeFields(t reflect.Type) []field { + // Anonymous fields to explore at the current level and the next. + current := []field{} + next := []field{{typ: t}} + + // Count of queued names for current level and the next. + var count, nextCount map[reflect.Type]int + + // Types already visited at an earlier level. + visited := map[reflect.Type]bool{} + + // Fields found. + var fields []field + + for len(next) > 0 { + current, next = next, current[:0] + count, nextCount = nextCount, map[reflect.Type]int{} + + for _, f := range current { + if visited[f.typ] { + continue + } + visited[f.typ] = true + + // Scan f.typ for fields to include. + for i := 0; i < f.typ.NumField(); i++ { + sf := f.typ.Field(i) + if sf.PkgPath != "" { // unexported + continue + } + tag := sf.Tag.Get("json") + if tag == "-" { + continue + } + name, opts := parseTag(tag) + if !isValidTag(name) { + name = "" + } + index := make([]int, len(f.index)+1) + copy(index, f.index) + index[len(f.index)] = i + + ft := sf.Type + if ft.Name() == "" && ft.Kind() == reflect.Ptr { + // Follow pointer. + ft = ft.Elem() + } + + // Record found field and index sequence. + if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { + tagged := name != "" + if name == "" { + name = sf.Name + } + fields = append(fields, fillField(field{ + name: name, + tag: tagged, + index: index, + typ: ft, + omitEmpty: opts.Contains("omitempty"), + quoted: opts.Contains("string"), + })) + if count[f.typ] > 1 { + // If there were multiple instances, add a second, + // so that the annihilation code will see a duplicate. + // It only cares about the distinction between 1 or 2, + // so don't bother generating any more copies. + fields = append(fields, fields[len(fields)-1]) + } + continue + } + + // Record new anonymous struct to explore in next round. + nextCount[ft]++ + if nextCount[ft] == 1 { + next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft})) + } + } + } + } + + sort.Sort(byName(fields)) + + // Delete all fields that are hidden by the Go rules for embedded fields, + // except that fields with JSON tags are promoted. + + // The fields are sorted in primary order of name, secondary order + // of field index length. Loop over names; for each name, delete + // hidden fields by choosing the one dominant field that survives. + out := fields[:0] + for advance, i := 0, 0; i < len(fields); i += advance { + // One iteration per name. + // Find the sequence of fields with the name of this first field. + fi := fields[i] + name := fi.name + for advance = 1; i+advance < len(fields); advance++ { + fj := fields[i+advance] + if fj.name != name { + break + } + } + if advance == 1 { // Only one field with this name + out = append(out, fi) + continue + } + dominant, ok := dominantField(fields[i : i+advance]) + if ok { + out = append(out, dominant) + } + } + + fields = out + sort.Sort(byIndex(fields)) + + return fields +} + +// dominantField looks through the fields, all of which are known to +// have the same name, to find the single field that dominates the +// others using Go's embedding rules, modified by the presence of +// JSON tags. If there are multiple top-level fields, the boolean +// will be false: This condition is an error in Go and we skip all +// the fields. +func dominantField(fields []field) (field, bool) { + // The fields are sorted in increasing index-length order. The winner + // must therefore be one with the shortest index length. Drop all + // longer entries, which is easy: just truncate the slice. + length := len(fields[0].index) + tagged := -1 // Index of first tagged field. + for i, f := range fields { + if len(f.index) > length { + fields = fields[:i] + break + } + if f.tag { + if tagged >= 0 { + // Multiple tagged fields at the same level: conflict. + // Return no field. + return field{}, false + } + tagged = i + } + } + if tagged >= 0 { + return fields[tagged], true + } + // All remaining fields have the same length. If there's more than one, + // we have a conflict (two fields named "X" at the same level) and we + // return no field. + if len(fields) > 1 { + return field{}, false + } + return fields[0], true +} + +var fieldCache struct { + sync.RWMutex + m map[reflect.Type][]field +} + +// cachedTypeFields is like typeFields but uses a cache to avoid repeated work. +func cachedTypeFields(t reflect.Type) []field { + fieldCache.RLock() + f := fieldCache.m[t] + fieldCache.RUnlock() + if f != nil { + return f + } + + // Compute fields without lock. + // Might duplicate effort but won't hold other computations back. + f = typeFields(t) + if f == nil { + f = []field{} + } + + fieldCache.Lock() + if fieldCache.m == nil { + fieldCache.m = map[reflect.Type][]field{} + } + fieldCache.m[t] = f + fieldCache.Unlock() + return f +} + +func isValidTag(s string) bool { + if s == "" { + return false + } + for _, c := range s { + switch { + case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): + // Backslash and quote chars are reserved, but + // otherwise any punctuation chars are allowed + // in a tag name. + default: + if !unicode.IsLetter(c) && !unicode.IsDigit(c) { + return false + } + } + } + return true +} + +const ( + caseMask = ^byte(0x20) // Mask to ignore case in ASCII. + kelvin = '\u212a' + smallLongEss = '\u017f' +) + +// foldFunc returns one of four different case folding equivalence +// functions, from most general (and slow) to fastest: +// +// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8 +// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S') +// 3) asciiEqualFold, no special, but includes non-letters (including _) +// 4) simpleLetterEqualFold, no specials, no non-letters. +// +// The letters S and K are special because they map to 3 runes, not just 2: +// - S maps to s and to U+017F 'ſ' Latin small letter long s +// - k maps to K and to U+212A 'K' Kelvin sign +// +// See http://play.golang.org/p/tTxjOc0OGo +// +// The returned function is specialized for matching against s and +// should only be given s. It's not curried for performance reasons. +func foldFunc(s []byte) func(s, t []byte) bool { + nonLetter := false + special := false // special letter + for _, b := range s { + if b >= utf8.RuneSelf { + return bytes.EqualFold + } + upper := b & caseMask + if upper < 'A' || upper > 'Z' { + nonLetter = true + } else if upper == 'K' || upper == 'S' { + // See above for why these letters are special. + special = true + } + } + if special { + return equalFoldRight + } + if nonLetter { + return asciiEqualFold + } + return simpleLetterEqualFold +} + +// equalFoldRight is a specialization of bytes.EqualFold when s is +// known to be all ASCII (including punctuation), but contains an 's', +// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t. +// See comments on foldFunc. +func equalFoldRight(s, t []byte) bool { + for _, sb := range s { + if len(t) == 0 { + return false + } + tb := t[0] + if tb < utf8.RuneSelf { + if sb != tb { + sbUpper := sb & caseMask + if 'A' <= sbUpper && sbUpper <= 'Z' { + if sbUpper != tb&caseMask { + return false + } + } else { + return false + } + } + t = t[1:] + continue + } + // sb is ASCII and t is not. t must be either kelvin + // sign or long s; sb must be s, S, k, or K. + tr, size := utf8.DecodeRune(t) + switch sb { + case 's', 'S': + if tr != smallLongEss { + return false + } + case 'k', 'K': + if tr != kelvin { + return false + } + default: + return false + } + t = t[size:] + + } + return len(t) <= 0 +} + +// asciiEqualFold is a specialization of bytes.EqualFold for use when +// s is all ASCII (but may contain non-letters) and contains no +// special-folding letters. +// See comments on foldFunc. +func asciiEqualFold(s, t []byte) bool { + if len(s) != len(t) { + return false + } + for i, sb := range s { + tb := t[i] + if sb == tb { + continue + } + if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') { + if sb&caseMask != tb&caseMask { + return false + } + } else { + return false + } + } + return true +} + +// simpleLetterEqualFold is a specialization of bytes.EqualFold for +// use when s is all ASCII letters (no underscores, etc) and also +// doesn't contain 'k', 'K', 's', or 'S'. +// See comments on foldFunc. +func simpleLetterEqualFold(s, t []byte) bool { + if len(s) != len(t) { + return false + } + for i, b := range s { + if b&caseMask != t[i]&caseMask { + return false + } + } + return true +} + +// tagOptions is the string following a comma in a struct field's "json" +// tag, or the empty string. It does not include the leading comma. +type tagOptions string + +// parseTag splits a struct field's json tag into its name and +// comma-separated options. +func parseTag(tag string) (string, tagOptions) { + if idx := strings.Index(tag, ","); idx != -1 { + return tag[:idx], tagOptions(tag[idx+1:]) + } + return tag, tagOptions("") +} + +// Contains reports whether a comma-separated list of options +// contains a particular substr flag. substr must be surrounded by a +// string boundary or commas. +func (o tagOptions) Contains(optionName string) bool { + if len(o) == 0 { + return false + } + s := string(o) + for s != "" { + var next string + i := strings.Index(s, ",") + if i >= 0 { + s, next = s[:i], s[i+1:] + } + if s == optionName { + return true + } + s = next + } + return false +} diff --git a/vendor/github.com/invopop/yaml/yaml.go b/vendor/github.com/invopop/yaml/yaml.go new file mode 100644 index 00000000..d57dfb10 --- /dev/null +++ b/vendor/github.com/invopop/yaml/yaml.go @@ -0,0 +1,312 @@ +// Package yaml provides a wrapper around go-yaml designed to enable a better +// way of handling YAML when marshaling to and from structs. +// +// In short, this package first converts YAML to JSON using go-yaml and then +// uses json.Marshal and json.Unmarshal to convert to or from the struct. This +// means that it effectively reuses the JSON struct tags as well as the custom +// JSON methods MarshalJSON and UnmarshalJSON unlike go-yaml. +package yaml // import "github.com/invopop/yaml" + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "reflect" + "strconv" + + "gopkg.in/yaml.v3" +) + +// Marshal the object into JSON then converts JSON to YAML and returns the +// YAML. +func Marshal(o interface{}) ([]byte, error) { + j, err := json.Marshal(o) + if err != nil { + return nil, fmt.Errorf("error marshaling into JSON: %v", err) + } + + y, err := JSONToYAML(j) + if err != nil { + return nil, fmt.Errorf("error converting JSON to YAML: %v", err) + } + + return y, nil +} + +// JSONOpt is a decoding option for decoding from JSON format. +type JSONOpt func(*json.Decoder) *json.Decoder + +// Unmarshal converts YAML to JSON then uses JSON to unmarshal into an object, +// optionally configuring the behavior of the JSON unmarshal. +func Unmarshal(y []byte, o interface{}, opts ...JSONOpt) error { + dec := yaml.NewDecoder(bytes.NewReader(y)) + return unmarshal(dec, o, opts) +} + +func unmarshal(dec *yaml.Decoder, o interface{}, opts []JSONOpt) error { + vo := reflect.ValueOf(o) + j, err := yamlToJSON(dec, &vo) + if err != nil { + return fmt.Errorf("error converting YAML to JSON: %v", err) + } + + err = jsonUnmarshal(bytes.NewReader(j), o, opts...) + if err != nil { + return fmt.Errorf("error unmarshaling JSON: %v", err) + } + + return nil +} + +// jsonUnmarshal unmarshals the JSON byte stream from the given reader into the +// object, optionally applying decoder options prior to decoding. We are not +// using json.Unmarshal directly as we want the chance to pass in non-default +// options. +func jsonUnmarshal(r io.Reader, o interface{}, opts ...JSONOpt) error { + d := json.NewDecoder(r) + for _, opt := range opts { + d = opt(d) + } + if err := d.Decode(&o); err != nil { + return fmt.Errorf("while decoding JSON: %v", err) + } + return nil +} + +// JSONToYAML converts JSON to YAML. +func JSONToYAML(j []byte) ([]byte, error) { + // Convert the JSON to an object. + var jsonObj interface{} + // We are using yaml.Unmarshal here (instead of json.Unmarshal) because the + // Go JSON library doesn't try to pick the right number type (int, float, + // etc.) when unmarshalling to interface{}, it just picks float64 + // universally. go-yaml does go through the effort of picking the right + // number type, so we can preserve number type throughout this process. + err := yaml.Unmarshal(j, &jsonObj) + if err != nil { + return nil, err + } + + // Marshal this object into YAML. + return yaml.Marshal(jsonObj) +} + +// YAMLToJSON converts YAML to JSON. Since JSON is a subset of YAML, +// passing JSON through this method should be a no-op. +// +// Things YAML can do that are not supported by JSON: +// - In YAML you can have binary and null keys in your maps. These are invalid +// in JSON. (int and float keys are converted to strings.) +// - Binary data in YAML with the !!binary tag is not supported. If you want to +// use binary data with this library, encode the data as base64 as usual but do +// not use the !!binary tag in your YAML. This will ensure the original base64 +// encoded data makes it all the way through to the JSON. +func YAMLToJSON(y []byte) ([]byte, error) { //nolint:revive + dec := yaml.NewDecoder(bytes.NewReader(y)) + return yamlToJSON(dec, nil) +} + +func yamlToJSON(dec *yaml.Decoder, jsonTarget *reflect.Value) ([]byte, error) { + // Convert the YAML to an object. + var yamlObj interface{} + if err := dec.Decode(&yamlObj); err != nil { + // Functionality changed in v3 which means we need to ignore EOF error. + // See https://github.com/go-yaml/yaml/issues/639 + if !errors.Is(err, io.EOF) { + return nil, err + } + } + + // YAML objects are not completely compatible with JSON objects (e.g. you + // can have non-string keys in YAML). So, convert the YAML-compatible object + // to a JSON-compatible object, failing with an error if irrecoverable + // incompatibilities happen along the way. + jsonObj, err := convertToJSONableObject(yamlObj, jsonTarget) + if err != nil { + return nil, err + } + + // Convert this object to JSON and return the data. + return json.Marshal(jsonObj) +} + +func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (interface{}, error) { //nolint:gocyclo + var err error + + // Resolve jsonTarget to a concrete value (i.e. not a pointer or an + // interface). We pass decodingNull as false because we're not actually + // decoding into the value, we're just checking if the ultimate target is a + // string. + if jsonTarget != nil { + ju, tu, pv := indirect(*jsonTarget, false) + // We have a JSON or Text Umarshaler at this level, so we can't be trying + // to decode into a string. + if ju != nil || tu != nil { + jsonTarget = nil + } else { + jsonTarget = &pv + } + } + + // go-yaml v3 changed from v2 and now will provide map[string]interface{} by + // default and map[interface{}]interface{} when none of the keys strings. + // To get around this, we run a pre-loop to convert the map. + // JSON only supports strings as keys, so we must convert. + + switch typedYAMLObj := yamlObj.(type) { + case map[interface{}]interface{}: + // From my reading of go-yaml v2 (specifically the resolve function), + // keys can only have the types string, int, int64, float64, binary + // (unsupported), or null (unsupported). + strMap := make(map[string]interface{}) + for k, v := range typedYAMLObj { + // Resolve the key to a string first. + var keyString string + switch typedKey := k.(type) { + case string: + keyString = typedKey + case int: + keyString = strconv.Itoa(typedKey) + case int64: + // go-yaml will only return an int64 as a key if the system + // architecture is 32-bit and the key's value is between 32-bit + // and 64-bit. Otherwise the key type will simply be int. + keyString = strconv.FormatInt(typedKey, 10) + case float64: + // Float64 is now supported in keys + keyString = strconv.FormatFloat(typedKey, 'g', -1, 64) + case bool: + if typedKey { + keyString = "true" + } else { + keyString = "false" + } + default: + return nil, fmt.Errorf("unsupported map key of type: %s, key: %+#v, value: %+#v", + reflect.TypeOf(k), k, v) + } + strMap[keyString] = v + } + // replace yamlObj with our new string map + yamlObj = strMap + } + + // If yamlObj is a number or a boolean, check if jsonTarget is a string - + // if so, coerce. Else return normal. + // If yamlObj is a map or array, find the field that each key is + // unmarshaling to, and when you recurse pass the reflect.Value for that + // field back into this function. + switch typedYAMLObj := yamlObj.(type) { + case map[string]interface{}: + for k, v := range typedYAMLObj { + + // jsonTarget should be a struct or a map. If it's a struct, find + // the field it's going to map to and pass its reflect.Value. If + // it's a map, find the element type of the map and pass the + // reflect.Value created from that type. If it's neither, just pass + // nil - JSON conversion will error for us if it's a real issue. + if jsonTarget != nil { + t := *jsonTarget + if t.Kind() == reflect.Struct { + keyBytes := []byte(k) + // Find the field that the JSON library would use. + var f *field + fields := cachedTypeFields(t.Type()) + for i := range fields { + ff := &fields[i] + if bytes.Equal(ff.nameBytes, keyBytes) { + f = ff + break + } + // Do case-insensitive comparison. + if f == nil && ff.equalFold(ff.nameBytes, keyBytes) { + f = ff + } + } + if f != nil { + // Find the reflect.Value of the most preferential + // struct field. + jtf := t.Field(f.index[0]) + typedYAMLObj[k], err = convertToJSONableObject(v, &jtf) + if err != nil { + return nil, err + } + continue + } + } else if t.Kind() == reflect.Map { + // Create a zero value of the map's element type to use as + // the JSON target. + jtv := reflect.Zero(t.Type().Elem()) + typedYAMLObj[k], err = convertToJSONableObject(v, &jtv) + if err != nil { + return nil, err + } + continue + } + } + typedYAMLObj[k], err = convertToJSONableObject(v, nil) + if err != nil { + return nil, err + } + } + return typedYAMLObj, nil + case []interface{}: + // We need to recurse into arrays in case there are any + // map[interface{}]interface{}'s inside and to convert any + // numbers to strings. + + // If jsonTarget is a slice (which it really should be), find the + // thing it's going to map to. If it's not a slice, just pass nil + // - JSON conversion will error for us if it's a real issue. + var jsonSliceElemValue *reflect.Value + if jsonTarget != nil { + t := *jsonTarget + if t.Kind() == reflect.Slice { + // By default slices point to nil, but we need a reflect.Value + // pointing to a value of the slice type, so we create one here. + ev := reflect.Indirect(reflect.New(t.Type().Elem())) + jsonSliceElemValue = &ev + } + } + + // Make and use a new array. + arr := make([]interface{}, len(typedYAMLObj)) + for i, v := range typedYAMLObj { + arr[i], err = convertToJSONableObject(v, jsonSliceElemValue) + if err != nil { + return nil, err + } + } + return arr, nil + default: + // If the target type is a string and the YAML type is a number, + // convert the YAML type to a string. + if jsonTarget != nil && (*jsonTarget).Kind() == reflect.String { + // Based on my reading of go-yaml, it may return int, int64, + // float64, or uint64. + var s string + switch typedVal := typedYAMLObj.(type) { + case int: + s = strconv.FormatInt(int64(typedVal), 10) + case int64: + s = strconv.FormatInt(typedVal, 10) + case float64: + s = strconv.FormatFloat(typedVal, 'g', -1, 64) + case uint64: + s = strconv.FormatUint(typedVal, 10) + case bool: + if typedVal { + s = "true" + } else { + s = "false" + } + } + if len(s) > 0 { + yamlObj = interface{}(s) + } + } + return yamlObj, nil + } +} diff --git a/vendor/github.com/josharian/intern/README.md b/vendor/github.com/josharian/intern/README.md new file mode 100644 index 00000000..ffc44b21 --- /dev/null +++ b/vendor/github.com/josharian/intern/README.md @@ -0,0 +1,5 @@ +Docs: https://godoc.org/github.com/josharian/intern + +See also [Go issue 5160](https://golang.org/issue/5160). + +License: MIT diff --git a/vendor/github.com/josharian/intern/intern.go b/vendor/github.com/josharian/intern/intern.go new file mode 100644 index 00000000..7acb1fe9 --- /dev/null +++ b/vendor/github.com/josharian/intern/intern.go @@ -0,0 +1,44 @@ +// Package intern interns strings. +// Interning is best effort only. +// Interned strings may be removed automatically +// at any time without notification. +// All functions may be called concurrently +// with themselves and each other. +package intern + +import "sync" + +var ( + pool sync.Pool = sync.Pool{ + New: func() interface{} { + return make(map[string]string) + }, + } +) + +// String returns s, interned. +func String(s string) string { + m := pool.Get().(map[string]string) + c, ok := m[s] + if ok { + pool.Put(m) + return c + } + m[s] = s + pool.Put(m) + return s +} + +// Bytes returns b converted to a string, interned. +func Bytes(b []byte) string { + m := pool.Get().(map[string]string) + c, ok := m[string(b)] + if ok { + pool.Put(m) + return c + } + s := string(b) + m[s] = s + pool.Put(m) + return s +} diff --git a/vendor/github.com/josharian/intern/license.md b/vendor/github.com/josharian/intern/license.md new file mode 100644 index 00000000..353d3055 --- /dev/null +++ b/vendor/github.com/josharian/intern/license.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Josh Bleecher Snyder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/labstack/echo/v4/.editorconfig b/vendor/github.com/labstack/echo/v4/.editorconfig new file mode 100644 index 00000000..17ae50dd --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/.editorconfig @@ -0,0 +1,25 @@ +# EditorConfig coding styles definitions. For more information about the +# properties used in this file, please see the EditorConfig documentation: +# http://editorconfig.org/ + +# indicate this is the root of the project +root = true + +[*] +charset = utf-8 + +end_of_line = LF +insert_final_newline = true +trim_trailing_whitespace = true + +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false + +[*.go] +indent_style = tab diff --git a/vendor/github.com/labstack/echo/v4/.gitattributes b/vendor/github.com/labstack/echo/v4/.gitattributes new file mode 100644 index 00000000..49b63e52 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/.gitattributes @@ -0,0 +1,20 @@ +# Automatically normalize line endings for all text-based files +# http://git-scm.com/docs/gitattributes#_end_of_line_conversion +* text=auto + +# For the following file types, normalize line endings to LF on checking and +# prevent conversion to CRLF when they are checked out (this is required in +# order to prevent newline related issues) +.* text eol=lf +*.go text eol=lf +*.yml text eol=lf +*.html text eol=lf +*.css text eol=lf +*.js text eol=lf +*.json text eol=lf +LICENSE text eol=lf + +# Exclude `website` and `cookbook` from GitHub's language statistics +# https://github.com/github/linguist#using-gitattributes +cookbook/* linguist-documentation +website/* linguist-documentation diff --git a/vendor/github.com/labstack/echo/v4/.gitignore b/vendor/github.com/labstack/echo/v4/.gitignore new file mode 100644 index 00000000..dbadf3bd --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +coverage.txt +_test +vendor +.idea +*.iml +*.out +.vscode diff --git a/vendor/github.com/labstack/echo/v4/CHANGELOG.md b/vendor/github.com/labstack/echo/v4/CHANGELOG.md new file mode 100644 index 00000000..4e88f8ab --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/CHANGELOG.md @@ -0,0 +1,538 @@ +# Changelog + +## v4.13.3 - 2024-12-19 + +**Security** + +* Update golang.org/x/net dependency [GO-2024-3333](https://pkg.go.dev/vuln/GO-2024-3333) in https://github.com/labstack/echo/pull/2722 + + +## v4.13.2 - 2024-12-12 + +**Security** + +* Update dependencies (dependabot reports [GO-2024-3321](https://pkg.go.dev/vuln/GO-2024-3321)) in https://github.com/labstack/echo/pull/2721 + + +## v4.13.1 - 2024-12-11 + +**Fixes** + +* Fix BindBody ignoring `Transfer-Encoding: chunked` requests by @178inaba in https://github.com/labstack/echo/pull/2717 + + + +## v4.13.0 - 2024-12-04 + +**BREAKING CHANGE** JWT Middleware Removed from Core use [labstack/echo-jwt](https://github.com/labstack/echo-jwt) instead + +The JWT middleware has been **removed from Echo core** due to another security vulnerability, [CVE-2024-51744](https://nvd.nist.gov/vuln/detail/CVE-2024-51744). For more details, refer to issue [#2699](https://github.com/labstack/echo/issues/2699). A drop-in replacement is available in the [labstack/echo-jwt](https://github.com/labstack/echo-jwt) repository. + +**Important**: Direct assignments like `token := c.Get("user").(*jwt.Token)` will now cause a panic due to an invalid cast. Update your code accordingly. Replace the current imports from `"github.com/golang-jwt/jwt"` in your handlers to the new middleware version using `"github.com/golang-jwt/jwt/v5"`. + + +Background: + +The version of `golang-jwt/jwt` (v3.2.2) previously used in Echo core has been in an unmaintained state for some time. This is not the first vulnerability affecting this library; earlier issues were addressed in [PR #1946](https://github.com/labstack/echo/pull/1946). +JWT middleware was marked as deprecated in Echo core as of [v4.10.0](https://github.com/labstack/echo/releases/tag/v4.10.0) on 2022-12-27. If you did not notice that, consider leveraging tools like [Staticcheck](https://staticcheck.dev/) to catch such deprecations earlier in you dev/CI flow. For bonus points - check out [gosec](https://github.com/securego/gosec). + +We sincerely apologize for any inconvenience caused by this change. While we strive to maintain backward compatibility within Echo core, recurring security issues with third-party dependencies have forced this decision. + +**Enhancements** + +* remove jwt middleware by @stevenwhitehead in https://github.com/labstack/echo/pull/2701 +* optimization: struct alignment by @behnambm in https://github.com/labstack/echo/pull/2636 +* bind: Maintain backwards compatibility for map[string]interface{} binding by @thesaltree in https://github.com/labstack/echo/pull/2656 +* Add Go 1.23 to CI by @aldas in https://github.com/labstack/echo/pull/2675 +* improve `MultipartForm` test by @martinyonatann in https://github.com/labstack/echo/pull/2682 +* `bind` : add support of multipart multi files by @martinyonatann in https://github.com/labstack/echo/pull/2684 +* Add TemplateRenderer struct to ease creating renderers for `html/template` and `text/template` packages. by @aldas in https://github.com/labstack/echo/pull/2690 +* Refactor TestBasicAuth to utilize table-driven test format by @ErikOlson in https://github.com/labstack/echo/pull/2688 +* Remove broken header by @aldas in https://github.com/labstack/echo/pull/2705 +* fix(bind body): content-length can be -1 by @phamvinhdat in https://github.com/labstack/echo/pull/2710 +* CORS middleware should compile allowOrigin regexp at creation by @aldas in https://github.com/labstack/echo/pull/2709 +* Shorten Github issue template and add test example by @aldas in https://github.com/labstack/echo/pull/2711 + + +## v4.12.0 - 2024-04-15 + +**Security** + +* Update golang.org/x/net dep because of [GO-2024-2687](https://pkg.go.dev/vuln/GO-2024-2687) by @aldas in https://github.com/labstack/echo/pull/2625 + + +**Enhancements** + +* binder: make binding to Map work better with string destinations by @aldas in https://github.com/labstack/echo/pull/2554 +* README.md: add Encore as sponsor by @marcuskohlberg in https://github.com/labstack/echo/pull/2579 +* Reorder paragraphs in README.md by @aldas in https://github.com/labstack/echo/pull/2581 +* CI: upgrade actions/checkout to v4 by @aldas in https://github.com/labstack/echo/pull/2584 +* Remove default charset from 'application/json' Content-Type header by @doortts in https://github.com/labstack/echo/pull/2568 +* CI: Use Go 1.22 by @aldas in https://github.com/labstack/echo/pull/2588 +* binder: allow binding to a nil map by @georgmu in https://github.com/labstack/echo/pull/2574 +* Add Skipper Unit Test In BasicBasicAuthConfig and Add More Detail Explanation regarding BasicAuthValidator by @RyoKusnadi in https://github.com/labstack/echo/pull/2461 +* fix some typos by @teslaedison in https://github.com/labstack/echo/pull/2603 +* fix: some typos by @pomadev in https://github.com/labstack/echo/pull/2596 +* Allow ResponseWriters to unwrap writers when flushing/hijacking by @aldas in https://github.com/labstack/echo/pull/2595 +* Add SPDX licence comments to files. by @aldas in https://github.com/labstack/echo/pull/2604 +* Upgrade deps by @aldas in https://github.com/labstack/echo/pull/2605 +* Change type definition blocks to single declarations. This helps copy… by @aldas in https://github.com/labstack/echo/pull/2606 +* Fix Real IP logic by @cl-bvl in https://github.com/labstack/echo/pull/2550 +* Default binder can use `UnmarshalParams(params []string) error` inter… by @aldas in https://github.com/labstack/echo/pull/2607 +* Default binder can bind pointer to slice as struct field. For example `*[]string` by @aldas in https://github.com/labstack/echo/pull/2608 +* Remove maxparam dependence from Context by @aldas in https://github.com/labstack/echo/pull/2611 +* When route is registered with empty path it is normalized to `/`. by @aldas in https://github.com/labstack/echo/pull/2616 +* proxy middleware should use httputil.ReverseProxy for SSE requests by @aldas in https://github.com/labstack/echo/pull/2624 + + +## v4.11.4 - 2023-12-20 + +**Security** + +* Upgrade golang.org/x/crypto to v0.17.0 to fix vulnerability [issue](https://pkg.go.dev/vuln/GO-2023-2402) [#2562](https://github.com/labstack/echo/pull/2562) + +**Enhancements** + +* Update deps and mark Go version to 1.18 as this is what golang.org/x/* use [#2563](https://github.com/labstack/echo/pull/2563) +* Request logger: add example for Slog https://pkg.go.dev/log/slog [#2543](https://github.com/labstack/echo/pull/2543) + + +## v4.11.3 - 2023-11-07 + +**Security** + +* 'c.Attachment' and 'c.Inline' should escape filename in 'Content-Disposition' header to avoid 'Reflect File Download' vulnerability. [#2541](https://github.com/labstack/echo/pull/2541) + +**Enhancements** + +* Tests: refactor context tests to be separate functions [#2540](https://github.com/labstack/echo/pull/2540) +* Proxy middleware: reuse echo request context [#2537](https://github.com/labstack/echo/pull/2537) +* Mark unmarshallable yaml struct tags as ignored [#2536](https://github.com/labstack/echo/pull/2536) + + +## v4.11.2 - 2023-10-11 + +**Security** + +* Bump golang.org/x/net to prevent CVE-2023-39325 / CVE-2023-44487 HTTP/2 Rapid Reset Attack [#2527](https://github.com/labstack/echo/pull/2527) +* fix(sec): randomString bias introduced by #2490 [#2492](https://github.com/labstack/echo/pull/2492) +* CSRF/RequestID mw: switch math/random usage to crypto/random [#2490](https://github.com/labstack/echo/pull/2490) + +**Enhancements** + +* Delete unused context in body_limit.go [#2483](https://github.com/labstack/echo/pull/2483) +* Use Go 1.21 in CI [#2505](https://github.com/labstack/echo/pull/2505) +* Fix some typos [#2511](https://github.com/labstack/echo/pull/2511) +* Allow CORS middleware to send Access-Control-Max-Age: 0 [#2518](https://github.com/labstack/echo/pull/2518) +* Bump dependancies [#2522](https://github.com/labstack/echo/pull/2522) + +## v4.11.1 - 2023-07-16 + +**Fixes** + +* Fix `Gzip` middleware not sending response code for no content responses (404, 301/302 redirects etc) [#2481](https://github.com/labstack/echo/pull/2481) + + +## v4.11.0 - 2023-07-14 + + +**Fixes** + +* Fixes the proxy middleware concurrency issue of calling the Next() proxy target on Round Robin Balancer [#2409](https://github.com/labstack/echo/pull/2409) +* Fix `group.RouteNotFound` not working when group has attached middlewares [#2411](https://github.com/labstack/echo/pull/2411) +* Fix global error handler return error message when message is an error [#2456](https://github.com/labstack/echo/pull/2456) +* Do not use global timeNow variables [#2477](https://github.com/labstack/echo/pull/2477) + + +**Enhancements** + +* Added a optional config variable to disable centralized error handler in recovery middleware [#2410](https://github.com/labstack/echo/pull/2410) +* refactor: use `strings.ReplaceAll` directly [#2424](https://github.com/labstack/echo/pull/2424) +* Add support for Go1.20 `http.rwUnwrapper` to Response struct [#2425](https://github.com/labstack/echo/pull/2425) +* Check whether is nil before invoking centralized error handling [#2429](https://github.com/labstack/echo/pull/2429) +* Proper colon support in `echo.Reverse` method [#2416](https://github.com/labstack/echo/pull/2416) +* Fix misuses of a vs an in documentation comments [#2436](https://github.com/labstack/echo/pull/2436) +* Add link to slog.Handler library for Echo logging into README.md [#2444](https://github.com/labstack/echo/pull/2444) +* In proxy middleware Support retries of failed proxy requests [#2414](https://github.com/labstack/echo/pull/2414) +* gofmt fixes to comments [#2452](https://github.com/labstack/echo/pull/2452) +* gzip response only if it exceeds a minimal length [#2267](https://github.com/labstack/echo/pull/2267) +* Upgrade packages [#2475](https://github.com/labstack/echo/pull/2475) + + +## v4.10.2 - 2023-02-22 + +**Security** + +* `filepath.Clean` behaviour has changed in Go 1.20 - adapt to it [#2406](https://github.com/labstack/echo/pull/2406) +* Add `middleware.CORSConfig.UnsafeWildcardOriginWithAllowCredentials` to make UNSAFE usages of wildcard origin + allow cretentials less likely [#2405](https://github.com/labstack/echo/pull/2405) + +**Enhancements** + +* Add more HTTP error values [#2277](https://github.com/labstack/echo/pull/2277) + + +## v4.10.1 - 2023-02-19 + +**Security** + +* Upgrade deps due to the latest golang.org/x/net vulnerability [#2402](https://github.com/labstack/echo/pull/2402) + + +**Enhancements** + +* Add new JWT repository to the README [#2377](https://github.com/labstack/echo/pull/2377) +* Return an empty string for ctx.path if there is no registered path [#2385](https://github.com/labstack/echo/pull/2385) +* Add context timeout middleware [#2380](https://github.com/labstack/echo/pull/2380) +* Update link to jaegertracing [#2394](https://github.com/labstack/echo/pull/2394) + + +## v4.10.0 - 2022-12-27 + +**Security** + +* We are deprecating JWT middleware in this repository. Please use https://github.com/labstack/echo-jwt instead. + + JWT middleware is moved to separate repository to allow us to bump/upgrade version of JWT implementation (`github.com/golang-jwt/jwt`) we are using +which we can not do in Echo core because this would break backwards compatibility guarantees we try to maintain. + +* This minor version bumps minimum Go version to 1.17 (from 1.16) due `golang.org/x/` packages we depend on. There are + several vulnerabilities fixed in these libraries. + + Echo still tries to support last 4 Go versions but there are occasions we can not guarantee this promise. + + +**Enhancements** + +* Bump x/text to 0.3.8 [#2305](https://github.com/labstack/echo/pull/2305) +* Bump dependencies and add notes about Go releases we support [#2336](https://github.com/labstack/echo/pull/2336) +* Add helper interface for ProxyBalancer interface [#2316](https://github.com/labstack/echo/pull/2316) +* Expose `middleware.CreateExtractors` function so we can use it from echo-contrib repository [#2338](https://github.com/labstack/echo/pull/2338) +* Refactor func(Context) error to HandlerFunc [#2315](https://github.com/labstack/echo/pull/2315) +* Improve function comments [#2329](https://github.com/labstack/echo/pull/2329) +* Add new method HTTPError.WithInternal [#2340](https://github.com/labstack/echo/pull/2340) +* Replace io/ioutil package usages [#2342](https://github.com/labstack/echo/pull/2342) +* Add staticcheck to CI flow [#2343](https://github.com/labstack/echo/pull/2343) +* Replace relative path determination from proprietary to std [#2345](https://github.com/labstack/echo/pull/2345) +* Remove square brackets from ipv6 addresses in XFF (X-Forwarded-For header) [#2182](https://github.com/labstack/echo/pull/2182) +* Add testcases for some BodyLimit middleware configuration options [#2350](https://github.com/labstack/echo/pull/2350) +* Additional configuration options for RequestLogger and Logger middleware [#2341](https://github.com/labstack/echo/pull/2341) +* Add route to request log [#2162](https://github.com/labstack/echo/pull/2162) +* GitHub Workflows security hardening [#2358](https://github.com/labstack/echo/pull/2358) +* Add govulncheck to CI and bump dependencies [#2362](https://github.com/labstack/echo/pull/2362) +* Fix rate limiter docs [#2366](https://github.com/labstack/echo/pull/2366) +* Refactor how `e.Routes()` work and introduce `e.OnAddRouteHandler` callback [#2337](https://github.com/labstack/echo/pull/2337) + + +## v4.9.1 - 2022-10-12 + +**Fixes** + +* Fix logger panicing (when template is set to empty) by bumping dependency version [#2295](https://github.com/labstack/echo/issues/2295) + +**Enhancements** + +* Improve CORS documentation [#2272](https://github.com/labstack/echo/pull/2272) +* Update readme about supported Go versions [#2291](https://github.com/labstack/echo/pull/2291) +* Tests: improve error handling on closing body [#2254](https://github.com/labstack/echo/pull/2254) +* Tests: refactor some of the assertions in tests [#2275](https://github.com/labstack/echo/pull/2275) +* Tests: refactor assertions [#2301](https://github.com/labstack/echo/pull/2301) + +## v4.9.0 - 2022-09-04 + +**Security** + +* Fix open redirect vulnerability in handlers serving static directories (e.Static, e.StaticFs, echo.StaticDirectoryHandler) [#2260](https://github.com/labstack/echo/pull/2260) + +**Enhancements** + +* Allow configuring ErrorHandler in CSRF middleware [#2257](https://github.com/labstack/echo/pull/2257) +* Replace HTTP method constants in tests with stdlib constants [#2247](https://github.com/labstack/echo/pull/2247) + + +## v4.8.0 - 2022-08-10 + +**Most notable things** + +You can now add any arbitrary HTTP method type as a route [#2237](https://github.com/labstack/echo/pull/2237) +```go +e.Add("COPY", "/*", func(c echo.Context) error + return c.String(http.StatusOK, "OK COPY") +}) +``` + +You can add custom 404 handler for specific paths [#2217](https://github.com/labstack/echo/pull/2217) +```go +e.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) }) + +g := e.Group("/images") +g.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) }) +``` + +**Enhancements** + +* Add new value binding methods (UnixTimeMilli,TextUnmarshaler,JSONUnmarshaler) to Valuebinder [#2127](https://github.com/labstack/echo/pull/2127) +* Refactor: body_limit middleware unit test [#2145](https://github.com/labstack/echo/pull/2145) +* Refactor: Timeout mw: rework how test waits for timeout. [#2187](https://github.com/labstack/echo/pull/2187) +* BasicAuth middleware returns 500 InternalServerError on invalid base64 strings but should return 400 [#2191](https://github.com/labstack/echo/pull/2191) +* Refactor: duplicated findStaticChild process at findChildWithLabel [#2176](https://github.com/labstack/echo/pull/2176) +* Allow different param names in different methods with same path scheme [#2209](https://github.com/labstack/echo/pull/2209) +* Add support for registering handlers for different 404 routes [#2217](https://github.com/labstack/echo/pull/2217) +* Middlewares should use errors.As() instead of type assertion on HTTPError [#2227](https://github.com/labstack/echo/pull/2227) +* Allow arbitrary HTTP method types to be added as routes [#2237](https://github.com/labstack/echo/pull/2237) + +## v4.7.2 - 2022-03-16 + +**Fixes** + +* Fix nil pointer exception when calling Start again after address binding error [#2131](https://github.com/labstack/echo/pull/2131) +* Fix CSRF middleware not being able to extract token from multipart/form-data form [#2136](https://github.com/labstack/echo/pull/2136) +* Fix Timeout middleware write race [#2126](https://github.com/labstack/echo/pull/2126) + +**Enhancements** + +* Recover middleware should not log panic for aborted handler [#2134](https://github.com/labstack/echo/pull/2134) + + +## v4.7.1 - 2022-03-13 + +**Fixes** + +* Fix `e.Static`, `.File()`, `c.Attachment()` being picky with paths starting with `./`, `../` and `/` after 4.7.0 introduced echo.Filesystem support (Go1.16+) [#2123](https://github.com/labstack/echo/pull/2123) + +**Enhancements** + +* Remove some unused code [#2116](https://github.com/labstack/echo/pull/2116) + + +## v4.7.0 - 2022-03-01 + +**Enhancements** + +* Add JWT, KeyAuth, CSRF multivalue extractors [#2060](https://github.com/labstack/echo/pull/2060) +* Add LogErrorFunc to recover middleware [#2072](https://github.com/labstack/echo/pull/2072) +* Add support for HEAD method query params binding [#2027](https://github.com/labstack/echo/pull/2027) +* Improve filesystem support with echo.FileFS, echo.StaticFS, group.FileFS, group.StaticFS [#2064](https://github.com/labstack/echo/pull/2064) + +**Fixes** + +* Fix X-Real-IP bug, improve tests [#2007](https://github.com/labstack/echo/pull/2007) +* Minor syntax fixes [#1994](https://github.com/labstack/echo/pull/1994), [#2102](https://github.com/labstack/echo/pull/2102), [#2102](https://github.com/labstack/echo/pull/2102) + +**General** + +* Add cache-control and connection headers [#2103](https://github.com/labstack/echo/pull/2103) +* Add Retry-After header constant [#2078](https://github.com/labstack/echo/pull/2078) +* Upgrade `go` directive in `go.mod` to 1.17 [#2049](https://github.com/labstack/echo/pull/2049) +* Add Pagoda [#2077](https://github.com/labstack/echo/pull/2077) and Souin [#2069](https://github.com/labstack/echo/pull/2069) to 3rd-party middlewares in README + +## v4.6.3 - 2022-01-10 + +**Fixes** + +* Fixed Echo version number in greeting message which was not incremented to `4.6.2` [#2066](https://github.com/labstack/echo/issues/2066) + + +## v4.6.2 - 2022-01-08 + +**Fixes** + +* Fixed route containing escaped colon should be matchable but is not matched to request path [#2047](https://github.com/labstack/echo/pull/2047) +* Fixed a problem that returned wrong content-encoding when the gzip compressed content was empty. [#1921](https://github.com/labstack/echo/pull/1921) +* Update (test) dependencies [#2021](https://github.com/labstack/echo/pull/2021) + + +**Enhancements** + +* Add support for configurable target header for the request_id middleware [#2040](https://github.com/labstack/echo/pull/2040) +* Change decompress middleware to use stream decompression instead of buffering [#2018](https://github.com/labstack/echo/pull/2018) +* Documentation updates + + +## v4.6.1 - 2021-09-26 + +**Enhancements** + +* Add start time to request logger middleware values [#1991](https://github.com/labstack/echo/pull/1991) + +## v4.6.0 - 2021-09-20 + +Introduced a new [request logger](https://github.com/labstack/echo/blob/master/middleware/request_logger.go) middleware +to help with cases when you want to use some other logging library in your application. + +**Fixes** + +* fix timeout middleware warning: superfluous response.WriteHeader [#1905](https://github.com/labstack/echo/issues/1905) + +**Enhancements** + +* Add Cookie to KeyAuth middleware's KeyLookup [#1929](https://github.com/labstack/echo/pull/1929) +* JWT middleware should ignore case of auth scheme in request header [#1951](https://github.com/labstack/echo/pull/1951) +* Refactor default error handler to return first if response is already committed [#1956](https://github.com/labstack/echo/pull/1956) +* Added request logger middleware which helps to use custom logger library for logging requests. [#1980](https://github.com/labstack/echo/pull/1980) +* Allow escaping of colon in route path so Google Cloud API "custom methods" could be implemented [#1988](https://github.com/labstack/echo/pull/1988) + +## v4.5.0 - 2021-08-01 + +**Important notes** + +A **BREAKING CHANGE** is introduced for JWT middleware users. +The JWT library used for the JWT middleware had to be changed from [github.com/dgrijalva/jwt-go](https://github.com/dgrijalva/jwt-go) to +[github.com/golang-jwt/jwt](https://github.com/golang-jwt/jwt) due former library being unmaintained and affected by security +issues. +The [github.com/golang-jwt/jwt](https://github.com/golang-jwt/jwt) project is a drop-in replacement, but supports only the latest 2 Go versions. +So for JWT middleware users Go 1.15+ is required. For detailed information please read [#1940](https://github.com/labstack/echo/discussions/) + +To change the library imports in all .go files in your project replace all occurrences of `dgrijalva/jwt-go` with `golang-jwt/jwt`. + +For Linux CLI you can use: +```bash +find -type f -name "*.go" -exec sed -i "s/dgrijalva\/jwt-go/golang-jwt\/jwt/g" {} \; +go mod tidy +``` + +**Fixes** + +* Change JWT library to `github.com/golang-jwt/jwt` [#1946](https://github.com/labstack/echo/pull/1946) + +## v4.4.0 - 2021-07-12 + +**Fixes** + +* Split HeaderXForwardedFor header only by comma [#1878](https://github.com/labstack/echo/pull/1878) +* Fix Timeout middleware Context propagation [#1910](https://github.com/labstack/echo/pull/1910) + +**Enhancements** + +* Bind data using headers as source [#1866](https://github.com/labstack/echo/pull/1866) +* Adds JWTConfig.ParseTokenFunc to JWT middleware to allow different libraries implementing JWT parsing. [#1887](https://github.com/labstack/echo/pull/1887) +* Adding tests for Echo#Host [#1895](https://github.com/labstack/echo/pull/1895) +* Adds RequestIDHandler function to RequestID middleware [#1898](https://github.com/labstack/echo/pull/1898) +* Allow for custom JSON encoding implementations [#1880](https://github.com/labstack/echo/pull/1880) + +## v4.3.0 - 2021-05-08 + +**Important notes** + +* Route matching has improvements for following cases: + 1. Correctly match routes with parameter part as last part of route (with trailing backslash) + 2. Considering handlers when resolving routes and search for matching http method handler +* Echo minimal Go version is now 1.13. + +**Fixes** + +* When url ends with slash first param route is the match [#1804](https://github.com/labstack/echo/pull/1812) +* Router should check if node is suitable as matching route by path+method and if not then continue search in tree [#1808](https://github.com/labstack/echo/issues/1808) +* Fix timeout middleware not writing response correctly when handler panics [#1864](https://github.com/labstack/echo/pull/1864) +* Fix binder not working with embedded pointer structs [#1861](https://github.com/labstack/echo/pull/1861) +* Add Go 1.16 to CI and drop 1.12 specific code [#1850](https://github.com/labstack/echo/pull/1850) + +**Enhancements** + +* Make KeyFunc public in JWT middleware [#1756](https://github.com/labstack/echo/pull/1756) +* Add support for optional filesystem to the static middleware [#1797](https://github.com/labstack/echo/pull/1797) +* Add a custom error handler to key-auth middleware [#1847](https://github.com/labstack/echo/pull/1847) +* Allow JWT token to be looked up from multiple sources [#1845](https://github.com/labstack/echo/pull/1845) + +## v4.2.2 - 2021-04-07 + +**Fixes** + +* Allow proxy middleware to use query part in rewrite (#1802) +* Fix timeout middleware not sending status code when handler returns an error (#1805) +* Fix Bind() when target is array/slice and path/query params complains bind target not being struct (#1835) +* Fix panic in redirect middleware on short host name (#1813) +* Fix timeout middleware docs (#1836) + +## v4.2.1 - 2021-03-08 + +**Important notes** + +Due to a datarace the config parameters for the newly added timeout middleware required a change. +See the [docs](https://echo.labstack.com/middleware/timeout). +A performance regression has been fixed, even bringing better performance than before for some routing scenarios. + +**Fixes** + +* Fix performance regression caused by path escaping (#1777, #1798, #1799, aldas) +* Avoid context canceled errors (#1789, clwluvw) +* Improve router to use on stack backtracking (#1791, aldas, stffabi) +* Fix panic in timeout middleware not being not recovered and cause application crash (#1794, aldas) +* Fix Echo.Serve() not serving on HTTP port correctly when TLSListener is used (#1785, #1793, aldas) +* Apply go fmt (#1788, Le0tk0k) +* Uses strings.Equalfold (#1790, rkilingr) +* Improve code quality (#1792, withshubh) + +This release was made possible by our **contributors**: +aldas, clwluvw, lammel, Le0tk0k, maciej-jezierski, rkilingr, stffabi, withshubh + +## v4.2.0 - 2021-02-11 + +**Important notes** + +The behaviour for binding data has been reworked for compatibility with echo before v4.1.11 by +enforcing `explicit tagging` for processing parameters. This **may break** your code if you +expect combined handling of query/path/form params. +Please see the updated documentation for [request](https://echo.labstack.com/guide/request) and [binding](https://echo.labstack.com/guide/request) + +The handling for rewrite rules has been slightly adjusted to expand `*` to a non-greedy `(.*?)` capture group. This is only relevant if multiple asterisks are used in your rules. +Please see [rewrite](https://echo.labstack.com/middleware/rewrite) and [proxy](https://echo.labstack.com/middleware/proxy) for details. + +**Security** + +* Fix directory traversal vulnerability for Windows (#1718, little-cui) +* Fix open redirect vulnerability with trailing slash (#1771,#1775 aldas,GeoffreyFrogeye) + +**Enhancements** + +* Add Echo#ListenerNetwork as configuration (#1667, pafuent) +* Add ability to change the status code using response beforeFuncs (#1706, RashadAnsari) +* Echo server startup to allow data race free access to listener address +* Binder: Restore pre v4.1.11 behaviour for c.Bind() to use query params only for GET or DELETE methods (#1727, aldas) +* Binder: Add separate methods to bind only query params, path params or request body (#1681, aldas) +* Binder: New fluent binder for query/path/form parameter binding (#1717, #1736, aldas) +* Router: Performance improvements for missed routes (#1689, pafuent) +* Router: Improve performance for Real-IP detection using IndexByte instead of Split (#1640, imxyb) +* Middleware: Support real regex rules for rewrite and proxy middleware (#1767) +* Middleware: New rate limiting middleware (#1724, iambenkay) +* Middleware: New timeout middleware implementation for go1.13+ (#1743, ) +* Middleware: Allow regex pattern for CORS middleware (#1623, KlotzAndrew) +* Middleware: Add IgnoreBase parameter to static middleware (#1701, lnenad, iambenkay) +* Middleware: Add an optional custom function to CORS middleware to validate origin (#1651, curvegrid) +* Middleware: Support form fields in JWT middleware (#1704, rkfg) +* Middleware: Use sync.Pool for (de)compress middleware to improve performance (#1699, #1672, pafuent) +* Middleware: Add decompress middleware to support gzip compressed requests (#1687, arun0009) +* Middleware: Add ErrJWTInvalid for JWT middleware (#1627, juanbelieni) +* Middleware: Add SameSite mode for CSRF cookies to support iframes (#1524, pr0head) + +**Fixes** + +* Fix handling of special trailing slash case for partial prefix (#1741, stffabi) +* Fix handling of static routes with trailing slash (#1747) +* Fix Static files route not working (#1671, pwli0755, lammel) +* Fix use of caret(^) in regex for rewrite middleware (#1588, chotow) +* Fix Echo#Reverse for Any type routes (#1695, pafuent) +* Fix Router#Find panic with infinite loop (#1661, pafuent) +* Fix Router#Find panic fails on Param paths (#1659, pafuent) +* Fix DefaultHTTPErrorHandler with Debug=true (#1477, lammel) +* Fix incorrect CORS headers (#1669, ulasakdeniz) +* Fix proxy middleware rewritePath to use url with updated tests (#1630, arun0009) +* Fix rewritePath for proxy middleware to use escaped path in (#1628, arun0009) +* Remove unless defer (#1656, imxyb) + +**General** + +* New maintainers for Echo: Roland Lammel (@lammel) and Pablo Andres Fuente (@pafuent) +* Add GitHub action to compare benchmarks (#1702, pafuent) +* Binding query/path params and form fields to struct only works for explicit tags (#1729,#1734, aldas) +* Add support for Go 1.15 in CI (#1683, asahasrabuddhe) +* Add test for request id to remain unchanged if provided (#1719, iambenkay) +* Refactor echo instance listener access and startup to speed up testing (#1735, aldas) +* Refactor and improve various tests for binding and routing +* Run test workflow only for relevant changes (#1637, #1636, pofl) +* Update .travis.yml (#1662, santosh653) +* Update README.md with an recents framework benchmark (#1679, pafuent) + +This release was made possible by **over 100 commits** from more than **20 contributors**: +asahasrabuddhe, aldas, AndrewKlotz, arun0009, chotow, curvegrid, iambenkay, imxyb, +juanbelieni, lammel, little-cui, lnenad, pafuent, pofl, pr0head, pwli, RashadAnsari, +rkfg, santosh653, segfiner, stffabi, ulasakdeniz diff --git a/vendor/github.com/labstack/echo/v4/LICENSE b/vendor/github.com/labstack/echo/v4/LICENSE new file mode 100644 index 00000000..c46d0105 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 LabStack + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/labstack/echo/v4/Makefile b/vendor/github.com/labstack/echo/v4/Makefile new file mode 100644 index 00000000..c07d8bb4 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/Makefile @@ -0,0 +1,36 @@ +PKG := "github.com/labstack/echo" +PKG_LIST := $(shell go list ${PKG}/...) + +tag: + @git tag `grep -P '^\tversion = ' echo.go|cut -f2 -d'"'` + @git tag|grep -v ^v + +.DEFAULT_GOAL := check +check: lint vet race ## Check project + +init: + @go install golang.org/x/lint/golint@latest + @go install honnef.co/go/tools/cmd/staticcheck@latest + +lint: ## Lint the files + @staticcheck ${PKG_LIST} + @golint -set_exit_status ${PKG_LIST} + +vet: ## Vet the files + @go vet ${PKG_LIST} + +test: ## Run tests + @go test -short ${PKG_LIST} + +race: ## Run tests with data race detector + @go test -race ${PKG_LIST} + +benchmark: ## Run benchmarks + @go test -run="-" -bench=".*" ${PKG_LIST} + +help: ## Display this help screen + @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +goversion ?= "1.20" +test_version: ## Run tests inside Docker with given version (defaults to 1.20 oldest supported). Example: make test_version goversion=1.20 + @docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check" diff --git a/vendor/github.com/labstack/echo/v4/README.md b/vendor/github.com/labstack/echo/v4/README.md new file mode 100644 index 00000000..5381898d --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/README.md @@ -0,0 +1,158 @@ +[![Sourcegraph](https://sourcegraph.com/github.com/labstack/echo/-/badge.svg?style=flat-square)](https://sourcegraph.com/github.com/labstack/echo?badge) +[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/labstack/echo/v4) +[![Go Report Card](https://goreportcard.com/badge/github.com/labstack/echo?style=flat-square)](https://goreportcard.com/report/github.com/labstack/echo) +[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/labstack/echo/echo.yml?style=flat-square)](https://github.com/labstack/echo/actions) +[![Codecov](https://img.shields.io/codecov/c/github/labstack/echo.svg?style=flat-square)](https://codecov.io/gh/labstack/echo) +[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://github.com/labstack/echo/discussions) +[![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack) +[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE) + +## Echo + +High performance, extensible, minimalist Go web framework. + +* [Official website](https://echo.labstack.com) +* [Quick start](https://echo.labstack.com/docs/quick-start) +* [Middlewares](https://echo.labstack.com/docs/category/middleware) + +Help and questions: [Github Discussions](https://github.com/labstack/echo/discussions) + + +### Feature Overview + +- Optimized HTTP router which smartly prioritize routes +- Build robust and scalable RESTful APIs +- Group APIs +- Extensible middleware framework +- Define middleware at root, group or route level +- Data binding for JSON, XML and form payload +- Handy functions to send variety of HTTP responses +- Centralized HTTP error handling +- Template rendering with any template engine +- Define your format for the logger +- Highly customizable +- Automatic TLS via Let’s Encrypt +- HTTP/2 support + +## Sponsors + + +
+ +Click [here](https://github.com/sponsors/labstack) for more information on sponsorship. + +## Benchmarks + +Date: 2020/11/11
+Source: https://github.com/vishr/web-framework-benchmark
+Lower is better! + + + + +The benchmarks above were run on an Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz + +## [Guide](https://echo.labstack.com/guide) + +### Installation + +```sh +// go get github.com/labstack/echo/{version} +go get github.com/labstack/echo/v4 +``` +Latest version of Echo supports last four Go major [releases](https://go.dev/doc/devel/release) and might work with older versions. + +### Example + +```go +package main + +import ( + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "log/slog" + "net/http" +) + +func main() { + // Echo instance + e := echo.New() + + // Middleware + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + + // Routes + e.GET("/", hello) + + // Start server + if err := e.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) { + slog.Error("failed to start server", "error", err) + } +} + +// Handler +func hello(c echo.Context) error { + return c.String(http.StatusOK, "Hello, World!") +} +``` + +# Official middleware repositories + +Following list of middleware is maintained by Echo team. + +| Repository | Description | +|------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [github.com/labstack/echo-jwt](https://github.com/labstack/echo-jwt) | [JWT](https://github.com/golang-jwt/jwt) middleware | +| [github.com/labstack/echo-contrib](https://github.com/labstack/echo-contrib) | [casbin](https://github.com/casbin/casbin), [gorilla/sessions](https://github.com/gorilla/sessions), [jaegertracing](https://github.com/uber/jaeger-client-go), [prometheus](https://github.com/prometheus/client_golang/), [pprof](https://pkg.go.dev/net/http/pprof), [zipkin](https://github.com/openzipkin/zipkin-go) middlewares | + +# Third-party middleware repositories + +Be careful when adding 3rd party middleware. Echo teams does not have time or manpower to guarantee safety and quality +of middlewares in this list. + +| Repository | Description | +|------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [deepmap/oapi-codegen](https://github.com/deepmap/oapi-codegen) | Automatically generate RESTful API documentation with [OpenAPI](https://swagger.io/specification/) Client and Server Code Generator | +| [github.com/swaggo/echo-swagger](https://github.com/swaggo/echo-swagger) | Automatically generate RESTful API documentation with [Swagger](https://swagger.io/) 2.0. | +| [github.com/ziflex/lecho](https://github.com/ziflex/lecho) | [Zerolog](https://github.com/rs/zerolog) logging library wrapper for Echo logger interface. | +| [github.com/brpaz/echozap](https://github.com/brpaz/echozap) | Uber´s [Zap](https://github.com/uber-go/zap) logging library wrapper for Echo logger interface. | +| [github.com/samber/slog-echo](https://github.com/samber/slog-echo) | Go [slog](https://pkg.go.dev/golang.org/x/exp/slog) logging library wrapper for Echo logger interface. | +| [github.com/darkweak/souin/plugins/echo](https://github.com/darkweak/souin/tree/master/plugins/echo) | HTTP cache system based on [Souin](https://github.com/darkweak/souin) to automatically get your endpoints cached. It supports some distributed and non-distributed storage systems depending your needs. | +| [github.com/mikestefanello/pagoda](https://github.com/mikestefanello/pagoda) | Rapid, easy full-stack web development starter kit built with Echo. | +| [github.com/go-woo/protoc-gen-echo](https://github.com/go-woo/protoc-gen-echo) | ProtoBuf generate Echo server side code | + +Please send a PR to add your own library here. + +## Contribute + +**Use issues for everything** + +- For a small change, just send a PR. +- For bigger changes open an issue for discussion before sending a PR. +- PR should have: + - Test case + - Documentation + - Example (If it makes sense) +- You can also contribute by: + - Reporting issues + - Suggesting new features or enhancements + - Improve/fix documentation + +## Credits + +- [Vishal Rana](https://github.com/vishr) (Author) +- [Nitin Rana](https://github.com/nr17) (Consultant) +- [Roland Lammel](https://github.com/lammel) (Maintainer) +- [Martti T.](https://github.com/aldas) (Maintainer) +- [Pablo Andres Fuente](https://github.com/pafuent) (Maintainer) +- [Contributors](https://github.com/labstack/echo/graphs/contributors) + +## License + +[MIT](https://github.com/labstack/echo/blob/master/LICENSE) diff --git a/vendor/github.com/labstack/echo/v4/bind.go b/vendor/github.com/labstack/echo/v4/bind.go new file mode 100644 index 00000000..ed7ca324 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/bind.go @@ -0,0 +1,466 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package echo + +import ( + "encoding" + "encoding/xml" + "errors" + "fmt" + "mime/multipart" + "net/http" + "reflect" + "strconv" + "strings" +) + +// Binder is the interface that wraps the Bind method. +type Binder interface { + Bind(i interface{}, c Context) error +} + +// DefaultBinder is the default implementation of the Binder interface. +type DefaultBinder struct{} + +// BindUnmarshaler is the interface used to wrap the UnmarshalParam method. +// Types that don't implement this, but do implement encoding.TextUnmarshaler +// will use that interface instead. +type BindUnmarshaler interface { + // UnmarshalParam decodes and assigns a value from an form or query param. + UnmarshalParam(param string) error +} + +// bindMultipleUnmarshaler is used by binder to unmarshal multiple values from request at once to +// type implementing this interface. For example request could have multiple query fields `?a=1&a=2&b=test` in that case +// for `a` following slice `["1", "2"] will be passed to unmarshaller. +type bindMultipleUnmarshaler interface { + UnmarshalParams(params []string) error +} + +// BindPathParams binds path params to bindable object +func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error { + names := c.ParamNames() + values := c.ParamValues() + params := map[string][]string{} + for i, name := range names { + params[name] = []string{values[i]} + } + if err := b.bindData(i, params, "param", nil); err != nil { + return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) + } + return nil +} + +// BindQueryParams binds query params to bindable object +func (b *DefaultBinder) BindQueryParams(c Context, i interface{}) error { + if err := b.bindData(i, c.QueryParams(), "query", nil); err != nil { + return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) + } + return nil +} + +// BindBody binds request body contents to bindable object +// NB: then binding forms take note that this implementation uses standard library form parsing +// which parses form data from BOTH URL and BODY if content type is not MIMEMultipartForm +// See non-MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseForm +// See MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseMultipartForm +func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) { + req := c.Request() + if req.ContentLength == 0 { + return + } + + // mediatype is found like `mime.ParseMediaType()` does it + base, _, _ := strings.Cut(req.Header.Get(HeaderContentType), ";") + mediatype := strings.TrimSpace(base) + + switch mediatype { + case MIMEApplicationJSON: + if err = c.Echo().JSONSerializer.Deserialize(c, i); err != nil { + switch err.(type) { + case *HTTPError: + return err + default: + return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) + } + } + case MIMEApplicationXML, MIMETextXML: + if err = xml.NewDecoder(req.Body).Decode(i); err != nil { + if ute, ok := err.(*xml.UnsupportedTypeError); ok { + return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err) + } else if se, ok := err.(*xml.SyntaxError); ok { + return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err) + } + return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) + } + case MIMEApplicationForm: + params, err := c.FormParams() + if err != nil { + return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) + } + if err = b.bindData(i, params, "form", nil); err != nil { + return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) + } + case MIMEMultipartForm: + params, err := c.MultipartForm() + if err != nil { + return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) + } + if err = b.bindData(i, params.Value, "form", params.File); err != nil { + return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) + } + default: + return ErrUnsupportedMediaType + } + return nil +} + +// BindHeaders binds HTTP headers to a bindable object +func (b *DefaultBinder) BindHeaders(c Context, i interface{}) error { + if err := b.bindData(i, c.Request().Header, "header", nil); err != nil { + return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) + } + return nil +} + +// Bind implements the `Binder#Bind` function. +// Binding is done in following order: 1) path params; 2) query params; 3) request body. Each step COULD override previous +// step binded values. For single source binding use their own methods BindBody, BindQueryParams, BindPathParams. +func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) { + if err := b.BindPathParams(c, i); err != nil { + return err + } + // Only bind query parameters for GET/DELETE/HEAD to avoid unexpected behavior with destination struct binding from body. + // For example a request URL `&id=1&lang=en` with body `{"id":100,"lang":"de"}` would lead to precedence issues. + // The HTTP method check restores pre-v4.1.11 behavior to avoid these problems (see issue #1670) + method := c.Request().Method + if method == http.MethodGet || method == http.MethodDelete || method == http.MethodHead { + if err = b.BindQueryParams(c, i); err != nil { + return err + } + } + return b.BindBody(c, i) +} + +// bindData will bind data ONLY fields in destination struct that have EXPLICIT tag +func (b *DefaultBinder) bindData(destination interface{}, data map[string][]string, tag string, dataFiles map[string][]*multipart.FileHeader) error { + if destination == nil || (len(data) == 0 && len(dataFiles) == 0) { + return nil + } + hasFiles := len(dataFiles) > 0 + typ := reflect.TypeOf(destination).Elem() + val := reflect.ValueOf(destination).Elem() + + // Support binding to limited Map destinations: + // - map[string][]string, + // - map[string]string <-- (binds first value from data slice) + // - map[string]interface{} + // You are better off binding to struct but there are user who want this map feature. Source of data for these cases are: + // params,query,header,form as these sources produce string values, most of the time slice of strings, actually. + if typ.Kind() == reflect.Map && typ.Key().Kind() == reflect.String { + k := typ.Elem().Kind() + isElemInterface := k == reflect.Interface + isElemString := k == reflect.String + isElemSliceOfStrings := k == reflect.Slice && typ.Elem().Elem().Kind() == reflect.String + if !(isElemSliceOfStrings || isElemString || isElemInterface) { + return nil + } + if val.IsNil() { + val.Set(reflect.MakeMap(typ)) + } + for k, v := range data { + if isElemString { + val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0])) + } else if isElemInterface { + // To maintain backward compatibility, we always bind to the first string value + // and not the slice of strings when dealing with map[string]interface{}{} + val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0])) + } else { + val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v)) + } + } + return nil + } + + // !struct + if typ.Kind() != reflect.Struct { + if tag == "param" || tag == "query" || tag == "header" { + // incompatible type, data is probably to be found in the body + return nil + } + return errors.New("binding element must be a struct") + } + + for i := 0; i < typ.NumField(); i++ { // iterate over all destination fields + typeField := typ.Field(i) + structField := val.Field(i) + if typeField.Anonymous { + if structField.Kind() == reflect.Ptr { + structField = structField.Elem() + } + } + if !structField.CanSet() { + continue + } + structFieldKind := structField.Kind() + inputFieldName := typeField.Tag.Get(tag) + if typeField.Anonymous && structFieldKind == reflect.Struct && inputFieldName != "" { + // if anonymous struct with query/param/form tags, report an error + return errors.New("query/param/form tags are not allowed with anonymous struct field") + } + + if inputFieldName == "" { + // If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contain fields with tags). + // structs that implement BindUnmarshaler are bound only when they have explicit tag + if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct { + if err := b.bindData(structField.Addr().Interface(), data, tag, dataFiles); err != nil { + return err + } + } + // does not have explicit tag and is not an ordinary struct - so move to next field + continue + } + + if hasFiles { + if ok, err := isFieldMultipartFile(structField.Type()); err != nil { + return err + } else if ok { + if ok := setMultipartFileHeaderTypes(structField, inputFieldName, dataFiles); ok { + continue + } + } + } + + inputValue, exists := data[inputFieldName] + if !exists { + // Go json.Unmarshal supports case-insensitive binding. However the + // url params are bound case-sensitive which is inconsistent. To + // fix this we must check all of the map values in a + // case-insensitive search. + for k, v := range data { + if strings.EqualFold(k, inputFieldName) { + inputValue = v + exists = true + break + } + } + } + + if !exists { + continue + } + + // NOTE: algorithm here is not particularly sophisticated. It probably does not work with absurd types like `**[]*int` + // but it is smart enough to handle niche cases like `*int`,`*[]string`,`[]*int` . + + // try unmarshalling first, in case we're dealing with an alias to an array type + if ok, err := unmarshalInputsToField(typeField.Type.Kind(), inputValue, structField); ok { + if err != nil { + return err + } + continue + } + + if ok, err := unmarshalInputToField(typeField.Type.Kind(), inputValue[0], structField); ok { + if err != nil { + return err + } + continue + } + + // we could be dealing with pointer to slice `*[]string` so dereference it. There are wierd OpenAPI generators + // that could create struct fields like that. + if structFieldKind == reflect.Pointer { + structFieldKind = structField.Elem().Kind() + structField = structField.Elem() + } + + if structFieldKind == reflect.Slice { + sliceOf := structField.Type().Elem().Kind() + numElems := len(inputValue) + slice := reflect.MakeSlice(structField.Type(), numElems, numElems) + for j := 0; j < numElems; j++ { + if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil { + return err + } + } + structField.Set(slice) + continue + } + + if err := setWithProperType(structFieldKind, inputValue[0], structField); err != nil { + return err + } + } + return nil +} + +func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error { + // But also call it here, in case we're dealing with an array of BindUnmarshalers + if ok, err := unmarshalInputToField(valueKind, val, structField); ok { + return err + } + + switch valueKind { + case reflect.Ptr: + return setWithProperType(structField.Elem().Kind(), val, structField.Elem()) + case reflect.Int: + return setIntField(val, 0, structField) + case reflect.Int8: + return setIntField(val, 8, structField) + case reflect.Int16: + return setIntField(val, 16, structField) + case reflect.Int32: + return setIntField(val, 32, structField) + case reflect.Int64: + return setIntField(val, 64, structField) + case reflect.Uint: + return setUintField(val, 0, structField) + case reflect.Uint8: + return setUintField(val, 8, structField) + case reflect.Uint16: + return setUintField(val, 16, structField) + case reflect.Uint32: + return setUintField(val, 32, structField) + case reflect.Uint64: + return setUintField(val, 64, structField) + case reflect.Bool: + return setBoolField(val, structField) + case reflect.Float32: + return setFloatField(val, 32, structField) + case reflect.Float64: + return setFloatField(val, 64, structField) + case reflect.String: + structField.SetString(val) + default: + return errors.New("unknown type") + } + return nil +} + +func unmarshalInputsToField(valueKind reflect.Kind, values []string, field reflect.Value) (bool, error) { + if valueKind == reflect.Ptr { + if field.IsNil() { + field.Set(reflect.New(field.Type().Elem())) + } + field = field.Elem() + } + + fieldIValue := field.Addr().Interface() + unmarshaler, ok := fieldIValue.(bindMultipleUnmarshaler) + if !ok { + return false, nil + } + return true, unmarshaler.UnmarshalParams(values) +} + +func unmarshalInputToField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) { + if valueKind == reflect.Ptr { + if field.IsNil() { + field.Set(reflect.New(field.Type().Elem())) + } + field = field.Elem() + } + + fieldIValue := field.Addr().Interface() + switch unmarshaler := fieldIValue.(type) { + case BindUnmarshaler: + return true, unmarshaler.UnmarshalParam(val) + case encoding.TextUnmarshaler: + return true, unmarshaler.UnmarshalText([]byte(val)) + } + + return false, nil +} + +func setIntField(value string, bitSize int, field reflect.Value) error { + if value == "" { + value = "0" + } + intVal, err := strconv.ParseInt(value, 10, bitSize) + if err == nil { + field.SetInt(intVal) + } + return err +} + +func setUintField(value string, bitSize int, field reflect.Value) error { + if value == "" { + value = "0" + } + uintVal, err := strconv.ParseUint(value, 10, bitSize) + if err == nil { + field.SetUint(uintVal) + } + return err +} + +func setBoolField(value string, field reflect.Value) error { + if value == "" { + value = "false" + } + boolVal, err := strconv.ParseBool(value) + if err == nil { + field.SetBool(boolVal) + } + return err +} + +func setFloatField(value string, bitSize int, field reflect.Value) error { + if value == "" { + value = "0.0" + } + floatVal, err := strconv.ParseFloat(value, bitSize) + if err == nil { + field.SetFloat(floatVal) + } + return err +} + +var ( + // NOT supported by bind as you can NOT check easily empty struct being actual file or not + multipartFileHeaderType = reflect.TypeOf(multipart.FileHeader{}) + // supported by bind as you can check by nil value if file existed or not + multipartFileHeaderPointerType = reflect.TypeOf(&multipart.FileHeader{}) + multipartFileHeaderSliceType = reflect.TypeOf([]multipart.FileHeader(nil)) + multipartFileHeaderPointerSliceType = reflect.TypeOf([]*multipart.FileHeader(nil)) +) + +func isFieldMultipartFile(field reflect.Type) (bool, error) { + switch field { + case multipartFileHeaderPointerType, + multipartFileHeaderSliceType, + multipartFileHeaderPointerSliceType: + return true, nil + case multipartFileHeaderType: + return true, errors.New("binding to multipart.FileHeader struct is not supported, use pointer to struct") + default: + return false, nil + } +} + +func setMultipartFileHeaderTypes(structField reflect.Value, inputFieldName string, files map[string][]*multipart.FileHeader) bool { + fileHeaders := files[inputFieldName] + if len(fileHeaders) == 0 { + return false + } + + result := true + switch structField.Type() { + case multipartFileHeaderPointerSliceType: + structField.Set(reflect.ValueOf(fileHeaders)) + case multipartFileHeaderSliceType: + headers := make([]multipart.FileHeader, len(fileHeaders)) + for i, fileHeader := range fileHeaders { + headers[i] = *fileHeader + } + structField.Set(reflect.ValueOf(headers)) + case multipartFileHeaderPointerType: + structField.Set(reflect.ValueOf(fileHeaders[0])) + default: + result = false + } + + return result +} diff --git a/vendor/github.com/labstack/echo/v4/binder.go b/vendor/github.com/labstack/echo/v4/binder.go new file mode 100644 index 00000000..da15ae82 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/binder.go @@ -0,0 +1,1333 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package echo + +import ( + "encoding" + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + "time" +) + +/** + Following functions provide handful of methods for binding to Go native types from request query or path parameters. + * QueryParamsBinder(c) - binds query parameters (source URL) + * PathParamsBinder(c) - binds path parameters (source URL) + * FormFieldBinder(c) - binds form fields (source URL + body) + + Example: + ```go + var length int64 + err := echo.QueryParamsBinder(c).Int64("length", &length).BindError() + ``` + + For every supported type there are following methods: + * ("param", &destination) - if parameter value exists then binds it to given destination of that type i.e Int64(...). + * Must("param", &destination) - parameter value is required to exist, binds it to given destination of that type i.e MustInt64(...). + * s("param", &destination) - (for slices) if parameter values exists then binds it to given destination of that type i.e Int64s(...). + * Musts("param", &destination) - (for slices) parameter value is required to exist, binds it to given destination of that type i.e MustInt64s(...). + + for some slice types `BindWithDelimiter("param", &dest, ",")` supports splitting parameter values before type conversion is done + i.e. URL `/api/search?id=1,2,3&id=1` can be bind to `[]int64{1,2,3,1}` + + `FailFast` flags binder to stop binding after first bind error during binder call chain. Enabled by default. + `BindError()` returns first bind error from binder and resets errors in binder. Useful along with `FailFast()` method + to do binding and returns on first problem + `BindErrors()` returns all bind errors from binder and resets errors in binder. + + Types that are supported: + * bool + * float32 + * float64 + * int + * int8 + * int16 + * int32 + * int64 + * uint + * uint8/byte (does not support `bytes()`. Use BindUnmarshaler/CustomFunc to convert value from base64 etc to []byte{}) + * uint16 + * uint32 + * uint64 + * string + * time + * duration + * BindUnmarshaler() interface + * TextUnmarshaler() interface + * JSONUnmarshaler() interface + * UnixTime() - converts unix time (integer) to time.Time + * UnixTimeMilli() - converts unix time with millisecond precision (integer) to time.Time + * UnixTimeNano() - converts unix time with nanosecond precision (integer) to time.Time + * CustomFunc() - callback function for your custom conversion logic. Signature `func(values []string) []error` +*/ + +// BindingError represents an error that occurred while binding request data. +type BindingError struct { + // Field is the field name where value binding failed + Field string `json:"field"` + *HTTPError + // Values of parameter that failed to bind. + Values []string `json:"-"` +} + +// NewBindingError creates new instance of binding error +func NewBindingError(sourceParam string, values []string, message interface{}, internalError error) error { + return &BindingError{ + Field: sourceParam, + Values: values, + HTTPError: &HTTPError{ + Code: http.StatusBadRequest, + Message: message, + Internal: internalError, + }, + } +} + +// Error returns error message +func (be *BindingError) Error() string { + return fmt.Sprintf("%s, field=%s", be.HTTPError.Error(), be.Field) +} + +// ValueBinder provides utility methods for binding query or path parameter to various Go built-in types +type ValueBinder struct { + // ValueFunc is used to get single parameter (first) value from request + ValueFunc func(sourceParam string) string + // ValuesFunc is used to get all values for parameter from request. i.e. `/api/search?ids=1&ids=2` + ValuesFunc func(sourceParam string) []string + // ErrorFunc is used to create errors. Allows you to use your own error type, that for example marshals to your specific json response + ErrorFunc func(sourceParam string, values []string, message interface{}, internalError error) error + errors []error + // failFast is flag for binding methods to return without attempting to bind when previous binding already failed + failFast bool +} + +// QueryParamsBinder creates query parameter value binder +func QueryParamsBinder(c Context) *ValueBinder { + return &ValueBinder{ + failFast: true, + ValueFunc: c.QueryParam, + ValuesFunc: func(sourceParam string) []string { + values, ok := c.QueryParams()[sourceParam] + if !ok { + return nil + } + return values + }, + ErrorFunc: NewBindingError, + } +} + +// PathParamsBinder creates path parameter value binder +func PathParamsBinder(c Context) *ValueBinder { + return &ValueBinder{ + failFast: true, + ValueFunc: c.Param, + ValuesFunc: func(sourceParam string) []string { + // path parameter should not have multiple values so getting values does not make sense but lets not error out here + value := c.Param(sourceParam) + if value == "" { + return nil + } + return []string{value} + }, + ErrorFunc: NewBindingError, + } +} + +// FormFieldBinder creates form field value binder +// For all requests, FormFieldBinder parses the raw query from the URL and uses query params as form fields +// +// For POST, PUT, and PATCH requests, it also reads the request body, parses it +// as a form and uses query params as form fields. Request body parameters take precedence over URL query +// string values in r.Form. +// +// NB: when binding forms take note that this implementation uses standard library form parsing +// which parses form data from BOTH URL and BODY if content type is not MIMEMultipartForm +// See https://golang.org/pkg/net/http/#Request.ParseForm +func FormFieldBinder(c Context) *ValueBinder { + vb := &ValueBinder{ + failFast: true, + ValueFunc: func(sourceParam string) string { + return c.Request().FormValue(sourceParam) + }, + ErrorFunc: NewBindingError, + } + vb.ValuesFunc = func(sourceParam string) []string { + if c.Request().Form == nil { + // this is same as `Request().FormValue()` does internally + _ = c.Request().ParseMultipartForm(32 << 20) + } + values, ok := c.Request().Form[sourceParam] + if !ok { + return nil + } + return values + } + + return vb +} + +// FailFast set internal flag to indicate if binding methods will return early (without binding) when previous bind failed +// NB: call this method before any other binding methods as it modifies binding methods behaviour +func (b *ValueBinder) FailFast(value bool) *ValueBinder { + b.failFast = value + return b +} + +func (b *ValueBinder) setError(err error) { + if b.errors == nil { + b.errors = []error{err} + return + } + b.errors = append(b.errors, err) +} + +// BindError returns first seen bind error and resets/empties binder errors for further calls +func (b *ValueBinder) BindError() error { + if b.errors == nil { + return nil + } + err := b.errors[0] + b.errors = nil // reset errors so next chain will start from zero + return err +} + +// BindErrors returns all bind errors and resets/empties binder errors for further calls +func (b *ValueBinder) BindErrors() []error { + if b.errors == nil { + return nil + } + errors := b.errors + b.errors = nil // reset errors so next chain will start from zero + return errors +} + +// CustomFunc binds parameter values with Func. Func is called only when parameter values exist. +func (b *ValueBinder) CustomFunc(sourceParam string, customFunc func(values []string) []error) *ValueBinder { + return b.customFunc(sourceParam, customFunc, false) +} + +// MustCustomFunc requires parameter values to exist to bind with Func. Returns error when value does not exist. +func (b *ValueBinder) MustCustomFunc(sourceParam string, customFunc func(values []string) []error) *ValueBinder { + return b.customFunc(sourceParam, customFunc, true) +} + +func (b *ValueBinder) customFunc(sourceParam string, customFunc func(values []string) []error, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + if errs := customFunc(values); errs != nil { + b.errors = append(b.errors, errs...) + } + return b +} + +// String binds parameter to string variable +func (b *ValueBinder) String(sourceParam string, dest *string) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + return b + } + *dest = value + return b +} + +// MustString requires parameter value to exist to bind to string variable. Returns error when value does not exist +func (b *ValueBinder) MustString(sourceParam string, dest *string) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil)) + return b + } + *dest = value + return b +} + +// Strings binds parameter values to slice of string +func (b *ValueBinder) Strings(sourceParam string, dest *[]string) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValuesFunc(sourceParam) + if value == nil { + return b + } + *dest = value + return b +} + +// MustStrings requires parameter values to exist to bind to slice of string variables. Returns error when value does not exist +func (b *ValueBinder) MustStrings(sourceParam string, dest *[]string) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValuesFunc(sourceParam) + if value == nil { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + return b + } + *dest = value + return b +} + +// BindUnmarshaler binds parameter to destination implementing BindUnmarshaler interface +func (b *ValueBinder) BindUnmarshaler(sourceParam string, dest BindUnmarshaler) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + tmp := b.ValueFunc(sourceParam) + if tmp == "" { + return b + } + + if err := dest.UnmarshalParam(tmp); err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to BindUnmarshaler interface", err)) + } + return b +} + +// MustBindUnmarshaler requires parameter value to exist to bind to destination implementing BindUnmarshaler interface. +// Returns error when value does not exist +func (b *ValueBinder) MustBindUnmarshaler(sourceParam string, dest BindUnmarshaler) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil)) + return b + } + + if err := dest.UnmarshalParam(value); err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to BindUnmarshaler interface", err)) + } + return b +} + +// JSONUnmarshaler binds parameter to destination implementing json.Unmarshaler interface +func (b *ValueBinder) JSONUnmarshaler(sourceParam string, dest json.Unmarshaler) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + tmp := b.ValueFunc(sourceParam) + if tmp == "" { + return b + } + + if err := dest.UnmarshalJSON([]byte(tmp)); err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to json.Unmarshaler interface", err)) + } + return b +} + +// MustJSONUnmarshaler requires parameter value to exist to bind to destination implementing json.Unmarshaler interface. +// Returns error when value does not exist +func (b *ValueBinder) MustJSONUnmarshaler(sourceParam string, dest json.Unmarshaler) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + tmp := b.ValueFunc(sourceParam) + if tmp == "" { + b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "required field value is empty", nil)) + return b + } + + if err := dest.UnmarshalJSON([]byte(tmp)); err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to json.Unmarshaler interface", err)) + } + return b +} + +// TextUnmarshaler binds parameter to destination implementing encoding.TextUnmarshaler interface +func (b *ValueBinder) TextUnmarshaler(sourceParam string, dest encoding.TextUnmarshaler) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + tmp := b.ValueFunc(sourceParam) + if tmp == "" { + return b + } + + if err := dest.UnmarshalText([]byte(tmp)); err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to encoding.TextUnmarshaler interface", err)) + } + return b +} + +// MustTextUnmarshaler requires parameter value to exist to bind to destination implementing encoding.TextUnmarshaler interface. +// Returns error when value does not exist +func (b *ValueBinder) MustTextUnmarshaler(sourceParam string, dest encoding.TextUnmarshaler) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + tmp := b.ValueFunc(sourceParam) + if tmp == "" { + b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "required field value is empty", nil)) + return b + } + + if err := dest.UnmarshalText([]byte(tmp)); err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to encoding.TextUnmarshaler interface", err)) + } + return b +} + +// BindWithDelimiter binds parameter to destination by suitable conversion function. +// Delimiter is used before conversion to split parameter value to separate values +func (b *ValueBinder) BindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder { + return b.bindWithDelimiter(sourceParam, dest, delimiter, false) +} + +// MustBindWithDelimiter requires parameter value to exist to bind destination by suitable conversion function. +// Delimiter is used before conversion to split parameter value to separate values +func (b *ValueBinder) MustBindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder { + return b.bindWithDelimiter(sourceParam, dest, delimiter, true) +} + +func (b *ValueBinder) bindWithDelimiter(sourceParam string, dest interface{}, delimiter string, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + tmpValues := make([]string, 0, len(values)) + for _, v := range values { + tmpValues = append(tmpValues, strings.Split(v, delimiter)...) + } + + switch d := dest.(type) { + case *[]string: + *d = tmpValues + return b + case *[]bool: + return b.bools(sourceParam, tmpValues, d) + case *[]int64, *[]int32, *[]int16, *[]int8, *[]int: + return b.ints(sourceParam, tmpValues, d) + case *[]uint64, *[]uint32, *[]uint16, *[]uint8, *[]uint: // *[]byte is same as *[]uint8 + return b.uints(sourceParam, tmpValues, d) + case *[]float64, *[]float32: + return b.floats(sourceParam, tmpValues, d) + case *[]time.Duration: + return b.durations(sourceParam, tmpValues, d) + default: + // support only cases when destination is slice + // does not support time.Time as it needs argument (layout) for parsing or BindUnmarshaler + b.setError(b.ErrorFunc(sourceParam, []string{}, "unsupported bind type", nil)) + return b + } +} + +// Int64 binds parameter to int64 variable +func (b *ValueBinder) Int64(sourceParam string, dest *int64) *ValueBinder { + return b.intValue(sourceParam, dest, 64, false) +} + +// MustInt64 requires parameter value to exist to bind to int64 variable. Returns error when value does not exist +func (b *ValueBinder) MustInt64(sourceParam string, dest *int64) *ValueBinder { + return b.intValue(sourceParam, dest, 64, true) +} + +// Int32 binds parameter to int32 variable +func (b *ValueBinder) Int32(sourceParam string, dest *int32) *ValueBinder { + return b.intValue(sourceParam, dest, 32, false) +} + +// MustInt32 requires parameter value to exist to bind to int32 variable. Returns error when value does not exist +func (b *ValueBinder) MustInt32(sourceParam string, dest *int32) *ValueBinder { + return b.intValue(sourceParam, dest, 32, true) +} + +// Int16 binds parameter to int16 variable +func (b *ValueBinder) Int16(sourceParam string, dest *int16) *ValueBinder { + return b.intValue(sourceParam, dest, 16, false) +} + +// MustInt16 requires parameter value to exist to bind to int16 variable. Returns error when value does not exist +func (b *ValueBinder) MustInt16(sourceParam string, dest *int16) *ValueBinder { + return b.intValue(sourceParam, dest, 16, true) +} + +// Int8 binds parameter to int8 variable +func (b *ValueBinder) Int8(sourceParam string, dest *int8) *ValueBinder { + return b.intValue(sourceParam, dest, 8, false) +} + +// MustInt8 requires parameter value to exist to bind to int8 variable. Returns error when value does not exist +func (b *ValueBinder) MustInt8(sourceParam string, dest *int8) *ValueBinder { + return b.intValue(sourceParam, dest, 8, true) +} + +// Int binds parameter to int variable +func (b *ValueBinder) Int(sourceParam string, dest *int) *ValueBinder { + return b.intValue(sourceParam, dest, 0, false) +} + +// MustInt requires parameter value to exist to bind to int variable. Returns error when value does not exist +func (b *ValueBinder) MustInt(sourceParam string, dest *int) *ValueBinder { + return b.intValue(sourceParam, dest, 0, true) +} + +func (b *ValueBinder) intValue(sourceParam string, dest interface{}, bitSize int, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + + return b.int(sourceParam, value, dest, bitSize) +} + +func (b *ValueBinder) int(sourceParam string, value string, dest interface{}, bitSize int) *ValueBinder { + n, err := strconv.ParseInt(value, 10, bitSize) + if err != nil { + if bitSize == 0 { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to int", err)) + } else { + b.setError(b.ErrorFunc(sourceParam, []string{value}, fmt.Sprintf("failed to bind field value to int%v", bitSize), err)) + } + return b + } + + switch d := dest.(type) { + case *int64: + *d = n + case *int32: + *d = int32(n) + case *int16: + *d = int16(n) + case *int8: + *d = int8(n) + case *int: + *d = int(n) + } + return b +} + +func (b *ValueBinder) intsValue(sourceParam string, dest interface{}, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, values, "required field value is empty", nil)) + } + return b + } + return b.ints(sourceParam, values, dest) +} + +func (b *ValueBinder) ints(sourceParam string, values []string, dest interface{}) *ValueBinder { + switch d := dest.(type) { + case *[]int64: + tmp := make([]int64, len(values)) + for i, v := range values { + b.int(sourceParam, v, &tmp[i], 64) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]int32: + tmp := make([]int32, len(values)) + for i, v := range values { + b.int(sourceParam, v, &tmp[i], 32) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]int16: + tmp := make([]int16, len(values)) + for i, v := range values { + b.int(sourceParam, v, &tmp[i], 16) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]int8: + tmp := make([]int8, len(values)) + for i, v := range values { + b.int(sourceParam, v, &tmp[i], 8) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]int: + tmp := make([]int, len(values)) + for i, v := range values { + b.int(sourceParam, v, &tmp[i], 0) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + } + return b +} + +// Int64s binds parameter to slice of int64 +func (b *ValueBinder) Int64s(sourceParam string, dest *[]int64) *ValueBinder { + return b.intsValue(sourceParam, dest, false) +} + +// MustInt64s requires parameter value to exist to bind to int64 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustInt64s(sourceParam string, dest *[]int64) *ValueBinder { + return b.intsValue(sourceParam, dest, true) +} + +// Int32s binds parameter to slice of int32 +func (b *ValueBinder) Int32s(sourceParam string, dest *[]int32) *ValueBinder { + return b.intsValue(sourceParam, dest, false) +} + +// MustInt32s requires parameter value to exist to bind to int32 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustInt32s(sourceParam string, dest *[]int32) *ValueBinder { + return b.intsValue(sourceParam, dest, true) +} + +// Int16s binds parameter to slice of int16 +func (b *ValueBinder) Int16s(sourceParam string, dest *[]int16) *ValueBinder { + return b.intsValue(sourceParam, dest, false) +} + +// MustInt16s requires parameter value to exist to bind to int16 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustInt16s(sourceParam string, dest *[]int16) *ValueBinder { + return b.intsValue(sourceParam, dest, true) +} + +// Int8s binds parameter to slice of int8 +func (b *ValueBinder) Int8s(sourceParam string, dest *[]int8) *ValueBinder { + return b.intsValue(sourceParam, dest, false) +} + +// MustInt8s requires parameter value to exist to bind to int8 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustInt8s(sourceParam string, dest *[]int8) *ValueBinder { + return b.intsValue(sourceParam, dest, true) +} + +// Ints binds parameter to slice of int +func (b *ValueBinder) Ints(sourceParam string, dest *[]int) *ValueBinder { + return b.intsValue(sourceParam, dest, false) +} + +// MustInts requires parameter value to exist to bind to int slice variable. Returns error when value does not exist +func (b *ValueBinder) MustInts(sourceParam string, dest *[]int) *ValueBinder { + return b.intsValue(sourceParam, dest, true) +} + +// Uint64 binds parameter to uint64 variable +func (b *ValueBinder) Uint64(sourceParam string, dest *uint64) *ValueBinder { + return b.uintValue(sourceParam, dest, 64, false) +} + +// MustUint64 requires parameter value to exist to bind to uint64 variable. Returns error when value does not exist +func (b *ValueBinder) MustUint64(sourceParam string, dest *uint64) *ValueBinder { + return b.uintValue(sourceParam, dest, 64, true) +} + +// Uint32 binds parameter to uint32 variable +func (b *ValueBinder) Uint32(sourceParam string, dest *uint32) *ValueBinder { + return b.uintValue(sourceParam, dest, 32, false) +} + +// MustUint32 requires parameter value to exist to bind to uint32 variable. Returns error when value does not exist +func (b *ValueBinder) MustUint32(sourceParam string, dest *uint32) *ValueBinder { + return b.uintValue(sourceParam, dest, 32, true) +} + +// Uint16 binds parameter to uint16 variable +func (b *ValueBinder) Uint16(sourceParam string, dest *uint16) *ValueBinder { + return b.uintValue(sourceParam, dest, 16, false) +} + +// MustUint16 requires parameter value to exist to bind to uint16 variable. Returns error when value does not exist +func (b *ValueBinder) MustUint16(sourceParam string, dest *uint16) *ValueBinder { + return b.uintValue(sourceParam, dest, 16, true) +} + +// Uint8 binds parameter to uint8 variable +func (b *ValueBinder) Uint8(sourceParam string, dest *uint8) *ValueBinder { + return b.uintValue(sourceParam, dest, 8, false) +} + +// MustUint8 requires parameter value to exist to bind to uint8 variable. Returns error when value does not exist +func (b *ValueBinder) MustUint8(sourceParam string, dest *uint8) *ValueBinder { + return b.uintValue(sourceParam, dest, 8, true) +} + +// Byte binds parameter to byte variable +func (b *ValueBinder) Byte(sourceParam string, dest *byte) *ValueBinder { + return b.uintValue(sourceParam, dest, 8, false) +} + +// MustByte requires parameter value to exist to bind to byte variable. Returns error when value does not exist +func (b *ValueBinder) MustByte(sourceParam string, dest *byte) *ValueBinder { + return b.uintValue(sourceParam, dest, 8, true) +} + +// Uint binds parameter to uint variable +func (b *ValueBinder) Uint(sourceParam string, dest *uint) *ValueBinder { + return b.uintValue(sourceParam, dest, 0, false) +} + +// MustUint requires parameter value to exist to bind to uint variable. Returns error when value does not exist +func (b *ValueBinder) MustUint(sourceParam string, dest *uint) *ValueBinder { + return b.uintValue(sourceParam, dest, 0, true) +} + +func (b *ValueBinder) uintValue(sourceParam string, dest interface{}, bitSize int, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + + return b.uint(sourceParam, value, dest, bitSize) +} + +func (b *ValueBinder) uint(sourceParam string, value string, dest interface{}, bitSize int) *ValueBinder { + n, err := strconv.ParseUint(value, 10, bitSize) + if err != nil { + if bitSize == 0 { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to uint", err)) + } else { + b.setError(b.ErrorFunc(sourceParam, []string{value}, fmt.Sprintf("failed to bind field value to uint%v", bitSize), err)) + } + return b + } + + switch d := dest.(type) { + case *uint64: + *d = n + case *uint32: + *d = uint32(n) + case *uint16: + *d = uint16(n) + case *uint8: // byte is alias to uint8 + *d = uint8(n) + case *uint: + *d = uint(n) + } + return b +} + +func (b *ValueBinder) uintsValue(sourceParam string, dest interface{}, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, values, "required field value is empty", nil)) + } + return b + } + return b.uints(sourceParam, values, dest) +} + +func (b *ValueBinder) uints(sourceParam string, values []string, dest interface{}) *ValueBinder { + switch d := dest.(type) { + case *[]uint64: + tmp := make([]uint64, len(values)) + for i, v := range values { + b.uint(sourceParam, v, &tmp[i], 64) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]uint32: + tmp := make([]uint32, len(values)) + for i, v := range values { + b.uint(sourceParam, v, &tmp[i], 32) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]uint16: + tmp := make([]uint16, len(values)) + for i, v := range values { + b.uint(sourceParam, v, &tmp[i], 16) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]uint8: // byte is alias to uint8 + tmp := make([]uint8, len(values)) + for i, v := range values { + b.uint(sourceParam, v, &tmp[i], 8) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]uint: + tmp := make([]uint, len(values)) + for i, v := range values { + b.uint(sourceParam, v, &tmp[i], 0) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + } + return b +} + +// Uint64s binds parameter to slice of uint64 +func (b *ValueBinder) Uint64s(sourceParam string, dest *[]uint64) *ValueBinder { + return b.uintsValue(sourceParam, dest, false) +} + +// MustUint64s requires parameter value to exist to bind to uint64 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustUint64s(sourceParam string, dest *[]uint64) *ValueBinder { + return b.uintsValue(sourceParam, dest, true) +} + +// Uint32s binds parameter to slice of uint32 +func (b *ValueBinder) Uint32s(sourceParam string, dest *[]uint32) *ValueBinder { + return b.uintsValue(sourceParam, dest, false) +} + +// MustUint32s requires parameter value to exist to bind to uint32 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustUint32s(sourceParam string, dest *[]uint32) *ValueBinder { + return b.uintsValue(sourceParam, dest, true) +} + +// Uint16s binds parameter to slice of uint16 +func (b *ValueBinder) Uint16s(sourceParam string, dest *[]uint16) *ValueBinder { + return b.uintsValue(sourceParam, dest, false) +} + +// MustUint16s requires parameter value to exist to bind to uint16 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustUint16s(sourceParam string, dest *[]uint16) *ValueBinder { + return b.uintsValue(sourceParam, dest, true) +} + +// Uint8s binds parameter to slice of uint8 +func (b *ValueBinder) Uint8s(sourceParam string, dest *[]uint8) *ValueBinder { + return b.uintsValue(sourceParam, dest, false) +} + +// MustUint8s requires parameter value to exist to bind to uint8 slice variable. Returns error when value does not exist +func (b *ValueBinder) MustUint8s(sourceParam string, dest *[]uint8) *ValueBinder { + return b.uintsValue(sourceParam, dest, true) +} + +// Uints binds parameter to slice of uint +func (b *ValueBinder) Uints(sourceParam string, dest *[]uint) *ValueBinder { + return b.uintsValue(sourceParam, dest, false) +} + +// MustUints requires parameter value to exist to bind to uint slice variable. Returns error when value does not exist +func (b *ValueBinder) MustUints(sourceParam string, dest *[]uint) *ValueBinder { + return b.uintsValue(sourceParam, dest, true) +} + +// Bool binds parameter to bool variable +func (b *ValueBinder) Bool(sourceParam string, dest *bool) *ValueBinder { + return b.boolValue(sourceParam, dest, false) +} + +// MustBool requires parameter value to exist to bind to bool variable. Returns error when value does not exist +func (b *ValueBinder) MustBool(sourceParam string, dest *bool) *ValueBinder { + return b.boolValue(sourceParam, dest, true) +} + +func (b *ValueBinder) boolValue(sourceParam string, dest *bool, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + return b.bool(sourceParam, value, dest) +} + +func (b *ValueBinder) bool(sourceParam string, value string, dest *bool) *ValueBinder { + n, err := strconv.ParseBool(value) + if err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to bool", err)) + return b + } + + *dest = n + return b +} + +func (b *ValueBinder) boolsValue(sourceParam string, dest *[]bool, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + return b.bools(sourceParam, values, dest) +} + +func (b *ValueBinder) bools(sourceParam string, values []string, dest *[]bool) *ValueBinder { + tmp := make([]bool, len(values)) + for i, v := range values { + b.bool(sourceParam, v, &tmp[i]) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *dest = tmp + } + return b +} + +// Bools binds parameter values to slice of bool variables +func (b *ValueBinder) Bools(sourceParam string, dest *[]bool) *ValueBinder { + return b.boolsValue(sourceParam, dest, false) +} + +// MustBools requires parameter values to exist to bind to slice of bool variables. Returns error when values does not exist +func (b *ValueBinder) MustBools(sourceParam string, dest *[]bool) *ValueBinder { + return b.boolsValue(sourceParam, dest, true) +} + +// Float64 binds parameter to float64 variable +func (b *ValueBinder) Float64(sourceParam string, dest *float64) *ValueBinder { + return b.floatValue(sourceParam, dest, 64, false) +} + +// MustFloat64 requires parameter value to exist to bind to float64 variable. Returns error when value does not exist +func (b *ValueBinder) MustFloat64(sourceParam string, dest *float64) *ValueBinder { + return b.floatValue(sourceParam, dest, 64, true) +} + +// Float32 binds parameter to float32 variable +func (b *ValueBinder) Float32(sourceParam string, dest *float32) *ValueBinder { + return b.floatValue(sourceParam, dest, 32, false) +} + +// MustFloat32 requires parameter value to exist to bind to float32 variable. Returns error when value does not exist +func (b *ValueBinder) MustFloat32(sourceParam string, dest *float32) *ValueBinder { + return b.floatValue(sourceParam, dest, 32, true) +} + +func (b *ValueBinder) floatValue(sourceParam string, dest interface{}, bitSize int, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + + return b.float(sourceParam, value, dest, bitSize) +} + +func (b *ValueBinder) float(sourceParam string, value string, dest interface{}, bitSize int) *ValueBinder { + n, err := strconv.ParseFloat(value, bitSize) + if err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{value}, fmt.Sprintf("failed to bind field value to float%v", bitSize), err)) + return b + } + + switch d := dest.(type) { + case *float64: + *d = n + case *float32: + *d = float32(n) + } + return b +} + +func (b *ValueBinder) floatsValue(sourceParam string, dest interface{}, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + return b.floats(sourceParam, values, dest) +} + +func (b *ValueBinder) floats(sourceParam string, values []string, dest interface{}) *ValueBinder { + switch d := dest.(type) { + case *[]float64: + tmp := make([]float64, len(values)) + for i, v := range values { + b.float(sourceParam, v, &tmp[i], 64) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + case *[]float32: + tmp := make([]float32, len(values)) + for i, v := range values { + b.float(sourceParam, v, &tmp[i], 32) + if b.failFast && b.errors != nil { + return b + } + } + if b.errors == nil { + *d = tmp + } + } + return b +} + +// Float64s binds parameter values to slice of float64 variables +func (b *ValueBinder) Float64s(sourceParam string, dest *[]float64) *ValueBinder { + return b.floatsValue(sourceParam, dest, false) +} + +// MustFloat64s requires parameter values to exist to bind to slice of float64 variables. Returns error when values does not exist +func (b *ValueBinder) MustFloat64s(sourceParam string, dest *[]float64) *ValueBinder { + return b.floatsValue(sourceParam, dest, true) +} + +// Float32s binds parameter values to slice of float32 variables +func (b *ValueBinder) Float32s(sourceParam string, dest *[]float32) *ValueBinder { + return b.floatsValue(sourceParam, dest, false) +} + +// MustFloat32s requires parameter values to exist to bind to slice of float32 variables. Returns error when values does not exist +func (b *ValueBinder) MustFloat32s(sourceParam string, dest *[]float32) *ValueBinder { + return b.floatsValue(sourceParam, dest, true) +} + +// Time binds parameter to time.Time variable +func (b *ValueBinder) Time(sourceParam string, dest *time.Time, layout string) *ValueBinder { + return b.time(sourceParam, dest, layout, false) +} + +// MustTime requires parameter value to exist to bind to time.Time variable. Returns error when value does not exist +func (b *ValueBinder) MustTime(sourceParam string, dest *time.Time, layout string) *ValueBinder { + return b.time(sourceParam, dest, layout, true) +} + +func (b *ValueBinder) time(sourceParam string, dest *time.Time, layout string, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil)) + } + return b + } + t, err := time.Parse(layout, value) + if err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to Time", err)) + return b + } + *dest = t + return b +} + +// Times binds parameter values to slice of time.Time variables +func (b *ValueBinder) Times(sourceParam string, dest *[]time.Time, layout string) *ValueBinder { + return b.times(sourceParam, dest, layout, false) +} + +// MustTimes requires parameter values to exist to bind to slice of time.Time variables. Returns error when values does not exist +func (b *ValueBinder) MustTimes(sourceParam string, dest *[]time.Time, layout string) *ValueBinder { + return b.times(sourceParam, dest, layout, true) +} + +func (b *ValueBinder) times(sourceParam string, dest *[]time.Time, layout string, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + + tmp := make([]time.Time, len(values)) + for i, v := range values { + t, err := time.Parse(layout, v) + if err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{v}, "failed to bind field value to Time", err)) + if b.failFast { + return b + } + continue + } + tmp[i] = t + } + if b.errors == nil { + *dest = tmp + } + return b +} + +// Duration binds parameter to time.Duration variable +func (b *ValueBinder) Duration(sourceParam string, dest *time.Duration) *ValueBinder { + return b.duration(sourceParam, dest, false) +} + +// MustDuration requires parameter value to exist to bind to time.Duration variable. Returns error when value does not exist +func (b *ValueBinder) MustDuration(sourceParam string, dest *time.Duration) *ValueBinder { + return b.duration(sourceParam, dest, true) +} + +func (b *ValueBinder) duration(sourceParam string, dest *time.Duration, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil)) + } + return b + } + t, err := time.ParseDuration(value) + if err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to Duration", err)) + return b + } + *dest = t + return b +} + +// Durations binds parameter values to slice of time.Duration variables +func (b *ValueBinder) Durations(sourceParam string, dest *[]time.Duration) *ValueBinder { + return b.durationsValue(sourceParam, dest, false) +} + +// MustDurations requires parameter values to exist to bind to slice of time.Duration variables. Returns error when values does not exist +func (b *ValueBinder) MustDurations(sourceParam string, dest *[]time.Duration) *ValueBinder { + return b.durationsValue(sourceParam, dest, true) +} + +func (b *ValueBinder) durationsValue(sourceParam string, dest *[]time.Duration, valueMustExist bool) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + values := b.ValuesFunc(sourceParam) + if len(values) == 0 { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil)) + } + return b + } + return b.durations(sourceParam, values, dest) +} + +func (b *ValueBinder) durations(sourceParam string, values []string, dest *[]time.Duration) *ValueBinder { + tmp := make([]time.Duration, len(values)) + for i, v := range values { + t, err := time.ParseDuration(v) + if err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{v}, "failed to bind field value to Duration", err)) + if b.failFast { + return b + } + continue + } + tmp[i] = t + } + if b.errors == nil { + *dest = tmp + } + return b +} + +// UnixTime binds parameter to time.Time variable (in local Time corresponding to the given Unix time). +// +// Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00 +// +// Note: +// - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal +func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder { + return b.unixTime(sourceParam, dest, false, time.Second) +} + +// MustUnixTime requires parameter value to exist to bind to time.Duration variable (in local time corresponding +// to the given Unix time). Returns error when value does not exist. +// +// Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00 +// +// Note: +// - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal +func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBinder { + return b.unixTime(sourceParam, dest, true, time.Second) +} + +// UnixTimeMilli binds parameter to time.Time variable (in local time corresponding to the given Unix time in millisecond precision). +// +// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00 +// +// Note: +// - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal +func (b *ValueBinder) UnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder { + return b.unixTime(sourceParam, dest, false, time.Millisecond) +} + +// MustUnixTimeMilli requires parameter value to exist to bind to time.Duration variable (in local time corresponding +// to the given Unix time in millisecond precision). Returns error when value does not exist. +// +// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00 +// +// Note: +// - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal +func (b *ValueBinder) MustUnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder { + return b.unixTime(sourceParam, dest, true, time.Millisecond) +} + +// UnixTimeNano binds parameter to time.Time variable (in local time corresponding to the given Unix time in nanosecond precision). +// +// Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00 +// Example: 1000000000 binds to 1970-01-01T00:00:01.000000000+00:00 +// Example: 999999999 binds to 1970-01-01T00:00:00.999999999+00:00 +// +// Note: +// - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal +// - Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example. +func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder { + return b.unixTime(sourceParam, dest, false, time.Nanosecond) +} + +// MustUnixTimeNano requires parameter value to exist to bind to time.Duration variable (in local Time corresponding +// to the given Unix time value in nano second precision). Returns error when value does not exist. +// +// Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00 +// Example: 1000000000 binds to 1970-01-01T00:00:01.000000000+00:00 +// Example: 999999999 binds to 1970-01-01T00:00:00.999999999+00:00 +// +// Note: +// - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal +// - Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example. +func (b *ValueBinder) MustUnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder { + return b.unixTime(sourceParam, dest, true, time.Nanosecond) +} + +func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExist bool, precision time.Duration) *ValueBinder { + if b.failFast && b.errors != nil { + return b + } + + value := b.ValueFunc(sourceParam) + if value == "" { + if valueMustExist { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil)) + } + return b + } + + n, err := strconv.ParseInt(value, 10, 64) + if err != nil { + b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to Time", err)) + return b + } + + switch precision { + case time.Second: + *dest = time.Unix(n, 0) + case time.Millisecond: + *dest = time.UnixMilli(n) + case time.Nanosecond: + *dest = time.Unix(0, n) + } + return b +} diff --git a/vendor/github.com/labstack/echo/v4/codecov.yml b/vendor/github.com/labstack/echo/v4/codecov.yml new file mode 100644 index 00000000..0fa3a3f1 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/codecov.yml @@ -0,0 +1,11 @@ +coverage: + status: + project: + default: + threshold: 1% + patch: + default: + threshold: 1% + +comment: + require_changes: true \ No newline at end of file diff --git a/vendor/github.com/labstack/echo/v4/context.go b/vendor/github.com/labstack/echo/v4/context.go new file mode 100644 index 00000000..f5dd5a69 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/context.go @@ -0,0 +1,660 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package echo + +import ( + "bytes" + "encoding/xml" + "fmt" + "io" + "mime/multipart" + "net" + "net/http" + "net/url" + "strings" + "sync" +) + +// Context represents the context of the current HTTP request. It holds request and +// response objects, path, path parameters, data and registered handler. +type Context interface { + // Request returns `*http.Request`. + Request() *http.Request + + // SetRequest sets `*http.Request`. + SetRequest(r *http.Request) + + // SetResponse sets `*Response`. + SetResponse(r *Response) + + // Response returns `*Response`. + Response() *Response + + // IsTLS returns true if HTTP connection is TLS otherwise false. + IsTLS() bool + + // IsWebSocket returns true if HTTP connection is WebSocket otherwise false. + IsWebSocket() bool + + // Scheme returns the HTTP protocol scheme, `http` or `https`. + Scheme() string + + // RealIP returns the client's network address based on `X-Forwarded-For` + // or `X-Real-IP` request header. + // The behavior can be configured using `Echo#IPExtractor`. + RealIP() string + + // Path returns the registered path for the handler. + Path() string + + // SetPath sets the registered path for the handler. + SetPath(p string) + + // Param returns path parameter by name. + Param(name string) string + + // ParamNames returns path parameter names. + ParamNames() []string + + // SetParamNames sets path parameter names. + SetParamNames(names ...string) + + // ParamValues returns path parameter values. + ParamValues() []string + + // SetParamValues sets path parameter values. + SetParamValues(values ...string) + + // QueryParam returns the query param for the provided name. + QueryParam(name string) string + + // QueryParams returns the query parameters as `url.Values`. + QueryParams() url.Values + + // QueryString returns the URL query string. + QueryString() string + + // FormValue returns the form field value for the provided name. + FormValue(name string) string + + // FormParams returns the form parameters as `url.Values`. + FormParams() (url.Values, error) + + // FormFile returns the multipart form file for the provided name. + FormFile(name string) (*multipart.FileHeader, error) + + // MultipartForm returns the multipart form. + MultipartForm() (*multipart.Form, error) + + // Cookie returns the named cookie provided in the request. + Cookie(name string) (*http.Cookie, error) + + // SetCookie adds a `Set-Cookie` header in HTTP response. + SetCookie(cookie *http.Cookie) + + // Cookies returns the HTTP cookies sent with the request. + Cookies() []*http.Cookie + + // Get retrieves data from the context. + Get(key string) interface{} + + // Set saves data in the context. + Set(key string, val interface{}) + + // Bind binds path params, query params and the request body into provided type `i`. The default binder + // binds body based on Content-Type header. + Bind(i interface{}) error + + // Validate validates provided `i`. It is usually called after `Context#Bind()`. + // Validator must be registered using `Echo#Validator`. + Validate(i interface{}) error + + // Render renders a template with data and sends a text/html response with status + // code. Renderer must be registered using `Echo.Renderer`. + Render(code int, name string, data interface{}) error + + // HTML sends an HTTP response with status code. + HTML(code int, html string) error + + // HTMLBlob sends an HTTP blob response with status code. + HTMLBlob(code int, b []byte) error + + // String sends a string response with status code. + String(code int, s string) error + + // JSON sends a JSON response with status code. + JSON(code int, i interface{}) error + + // JSONPretty sends a pretty-print JSON with status code. + JSONPretty(code int, i interface{}, indent string) error + + // JSONBlob sends a JSON blob response with status code. + JSONBlob(code int, b []byte) error + + // JSONP sends a JSONP response with status code. It uses `callback` to construct + // the JSONP payload. + JSONP(code int, callback string, i interface{}) error + + // JSONPBlob sends a JSONP blob response with status code. It uses `callback` + // to construct the JSONP payload. + JSONPBlob(code int, callback string, b []byte) error + + // XML sends an XML response with status code. + XML(code int, i interface{}) error + + // XMLPretty sends a pretty-print XML with status code. + XMLPretty(code int, i interface{}, indent string) error + + // XMLBlob sends an XML blob response with status code. + XMLBlob(code int, b []byte) error + + // Blob sends a blob response with status code and content type. + Blob(code int, contentType string, b []byte) error + + // Stream sends a streaming response with status code and content type. + Stream(code int, contentType string, r io.Reader) error + + // File sends a response with the content of the file. + File(file string) error + + // Attachment sends a response as attachment, prompting client to save the + // file. + Attachment(file string, name string) error + + // Inline sends a response as inline, opening the file in the browser. + Inline(file string, name string) error + + // NoContent sends a response with no body and a status code. + NoContent(code int) error + + // Redirect redirects the request to a provided URL with status code. + Redirect(code int, url string) error + + // Error invokes the registered global HTTP error handler. Generally used by middleware. + // A side-effect of calling global error handler is that now Response has been committed (sent to the client) and + // middlewares up in chain can not change Response status code or Response body anymore. + // + // Avoid using this method in handlers as no middleware will be able to effectively handle errors after that. + Error(err error) + + // Handler returns the matched handler by router. + Handler() HandlerFunc + + // SetHandler sets the matched handler by router. + SetHandler(h HandlerFunc) + + // Logger returns the `Logger` instance. + Logger() Logger + + // SetLogger Set the logger + SetLogger(l Logger) + + // Echo returns the `Echo` instance. + Echo() *Echo + + // Reset resets the context after request completes. It must be called along + // with `Echo#AcquireContext()` and `Echo#ReleaseContext()`. + // See `Echo#ServeHTTP()` + Reset(r *http.Request, w http.ResponseWriter) +} + +type context struct { + logger Logger + request *http.Request + response *Response + query url.Values + echo *Echo + + store Map + lock sync.RWMutex + + // following fields are set by Router + handler HandlerFunc + + // path is route path that Router matched. It is empty string where there is no route match. + // Route registered with RouteNotFound is considered as a match and path therefore is not empty. + path string + + // Usually echo.Echo is sizing pvalues but there could be user created middlewares that decide to + // overwrite parameter by calling SetParamNames + SetParamValues. + // When echo.Echo allocated that slice it length/capacity is tied to echo.Echo.maxParam value. + // + // It is important that pvalues size is always equal or bigger to pnames length. + pvalues []string + + // pnames length is tied to param count for the matched route + pnames []string +} + +const ( + // ContextKeyHeaderAllow is set by Router for getting value for `Allow` header in later stages of handler call chain. + // Allow header is mandatory for status 405 (method not found) and useful for OPTIONS method requests. + // It is added to context only when Router does not find matching method handler for request. + ContextKeyHeaderAllow = "echo_header_allow" +) + +const ( + defaultMemory = 32 << 20 // 32 MB + indexPage = "index.html" + defaultIndent = " " +) + +func (c *context) writeContentType(value string) { + header := c.Response().Header() + if header.Get(HeaderContentType) == "" { + header.Set(HeaderContentType, value) + } +} + +func (c *context) Request() *http.Request { + return c.request +} + +func (c *context) SetRequest(r *http.Request) { + c.request = r +} + +func (c *context) Response() *Response { + return c.response +} + +func (c *context) SetResponse(r *Response) { + c.response = r +} + +func (c *context) IsTLS() bool { + return c.request.TLS != nil +} + +func (c *context) IsWebSocket() bool { + upgrade := c.request.Header.Get(HeaderUpgrade) + return strings.EqualFold(upgrade, "websocket") +} + +func (c *context) Scheme() string { + // Can't use `r.Request.URL.Scheme` + // See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0 + if c.IsTLS() { + return "https" + } + if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" { + return scheme + } + if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" { + return scheme + } + if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" { + return "https" + } + if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" { + return scheme + } + return "http" +} + +func (c *context) RealIP() string { + if c.echo != nil && c.echo.IPExtractor != nil { + return c.echo.IPExtractor(c.request) + } + // Fall back to legacy behavior + if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" { + i := strings.IndexAny(ip, ",") + if i > 0 { + xffip := strings.TrimSpace(ip[:i]) + xffip = strings.TrimPrefix(xffip, "[") + xffip = strings.TrimSuffix(xffip, "]") + return xffip + } + return ip + } + if ip := c.request.Header.Get(HeaderXRealIP); ip != "" { + ip = strings.TrimPrefix(ip, "[") + ip = strings.TrimSuffix(ip, "]") + return ip + } + ra, _, _ := net.SplitHostPort(c.request.RemoteAddr) + return ra +} + +func (c *context) Path() string { + return c.path +} + +func (c *context) SetPath(p string) { + c.path = p +} + +func (c *context) Param(name string) string { + for i, n := range c.pnames { + if i < len(c.pvalues) { + if n == name { + return c.pvalues[i] + } + } + } + return "" +} + +func (c *context) ParamNames() []string { + return c.pnames +} + +func (c *context) SetParamNames(names ...string) { + c.pnames = names + + l := len(names) + if len(c.pvalues) < l { + // Keeping the old pvalues just for backward compatibility, but it sounds that doesn't make sense to keep them, + // probably those values will be overridden in a Context#SetParamValues + newPvalues := make([]string, l) + copy(newPvalues, c.pvalues) + c.pvalues = newPvalues + } +} + +func (c *context) ParamValues() []string { + return c.pvalues[:len(c.pnames)] +} + +func (c *context) SetParamValues(values ...string) { + // NOTE: Don't just set c.pvalues = values, because it has to have length c.echo.maxParam (or bigger) at all times + // It will brake the Router#Find code + limit := len(values) + if limit > len(c.pvalues) { + c.pvalues = make([]string, limit) + } + for i := 0; i < limit; i++ { + c.pvalues[i] = values[i] + } +} + +func (c *context) QueryParam(name string) string { + if c.query == nil { + c.query = c.request.URL.Query() + } + return c.query.Get(name) +} + +func (c *context) QueryParams() url.Values { + if c.query == nil { + c.query = c.request.URL.Query() + } + return c.query +} + +func (c *context) QueryString() string { + return c.request.URL.RawQuery +} + +func (c *context) FormValue(name string) string { + return c.request.FormValue(name) +} + +func (c *context) FormParams() (url.Values, error) { + if strings.HasPrefix(c.request.Header.Get(HeaderContentType), MIMEMultipartForm) { + if err := c.request.ParseMultipartForm(defaultMemory); err != nil { + return nil, err + } + } else { + if err := c.request.ParseForm(); err != nil { + return nil, err + } + } + return c.request.Form, nil +} + +func (c *context) FormFile(name string) (*multipart.FileHeader, error) { + f, fh, err := c.request.FormFile(name) + if err != nil { + return nil, err + } + f.Close() + return fh, nil +} + +func (c *context) MultipartForm() (*multipart.Form, error) { + err := c.request.ParseMultipartForm(defaultMemory) + return c.request.MultipartForm, err +} + +func (c *context) Cookie(name string) (*http.Cookie, error) { + return c.request.Cookie(name) +} + +func (c *context) SetCookie(cookie *http.Cookie) { + http.SetCookie(c.Response(), cookie) +} + +func (c *context) Cookies() []*http.Cookie { + return c.request.Cookies() +} + +func (c *context) Get(key string) interface{} { + c.lock.RLock() + defer c.lock.RUnlock() + return c.store[key] +} + +func (c *context) Set(key string, val interface{}) { + c.lock.Lock() + defer c.lock.Unlock() + + if c.store == nil { + c.store = make(Map) + } + c.store[key] = val +} + +func (c *context) Bind(i interface{}) error { + return c.echo.Binder.Bind(i, c) +} + +func (c *context) Validate(i interface{}) error { + if c.echo.Validator == nil { + return ErrValidatorNotRegistered + } + return c.echo.Validator.Validate(i) +} + +func (c *context) Render(code int, name string, data interface{}) (err error) { + if c.echo.Renderer == nil { + return ErrRendererNotRegistered + } + buf := new(bytes.Buffer) + if err = c.echo.Renderer.Render(buf, name, data, c); err != nil { + return + } + return c.HTMLBlob(code, buf.Bytes()) +} + +func (c *context) HTML(code int, html string) (err error) { + return c.HTMLBlob(code, []byte(html)) +} + +func (c *context) HTMLBlob(code int, b []byte) (err error) { + return c.Blob(code, MIMETextHTMLCharsetUTF8, b) +} + +func (c *context) String(code int, s string) (err error) { + return c.Blob(code, MIMETextPlainCharsetUTF8, []byte(s)) +} + +func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error) { + indent := "" + if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty { + indent = defaultIndent + } + c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8) + c.response.WriteHeader(code) + if _, err = c.response.Write([]byte(callback + "(")); err != nil { + return + } + if err = c.echo.JSONSerializer.Serialize(c, i, indent); err != nil { + return + } + if _, err = c.response.Write([]byte(");")); err != nil { + return + } + return +} + +func (c *context) json(code int, i interface{}, indent string) error { + c.writeContentType(MIMEApplicationJSON) + c.response.Status = code + return c.echo.JSONSerializer.Serialize(c, i, indent) +} + +func (c *context) JSON(code int, i interface{}) (err error) { + indent := "" + if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty { + indent = defaultIndent + } + return c.json(code, i, indent) +} + +func (c *context) JSONPretty(code int, i interface{}, indent string) (err error) { + return c.json(code, i, indent) +} + +func (c *context) JSONBlob(code int, b []byte) (err error) { + return c.Blob(code, MIMEApplicationJSON, b) +} + +func (c *context) JSONP(code int, callback string, i interface{}) (err error) { + return c.jsonPBlob(code, callback, i) +} + +func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) { + c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8) + c.response.WriteHeader(code) + if _, err = c.response.Write([]byte(callback + "(")); err != nil { + return + } + if _, err = c.response.Write(b); err != nil { + return + } + _, err = c.response.Write([]byte(");")) + return +} + +func (c *context) xml(code int, i interface{}, indent string) (err error) { + c.writeContentType(MIMEApplicationXMLCharsetUTF8) + c.response.WriteHeader(code) + enc := xml.NewEncoder(c.response) + if indent != "" { + enc.Indent("", indent) + } + if _, err = c.response.Write([]byte(xml.Header)); err != nil { + return + } + return enc.Encode(i) +} + +func (c *context) XML(code int, i interface{}) (err error) { + indent := "" + if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty { + indent = defaultIndent + } + return c.xml(code, i, indent) +} + +func (c *context) XMLPretty(code int, i interface{}, indent string) (err error) { + return c.xml(code, i, indent) +} + +func (c *context) XMLBlob(code int, b []byte) (err error) { + c.writeContentType(MIMEApplicationXMLCharsetUTF8) + c.response.WriteHeader(code) + if _, err = c.response.Write([]byte(xml.Header)); err != nil { + return + } + _, err = c.response.Write(b) + return +} + +func (c *context) Blob(code int, contentType string, b []byte) (err error) { + c.writeContentType(contentType) + c.response.WriteHeader(code) + _, err = c.response.Write(b) + return +} + +func (c *context) Stream(code int, contentType string, r io.Reader) (err error) { + c.writeContentType(contentType) + c.response.WriteHeader(code) + _, err = io.Copy(c.response, r) + return +} + +func (c *context) Attachment(file, name string) error { + return c.contentDisposition(file, name, "attachment") +} + +func (c *context) Inline(file, name string) error { + return c.contentDisposition(file, name, "inline") +} + +var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") + +func (c *context) contentDisposition(file, name, dispositionType string) error { + c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf(`%s; filename="%s"`, dispositionType, quoteEscaper.Replace(name))) + return c.File(file) +} + +func (c *context) NoContent(code int) error { + c.response.WriteHeader(code) + return nil +} + +func (c *context) Redirect(code int, url string) error { + if code < 300 || code > 308 { + return ErrInvalidRedirectCode + } + c.response.Header().Set(HeaderLocation, url) + c.response.WriteHeader(code) + return nil +} + +func (c *context) Error(err error) { + c.echo.HTTPErrorHandler(err, c) +} + +func (c *context) Echo() *Echo { + return c.echo +} + +func (c *context) Handler() HandlerFunc { + return c.handler +} + +func (c *context) SetHandler(h HandlerFunc) { + c.handler = h +} + +func (c *context) Logger() Logger { + res := c.logger + if res != nil { + return res + } + return c.echo.Logger +} + +func (c *context) SetLogger(l Logger) { + c.logger = l +} + +func (c *context) Reset(r *http.Request, w http.ResponseWriter) { + c.request = r + c.response.reset(w) + c.query = nil + c.handler = NotFoundHandler + c.store = nil + c.path = "" + c.pnames = nil + c.logger = nil + // NOTE: Don't reset because it has to have length c.echo.maxParam (or bigger) at all times + for i := 0; i < len(c.pvalues); i++ { + c.pvalues[i] = "" + } +} diff --git a/vendor/github.com/labstack/echo/v4/context_fs.go b/vendor/github.com/labstack/echo/v4/context_fs.go new file mode 100644 index 00000000..1c25baf1 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/context_fs.go @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package echo + +import ( + "errors" + "io" + "io/fs" + "net/http" + "path/filepath" +) + +func (c *context) File(file string) error { + return fsFile(c, file, c.echo.Filesystem) +} + +// FileFS serves file from given file system. +// +// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary +// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths +// including `assets/images` as their prefix. +func (c *context) FileFS(file string, filesystem fs.FS) error { + return fsFile(c, file, filesystem) +} + +func fsFile(c Context, file string, filesystem fs.FS) error { + f, err := filesystem.Open(file) + if err != nil { + return ErrNotFound + } + defer f.Close() + + fi, _ := f.Stat() + if fi.IsDir() { + file = filepath.ToSlash(filepath.Join(file, indexPage)) // ToSlash is necessary for Windows. fs.Open and os.Open are different in that aspect. + f, err = filesystem.Open(file) + if err != nil { + return ErrNotFound + } + defer f.Close() + if fi, err = f.Stat(); err != nil { + return err + } + } + ff, ok := f.(io.ReadSeeker) + if !ok { + return errors.New("file does not implement io.ReadSeeker") + } + http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), ff) + return nil +} diff --git a/vendor/github.com/labstack/echo/v4/echo.go b/vendor/github.com/labstack/echo/v4/echo.go new file mode 100644 index 00000000..60f7061d --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/echo.go @@ -0,0 +1,1015 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +/* +Package echo implements high performance, minimalist Go web framework. + +Example: + + package main + + import ( + "net/http" + + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + ) + + // Handler + func hello(c echo.Context) error { + return c.String(http.StatusOK, "Hello, World!") + } + + func main() { + // Echo instance + e := echo.New() + + // Middleware + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + + // Routes + e.GET("/", hello) + + // Start server + e.Logger.Fatal(e.Start(":1323")) + } + +Learn more at https://echo.labstack.com +*/ +package echo + +import ( + stdContext "context" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + stdLog "log" + "net" + "net/http" + "os" + "reflect" + "runtime" + "sync" + "time" + + "github.com/labstack/gommon/color" + "github.com/labstack/gommon/log" + "golang.org/x/crypto/acme" + "golang.org/x/crypto/acme/autocert" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" +) + +// Echo is the top-level framework instance. +// +// Goroutine safety: Do not mutate Echo instance fields after server has started. Accessing these +// fields from handlers/middlewares and changing field values at the same time leads to data-races. +// Adding new routes after the server has been started is also not safe! +type Echo struct { + filesystem + common + // startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get + // listener address info (on which interface/port was listener bound) without having data races. + startupMutex sync.RWMutex + colorer *color.Color + + // premiddleware are middlewares that are run before routing is done. In case a pre-middleware returns + // an error the router is not executed and the request will end up in the global error handler. + premiddleware []MiddlewareFunc + middleware []MiddlewareFunc + maxParam *int + router *Router + routers map[string]*Router + pool sync.Pool + + StdLogger *stdLog.Logger + Server *http.Server + TLSServer *http.Server + Listener net.Listener + TLSListener net.Listener + AutoTLSManager autocert.Manager + HTTPErrorHandler HTTPErrorHandler + Binder Binder + JSONSerializer JSONSerializer + Validator Validator + Renderer Renderer + Logger Logger + IPExtractor IPExtractor + ListenerNetwork string + + // OnAddRouteHandler is called when Echo adds new route to specific host router. + OnAddRouteHandler func(host string, route Route, handler HandlerFunc, middleware []MiddlewareFunc) + DisableHTTP2 bool + Debug bool + HideBanner bool + HidePort bool +} + +// Route contains a handler and information for matching against requests. +type Route struct { + Method string `json:"method"` + Path string `json:"path"` + Name string `json:"name"` +} + +// HTTPError represents an error that occurred while handling a request. +type HTTPError struct { + Internal error `json:"-"` // Stores the error returned by an external dependency + Message interface{} `json:"message"` + Code int `json:"-"` +} + +// MiddlewareFunc defines a function to process middleware. +type MiddlewareFunc func(next HandlerFunc) HandlerFunc + +// HandlerFunc defines a function to serve HTTP requests. +type HandlerFunc func(c Context) error + +// HTTPErrorHandler is a centralized HTTP error handler. +type HTTPErrorHandler func(err error, c Context) + +// Validator is the interface that wraps the Validate function. +type Validator interface { + Validate(i interface{}) error +} + +// JSONSerializer is the interface that encodes and decodes JSON to and from interfaces. +type JSONSerializer interface { + Serialize(c Context, i interface{}, indent string) error + Deserialize(c Context, i interface{}) error +} + +// Map defines a generic map of type `map[string]interface{}`. +type Map map[string]interface{} + +// Common struct for Echo & Group. +type common struct{} + +// HTTP methods +// NOTE: Deprecated, please use the stdlib constants directly instead. +const ( + CONNECT = http.MethodConnect + DELETE = http.MethodDelete + GET = http.MethodGet + HEAD = http.MethodHead + OPTIONS = http.MethodOptions + PATCH = http.MethodPatch + POST = http.MethodPost + // PROPFIND = "PROPFIND" + PUT = http.MethodPut + TRACE = http.MethodTrace +) + +// MIME types +const ( + // MIMEApplicationJSON JavaScript Object Notation (JSON) https://www.rfc-editor.org/rfc/rfc8259 + MIMEApplicationJSON = "application/json" + // Deprecated: Please use MIMEApplicationJSON instead. JSON should be encoded using UTF-8 by default. + // No "charset" parameter is defined for this registration. + // Adding one really has no effect on compliant recipients. + // See RFC 8259, section 8.1. https://datatracker.ietf.org/doc/html/rfc8259#section-8.1 + MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8 + MIMEApplicationJavaScript = "application/javascript" + MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8 + MIMEApplicationXML = "application/xml" + MIMEApplicationXMLCharsetUTF8 = MIMEApplicationXML + "; " + charsetUTF8 + MIMETextXML = "text/xml" + MIMETextXMLCharsetUTF8 = MIMETextXML + "; " + charsetUTF8 + MIMEApplicationForm = "application/x-www-form-urlencoded" + MIMEApplicationProtobuf = "application/protobuf" + MIMEApplicationMsgpack = "application/msgpack" + MIMETextHTML = "text/html" + MIMETextHTMLCharsetUTF8 = MIMETextHTML + "; " + charsetUTF8 + MIMETextPlain = "text/plain" + MIMETextPlainCharsetUTF8 = MIMETextPlain + "; " + charsetUTF8 + MIMEMultipartForm = "multipart/form-data" + MIMEOctetStream = "application/octet-stream" +) + +const ( + charsetUTF8 = "charset=UTF-8" + // PROPFIND Method can be used on collection and property resources. + PROPFIND = "PROPFIND" + // REPORT Method can be used to get information about a resource, see rfc 3253 + REPORT = "REPORT" + // RouteNotFound is special method type for routes handling "route not found" (404) cases + RouteNotFound = "echo_route_not_found" +) + +// Headers +const ( + HeaderAccept = "Accept" + HeaderAcceptEncoding = "Accept-Encoding" + // HeaderAllow is the name of the "Allow" header field used to list the set of methods + // advertised as supported by the target resource. Returning an Allow header is mandatory + // for status 405 (method not found) and useful for the OPTIONS method in responses. + // See RFC 7231: https://datatracker.ietf.org/doc/html/rfc7231#section-7.4.1 + HeaderAllow = "Allow" + HeaderAuthorization = "Authorization" + HeaderContentDisposition = "Content-Disposition" + HeaderContentEncoding = "Content-Encoding" + HeaderContentLength = "Content-Length" + HeaderContentType = "Content-Type" + HeaderCookie = "Cookie" + HeaderSetCookie = "Set-Cookie" + HeaderIfModifiedSince = "If-Modified-Since" + HeaderLastModified = "Last-Modified" + HeaderLocation = "Location" + HeaderRetryAfter = "Retry-After" + HeaderUpgrade = "Upgrade" + HeaderVary = "Vary" + HeaderWWWAuthenticate = "WWW-Authenticate" + HeaderXForwardedFor = "X-Forwarded-For" + HeaderXForwardedProto = "X-Forwarded-Proto" + HeaderXForwardedProtocol = "X-Forwarded-Protocol" + HeaderXForwardedSsl = "X-Forwarded-Ssl" + HeaderXUrlScheme = "X-Url-Scheme" + HeaderXHTTPMethodOverride = "X-HTTP-Method-Override" + HeaderXRealIP = "X-Real-Ip" + HeaderXRequestID = "X-Request-Id" + HeaderXCorrelationID = "X-Correlation-Id" + HeaderXRequestedWith = "X-Requested-With" + HeaderServer = "Server" + HeaderOrigin = "Origin" + HeaderCacheControl = "Cache-Control" + HeaderConnection = "Connection" + + // Access control + HeaderAccessControlRequestMethod = "Access-Control-Request-Method" + HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers" + HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" + HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" + HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" + HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" + HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" + HeaderAccessControlMaxAge = "Access-Control-Max-Age" + + // Security + HeaderStrictTransportSecurity = "Strict-Transport-Security" + HeaderXContentTypeOptions = "X-Content-Type-Options" + HeaderXXSSProtection = "X-XSS-Protection" + HeaderXFrameOptions = "X-Frame-Options" + HeaderContentSecurityPolicy = "Content-Security-Policy" + HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" + HeaderXCSRFToken = "X-CSRF-Token" + HeaderReferrerPolicy = "Referrer-Policy" +) + +const ( + // Version of Echo + Version = "4.13.3" + website = "https://echo.labstack.com" + // http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo + banner = ` + ____ __ + / __/___/ / ___ + / _// __/ _ \/ _ \ +/___/\__/_//_/\___/ %s +High performance, minimalist Go web framework +%s +____________________________________O/_______ + O\ +` +) + +var methods = [...]string{ + http.MethodConnect, + http.MethodDelete, + http.MethodGet, + http.MethodHead, + http.MethodOptions, + http.MethodPatch, + http.MethodPost, + PROPFIND, + http.MethodPut, + http.MethodTrace, + REPORT, +} + +// Errors +var ( + ErrBadRequest = NewHTTPError(http.StatusBadRequest) // HTTP 400 Bad Request + ErrUnauthorized = NewHTTPError(http.StatusUnauthorized) // HTTP 401 Unauthorized + ErrPaymentRequired = NewHTTPError(http.StatusPaymentRequired) // HTTP 402 Payment Required + ErrForbidden = NewHTTPError(http.StatusForbidden) // HTTP 403 Forbidden + ErrNotFound = NewHTTPError(http.StatusNotFound) // HTTP 404 Not Found + ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed) // HTTP 405 Method Not Allowed + ErrNotAcceptable = NewHTTPError(http.StatusNotAcceptable) // HTTP 406 Not Acceptable + ErrProxyAuthRequired = NewHTTPError(http.StatusProxyAuthRequired) // HTTP 407 Proxy AuthRequired + ErrRequestTimeout = NewHTTPError(http.StatusRequestTimeout) // HTTP 408 Request Timeout + ErrConflict = NewHTTPError(http.StatusConflict) // HTTP 409 Conflict + ErrGone = NewHTTPError(http.StatusGone) // HTTP 410 Gone + ErrLengthRequired = NewHTTPError(http.StatusLengthRequired) // HTTP 411 Length Required + ErrPreconditionFailed = NewHTTPError(http.StatusPreconditionFailed) // HTTP 412 Precondition Failed + ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge) // HTTP 413 Payload Too Large + ErrRequestURITooLong = NewHTTPError(http.StatusRequestURITooLong) // HTTP 414 URI Too Long + ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType) // HTTP 415 Unsupported Media Type + ErrRequestedRangeNotSatisfiable = NewHTTPError(http.StatusRequestedRangeNotSatisfiable) // HTTP 416 Range Not Satisfiable + ErrExpectationFailed = NewHTTPError(http.StatusExpectationFailed) // HTTP 417 Expectation Failed + ErrTeapot = NewHTTPError(http.StatusTeapot) // HTTP 418 I'm a teapot + ErrMisdirectedRequest = NewHTTPError(http.StatusMisdirectedRequest) // HTTP 421 Misdirected Request + ErrUnprocessableEntity = NewHTTPError(http.StatusUnprocessableEntity) // HTTP 422 Unprocessable Entity + ErrLocked = NewHTTPError(http.StatusLocked) // HTTP 423 Locked + ErrFailedDependency = NewHTTPError(http.StatusFailedDependency) // HTTP 424 Failed Dependency + ErrTooEarly = NewHTTPError(http.StatusTooEarly) // HTTP 425 Too Early + ErrUpgradeRequired = NewHTTPError(http.StatusUpgradeRequired) // HTTP 426 Upgrade Required + ErrPreconditionRequired = NewHTTPError(http.StatusPreconditionRequired) // HTTP 428 Precondition Required + ErrTooManyRequests = NewHTTPError(http.StatusTooManyRequests) // HTTP 429 Too Many Requests + ErrRequestHeaderFieldsTooLarge = NewHTTPError(http.StatusRequestHeaderFieldsTooLarge) // HTTP 431 Request Header Fields Too Large + ErrUnavailableForLegalReasons = NewHTTPError(http.StatusUnavailableForLegalReasons) // HTTP 451 Unavailable For Legal Reasons + ErrInternalServerError = NewHTTPError(http.StatusInternalServerError) // HTTP 500 Internal Server Error + ErrNotImplemented = NewHTTPError(http.StatusNotImplemented) // HTTP 501 Not Implemented + ErrBadGateway = NewHTTPError(http.StatusBadGateway) // HTTP 502 Bad Gateway + ErrServiceUnavailable = NewHTTPError(http.StatusServiceUnavailable) // HTTP 503 Service Unavailable + ErrGatewayTimeout = NewHTTPError(http.StatusGatewayTimeout) // HTTP 504 Gateway Timeout + ErrHTTPVersionNotSupported = NewHTTPError(http.StatusHTTPVersionNotSupported) // HTTP 505 HTTP Version Not Supported + ErrVariantAlsoNegotiates = NewHTTPError(http.StatusVariantAlsoNegotiates) // HTTP 506 Variant Also Negotiates + ErrInsufficientStorage = NewHTTPError(http.StatusInsufficientStorage) // HTTP 507 Insufficient Storage + ErrLoopDetected = NewHTTPError(http.StatusLoopDetected) // HTTP 508 Loop Detected + ErrNotExtended = NewHTTPError(http.StatusNotExtended) // HTTP 510 Not Extended + ErrNetworkAuthenticationRequired = NewHTTPError(http.StatusNetworkAuthenticationRequired) // HTTP 511 Network Authentication Required + + ErrValidatorNotRegistered = errors.New("validator not registered") + ErrRendererNotRegistered = errors.New("renderer not registered") + ErrInvalidRedirectCode = errors.New("invalid redirect status code") + ErrCookieNotFound = errors.New("cookie not found") + ErrInvalidCertOrKeyType = errors.New("invalid cert or key type, must be string or []byte") + ErrInvalidListenerNetwork = errors.New("invalid listener network") +) + +// NotFoundHandler is the handler that router uses in case there was no matching route found. Returns an error that results +// HTTP 404 status code. +var NotFoundHandler = func(c Context) error { + return ErrNotFound +} + +// MethodNotAllowedHandler is the handler thar router uses in case there was no matching route found but there was +// another matching routes for that requested URL. Returns an error that results HTTP 405 Method Not Allowed status code. +var MethodNotAllowedHandler = func(c Context) error { + // See RFC 7231 section 7.4.1: An origin server MUST generate an Allow field in a 405 (Method Not Allowed) + // response and MAY do so in any other response. For disabled resources an empty Allow header may be returned + routerAllowMethods, ok := c.Get(ContextKeyHeaderAllow).(string) + if ok && routerAllowMethods != "" { + c.Response().Header().Set(HeaderAllow, routerAllowMethods) + } + return ErrMethodNotAllowed +} + +// New creates an instance of Echo. +func New() (e *Echo) { + e = &Echo{ + filesystem: createFilesystem(), + Server: new(http.Server), + TLSServer: new(http.Server), + AutoTLSManager: autocert.Manager{ + Prompt: autocert.AcceptTOS, + }, + Logger: log.New("echo"), + colorer: color.New(), + maxParam: new(int), + ListenerNetwork: "tcp", + } + e.Server.Handler = e + e.TLSServer.Handler = e + e.HTTPErrorHandler = e.DefaultHTTPErrorHandler + e.Binder = &DefaultBinder{} + e.JSONSerializer = &DefaultJSONSerializer{} + e.Logger.SetLevel(log.ERROR) + e.StdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0) + e.pool.New = func() interface{} { + return e.NewContext(nil, nil) + } + e.router = NewRouter(e) + e.routers = map[string]*Router{} + return +} + +// NewContext returns a Context instance. +func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context { + return &context{ + request: r, + response: NewResponse(w, e), + store: make(Map), + echo: e, + pvalues: make([]string, *e.maxParam), + handler: NotFoundHandler, + } +} + +// Router returns the default router. +func (e *Echo) Router() *Router { + return e.router +} + +// Routers returns the map of host => router. +func (e *Echo) Routers() map[string]*Router { + return e.routers +} + +// DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response +// with status code. +// +// NOTE: In case errors happens in middleware call-chain that is returning from handler (which did not return an error). +// When handler has already sent response (ala c.JSON()) and there is error in middleware that is returning from +// handler. Then the error that global error handler received will be ignored because we have already "committed" the +// response and status code header has been sent to the client. +func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) { + + if c.Response().Committed { + return + } + + he, ok := err.(*HTTPError) + if ok { + if he.Internal != nil { + if herr, ok := he.Internal.(*HTTPError); ok { + he = herr + } + } + } else { + he = &HTTPError{ + Code: http.StatusInternalServerError, + Message: http.StatusText(http.StatusInternalServerError), + } + } + + // Issue #1426 + code := he.Code + message := he.Message + + switch m := he.Message.(type) { + case string: + if e.Debug { + message = Map{"message": m, "error": err.Error()} + } else { + message = Map{"message": m} + } + case json.Marshaler: + // do nothing - this type knows how to format itself to JSON + case error: + message = Map{"message": m.Error()} + } + + // Send response + if c.Request().Method == http.MethodHead { // Issue #608 + err = c.NoContent(he.Code) + } else { + err = c.JSON(code, message) + } + if err != nil { + e.Logger.Error(err) + } +} + +// Pre adds middleware to the chain which is run before router. +func (e *Echo) Pre(middleware ...MiddlewareFunc) { + e.premiddleware = append(e.premiddleware, middleware...) +} + +// Use adds middleware to the chain which is run after router. +func (e *Echo) Use(middleware ...MiddlewareFunc) { + e.middleware = append(e.middleware, middleware...) +} + +// CONNECT registers a new CONNECT route for a path with matching handler in the +// router with optional route-level middleware. +func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(http.MethodConnect, path, h, m...) +} + +// DELETE registers a new DELETE route for a path with matching handler in the router +// with optional route-level middleware. +func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(http.MethodDelete, path, h, m...) +} + +// GET registers a new GET route for a path with matching handler in the router +// with optional route-level middleware. +func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(http.MethodGet, path, h, m...) +} + +// HEAD registers a new HEAD route for a path with matching handler in the +// router with optional route-level middleware. +func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(http.MethodHead, path, h, m...) +} + +// OPTIONS registers a new OPTIONS route for a path with matching handler in the +// router with optional route-level middleware. +func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(http.MethodOptions, path, h, m...) +} + +// PATCH registers a new PATCH route for a path with matching handler in the +// router with optional route-level middleware. +func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(http.MethodPatch, path, h, m...) +} + +// POST registers a new POST route for a path with matching handler in the +// router with optional route-level middleware. +func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(http.MethodPost, path, h, m...) +} + +// PUT registers a new PUT route for a path with matching handler in the +// router with optional route-level middleware. +func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(http.MethodPut, path, h, m...) +} + +// TRACE registers a new TRACE route for a path with matching handler in the +// router with optional route-level middleware. +func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(http.MethodTrace, path, h, m...) +} + +// RouteNotFound registers a special-case route which is executed when no other route is found (i.e. HTTP 404 cases) +// for current request URL. +// Path supports static and named/any parameters just like other http method is defined. Generally path is ended with +// wildcard/match-any character (`/*`, `/download/*` etc). +// +// Example: `e.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })` +func (e *Echo) RouteNotFound(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(RouteNotFound, path, h, m...) +} + +// Any registers a new route for all HTTP methods (supported by Echo) and path with matching handler +// in the router with optional route-level middleware. +// +// Note: this method only adds specific set of supported HTTP methods as handler and is not true +// "catch-any-arbitrary-method" way of matching requests. +func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route { + routes := make([]*Route, len(methods)) + for i, m := range methods { + routes[i] = e.Add(m, path, handler, middleware...) + } + return routes +} + +// Match registers a new route for multiple HTTP methods and path with matching +// handler in the router with optional route-level middleware. +func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route { + routes := make([]*Route, len(methods)) + for i, m := range methods { + routes[i] = e.Add(m, path, handler, middleware...) + } + return routes +} + +func (common) file(path, file string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route, + m ...MiddlewareFunc) *Route { + return get(path, func(c Context) error { + return c.File(file) + }, m...) +} + +// File registers a new route with path to serve a static file with optional route-level middleware. +func (e *Echo) File(path, file string, m ...MiddlewareFunc) *Route { + return e.file(path, file, e.GET, m...) +} + +func (e *Echo) add(host, method, path string, handler HandlerFunc, middlewares ...MiddlewareFunc) *Route { + router := e.findRouter(host) + //FIXME: when handler+middleware are both nil ... make it behave like handler removal + name := handlerName(handler) + route := router.add(method, path, name, func(c Context) error { + h := applyMiddleware(handler, middlewares...) + return h(c) + }) + + if e.OnAddRouteHandler != nil { + e.OnAddRouteHandler(host, *route, handler, middlewares) + } + + return route +} + +// Add registers a new route for an HTTP method and path with matching handler +// in the router with optional route-level middleware. +func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route { + return e.add("", method, path, handler, middleware...) +} + +// Host creates a new router group for the provided host and optional host-level middleware. +func (e *Echo) Host(name string, m ...MiddlewareFunc) (g *Group) { + e.routers[name] = NewRouter(e) + g = &Group{host: name, echo: e} + g.Use(m...) + return +} + +// Group creates a new router group with prefix and optional group-level middleware. +func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) { + g = &Group{prefix: prefix, echo: e} + g.Use(m...) + return +} + +// URI generates an URI from handler. +func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string { + name := handlerName(handler) + return e.Reverse(name, params...) +} + +// URL is an alias for `URI` function. +func (e *Echo) URL(h HandlerFunc, params ...interface{}) string { + return e.URI(h, params...) +} + +// Reverse generates a URL from route name and provided parameters. +func (e *Echo) Reverse(name string, params ...interface{}) string { + return e.router.Reverse(name, params...) +} + +// Routes returns the registered routes for default router. +// In case when Echo serves multiple hosts/domains use `e.Routers()["domain2.site"].Routes()` to get specific host routes. +func (e *Echo) Routes() []*Route { + return e.router.Routes() +} + +// AcquireContext returns an empty `Context` instance from the pool. +// You must return the context by calling `ReleaseContext()`. +func (e *Echo) AcquireContext() Context { + return e.pool.Get().(Context) +} + +// ReleaseContext returns the `Context` instance back to the pool. +// You must call it after `AcquireContext()`. +func (e *Echo) ReleaseContext(c Context) { + e.pool.Put(c) +} + +// ServeHTTP implements `http.Handler` interface, which serves HTTP requests. +func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Acquire context + c := e.pool.Get().(*context) + c.Reset(r, w) + var h HandlerFunc + + if e.premiddleware == nil { + e.findRouter(r.Host).Find(r.Method, GetPath(r), c) + h = c.Handler() + h = applyMiddleware(h, e.middleware...) + } else { + h = func(c Context) error { + e.findRouter(r.Host).Find(r.Method, GetPath(r), c) + h := c.Handler() + h = applyMiddleware(h, e.middleware...) + return h(c) + } + h = applyMiddleware(h, e.premiddleware...) + } + + // Execute chain + if err := h(c); err != nil { + e.HTTPErrorHandler(err, c) + } + + // Release context + e.pool.Put(c) +} + +// Start starts an HTTP server. +func (e *Echo) Start(address string) error { + e.startupMutex.Lock() + e.Server.Addr = address + if err := e.configureServer(e.Server); err != nil { + e.startupMutex.Unlock() + return err + } + e.startupMutex.Unlock() + return e.Server.Serve(e.Listener) +} + +// StartTLS starts an HTTPS server. +// If `certFile` or `keyFile` is `string` the values are treated as file paths. +// If `certFile` or `keyFile` is `[]byte` the values are treated as the certificate or key as-is. +func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err error) { + e.startupMutex.Lock() + var cert []byte + if cert, err = filepathOrContent(certFile); err != nil { + e.startupMutex.Unlock() + return + } + + var key []byte + if key, err = filepathOrContent(keyFile); err != nil { + e.startupMutex.Unlock() + return + } + + s := e.TLSServer + s.TLSConfig = new(tls.Config) + s.TLSConfig.Certificates = make([]tls.Certificate, 1) + if s.TLSConfig.Certificates[0], err = tls.X509KeyPair(cert, key); err != nil { + e.startupMutex.Unlock() + return + } + + e.configureTLS(address) + if err := e.configureServer(s); err != nil { + e.startupMutex.Unlock() + return err + } + e.startupMutex.Unlock() + return s.Serve(e.TLSListener) +} + +func filepathOrContent(fileOrContent interface{}) (content []byte, err error) { + switch v := fileOrContent.(type) { + case string: + return os.ReadFile(v) + case []byte: + return v, nil + default: + return nil, ErrInvalidCertOrKeyType + } +} + +// StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org. +func (e *Echo) StartAutoTLS(address string) error { + e.startupMutex.Lock() + s := e.TLSServer + s.TLSConfig = new(tls.Config) + s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate + s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, acme.ALPNProto) + + e.configureTLS(address) + if err := e.configureServer(s); err != nil { + e.startupMutex.Unlock() + return err + } + e.startupMutex.Unlock() + return s.Serve(e.TLSListener) +} + +func (e *Echo) configureTLS(address string) { + s := e.TLSServer + s.Addr = address + if !e.DisableHTTP2 { + s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "h2") + } +} + +// StartServer starts a custom http server. +func (e *Echo) StartServer(s *http.Server) (err error) { + e.startupMutex.Lock() + if err := e.configureServer(s); err != nil { + e.startupMutex.Unlock() + return err + } + if s.TLSConfig != nil { + e.startupMutex.Unlock() + return s.Serve(e.TLSListener) + } + e.startupMutex.Unlock() + return s.Serve(e.Listener) +} + +func (e *Echo) configureServer(s *http.Server) error { + // Setup + e.colorer.SetOutput(e.Logger.Output()) + s.ErrorLog = e.StdLogger + s.Handler = e + if e.Debug { + e.Logger.SetLevel(log.DEBUG) + } + + if !e.HideBanner { + e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website)) + } + + if s.TLSConfig == nil { + if e.Listener == nil { + l, err := newListener(s.Addr, e.ListenerNetwork) + if err != nil { + return err + } + e.Listener = l + } + if !e.HidePort { + e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr())) + } + return nil + } + if e.TLSListener == nil { + l, err := newListener(s.Addr, e.ListenerNetwork) + if err != nil { + return err + } + e.TLSListener = tls.NewListener(l, s.TLSConfig) + } + if !e.HidePort { + e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr())) + } + return nil +} + +// ListenerAddr returns net.Addr for Listener +func (e *Echo) ListenerAddr() net.Addr { + e.startupMutex.RLock() + defer e.startupMutex.RUnlock() + if e.Listener == nil { + return nil + } + return e.Listener.Addr() +} + +// TLSListenerAddr returns net.Addr for TLSListener +func (e *Echo) TLSListenerAddr() net.Addr { + e.startupMutex.RLock() + defer e.startupMutex.RUnlock() + if e.TLSListener == nil { + return nil + } + return e.TLSListener.Addr() +} + +// StartH2CServer starts a custom http/2 server with h2c (HTTP/2 Cleartext). +func (e *Echo) StartH2CServer(address string, h2s *http2.Server) error { + e.startupMutex.Lock() + // Setup + s := e.Server + s.Addr = address + e.colorer.SetOutput(e.Logger.Output()) + s.ErrorLog = e.StdLogger + s.Handler = h2c.NewHandler(e, h2s) + if e.Debug { + e.Logger.SetLevel(log.DEBUG) + } + + if !e.HideBanner { + e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website)) + } + + if e.Listener == nil { + l, err := newListener(s.Addr, e.ListenerNetwork) + if err != nil { + e.startupMutex.Unlock() + return err + } + e.Listener = l + } + if !e.HidePort { + e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr())) + } + e.startupMutex.Unlock() + return s.Serve(e.Listener) +} + +// Close immediately stops the server. +// It internally calls `http.Server#Close()`. +func (e *Echo) Close() error { + e.startupMutex.Lock() + defer e.startupMutex.Unlock() + if err := e.TLSServer.Close(); err != nil { + return err + } + return e.Server.Close() +} + +// Shutdown stops the server gracefully. +// It internally calls `http.Server#Shutdown()`. +func (e *Echo) Shutdown(ctx stdContext.Context) error { + e.startupMutex.Lock() + defer e.startupMutex.Unlock() + if err := e.TLSServer.Shutdown(ctx); err != nil { + return err + } + return e.Server.Shutdown(ctx) +} + +// NewHTTPError creates a new HTTPError instance. +func NewHTTPError(code int, message ...interface{}) *HTTPError { + he := &HTTPError{Code: code, Message: http.StatusText(code)} + if len(message) > 0 { + he.Message = message[0] + } + return he +} + +// Error makes it compatible with `error` interface. +func (he *HTTPError) Error() string { + if he.Internal == nil { + return fmt.Sprintf("code=%d, message=%v", he.Code, he.Message) + } + return fmt.Sprintf("code=%d, message=%v, internal=%v", he.Code, he.Message, he.Internal) +} + +// SetInternal sets error to HTTPError.Internal +func (he *HTTPError) SetInternal(err error) *HTTPError { + he.Internal = err + return he +} + +// WithInternal returns clone of HTTPError with err set to HTTPError.Internal field +func (he *HTTPError) WithInternal(err error) *HTTPError { + return &HTTPError{ + Code: he.Code, + Message: he.Message, + Internal: err, + } +} + +// Unwrap satisfies the Go 1.13 error wrapper interface. +func (he *HTTPError) Unwrap() error { + return he.Internal +} + +// WrapHandler wraps `http.Handler` into `echo.HandlerFunc`. +func WrapHandler(h http.Handler) HandlerFunc { + return func(c Context) error { + h.ServeHTTP(c.Response(), c.Request()) + return nil + } +} + +// WrapMiddleware wraps `func(http.Handler) http.Handler` into `echo.MiddlewareFunc` +func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc { + return func(next HandlerFunc) HandlerFunc { + return func(c Context) (err error) { + m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c.SetRequest(r) + c.SetResponse(NewResponse(w, c.Echo())) + err = next(c) + })).ServeHTTP(c.Response(), c.Request()) + return + } + } +} + +// GetPath returns RawPath, if it's empty returns Path from URL +// Difference between RawPath and Path is: +// - Path is where request path is stored. Value is stored in decoded form: /%47%6f%2f becomes /Go/. +// - RawPath is an optional field which only gets set if the default encoding is different from Path. +func GetPath(r *http.Request) string { + path := r.URL.RawPath + if path == "" { + path = r.URL.Path + } + return path +} + +func (e *Echo) findRouter(host string) *Router { + if len(e.routers) > 0 { + if r, ok := e.routers[host]; ok { + return r + } + } + return e.router +} + +func handlerName(h HandlerFunc) string { + t := reflect.ValueOf(h).Type() + if t.Kind() == reflect.Func { + return runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name() + } + return t.String() +} + +// // PathUnescape is wraps `url.PathUnescape` +// func PathUnescape(s string) (string, error) { +// return url.PathUnescape(s) +// } + +// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted +// connections. It's used by ListenAndServe and ListenAndServeTLS so +// dead TCP connections (e.g. closing laptop mid-download) eventually +// go away. +type tcpKeepAliveListener struct { + *net.TCPListener +} + +func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { + if c, err = ln.AcceptTCP(); err != nil { + return + } else if err = c.(*net.TCPConn).SetKeepAlive(true); err != nil { + return + } + // Ignore error from setting the KeepAlivePeriod as some systems, such as + // OpenBSD, do not support setting TCP_USER_TIMEOUT on IPPROTO_TCP + _ = c.(*net.TCPConn).SetKeepAlivePeriod(3 * time.Minute) + return +} + +func newListener(address, network string) (*tcpKeepAliveListener, error) { + if network != "tcp" && network != "tcp4" && network != "tcp6" { + return nil, ErrInvalidListenerNetwork + } + l, err := net.Listen(network, address) + if err != nil { + return nil, err + } + return &tcpKeepAliveListener{l.(*net.TCPListener)}, nil +} + +func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) HandlerFunc { + for i := len(middleware) - 1; i >= 0; i-- { + h = middleware[i](h) + } + return h +} diff --git a/vendor/github.com/labstack/echo/v4/echo_fs.go b/vendor/github.com/labstack/echo/v4/echo_fs.go new file mode 100644 index 00000000..0ffc4b0b --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/echo_fs.go @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package echo + +import ( + "fmt" + "io/fs" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" +) + +type filesystem struct { + // Filesystem is file system used by Static and File handlers to access files. + // Defaults to os.DirFS(".") + // + // When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary + // prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths + // including `assets/images` as their prefix. + Filesystem fs.FS +} + +func createFilesystem() filesystem { + return filesystem{ + Filesystem: newDefaultFS(), + } +} + +// Static registers a new route with path prefix to serve static files from the provided root directory. +func (e *Echo) Static(pathPrefix, fsRoot string) *Route { + subFs := MustSubFS(e.Filesystem, fsRoot) + return e.Add( + http.MethodGet, + pathPrefix+"*", + StaticDirectoryHandler(subFs, false), + ) +} + +// StaticFS registers a new route with path prefix to serve static files from the provided file system. +// +// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary +// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths +// including `assets/images` as their prefix. +func (e *Echo) StaticFS(pathPrefix string, filesystem fs.FS) *Route { + return e.Add( + http.MethodGet, + pathPrefix+"*", + StaticDirectoryHandler(filesystem, false), + ) +} + +// StaticDirectoryHandler creates handler function to serve files from provided file system +// When disablePathUnescaping is set then file name from path is not unescaped and is served as is. +func StaticDirectoryHandler(fileSystem fs.FS, disablePathUnescaping bool) HandlerFunc { + return func(c Context) error { + p := c.Param("*") + if !disablePathUnescaping { // when router is already unescaping we do not want to do is twice + tmpPath, err := url.PathUnescape(p) + if err != nil { + return fmt.Errorf("failed to unescape path variable: %w", err) + } + p = tmpPath + } + + // fs.FS.Open() already assumes that file names are relative to FS root path and considers name with prefix `/` as invalid + name := filepath.ToSlash(filepath.Clean(strings.TrimPrefix(p, "/"))) + fi, err := fs.Stat(fileSystem, name) + if err != nil { + return ErrNotFound + } + + // If the request is for a directory and does not end with "/" + p = c.Request().URL.Path // path must not be empty. + if fi.IsDir() && len(p) > 0 && p[len(p)-1] != '/' { + // Redirect to ends with "/" + return c.Redirect(http.StatusMovedPermanently, sanitizeURI(p+"/")) + } + return fsFile(c, name, fileSystem) + } +} + +// FileFS registers a new route with path to serve file from the provided file system. +func (e *Echo) FileFS(path, file string, filesystem fs.FS, m ...MiddlewareFunc) *Route { + return e.GET(path, StaticFileHandler(file, filesystem), m...) +} + +// StaticFileHandler creates handler function to serve file from provided file system +func StaticFileHandler(file string, filesystem fs.FS) HandlerFunc { + return func(c Context) error { + return fsFile(c, file, filesystem) + } +} + +// defaultFS exists to preserve pre v4.7.0 behaviour where files were open by `os.Open`. +// v4.7 introduced `echo.Filesystem` field which is Go1.16+ `fs.Fs` interface. +// Difference between `os.Open` and `fs.Open` is that FS does not allow opening path that start with `.`, `..` or `/` +// etc. For example previously you could have `../images` in your application but `fs := os.DirFS("./")` would not +// allow you to use `fs.Open("../images")` and this would break all old applications that rely on being able to +// traverse up from current executable run path. +// NB: private because you really should use fs.FS implementation instances +type defaultFS struct { + fs fs.FS + prefix string +} + +func newDefaultFS() *defaultFS { + dir, _ := os.Getwd() + return &defaultFS{ + prefix: dir, + fs: nil, + } +} + +func (fs defaultFS) Open(name string) (fs.File, error) { + if fs.fs == nil { + return os.Open(name) + } + return fs.fs.Open(name) +} + +func subFS(currentFs fs.FS, root string) (fs.FS, error) { + root = filepath.ToSlash(filepath.Clean(root)) // note: fs.FS operates only with slashes. `ToSlash` is necessary for Windows + if dFS, ok := currentFs.(*defaultFS); ok { + // we need to make exception for `defaultFS` instances as it interprets root prefix differently from fs.FS. + // fs.Fs.Open does not like relative paths ("./", "../") and absolute paths at all but prior echo.Filesystem we + // were able to use paths like `./myfile.log`, `/etc/hosts` and these would work fine with `os.Open` but not with fs.Fs + if !filepath.IsAbs(root) { + root = filepath.Join(dFS.prefix, root) + } + return &defaultFS{ + prefix: root, + fs: os.DirFS(root), + }, nil + } + return fs.Sub(currentFs, root) +} + +// MustSubFS creates sub FS from current filesystem or panic on failure. +// Panic happens when `fsRoot` contains invalid path according to `fs.ValidPath` rules. +// +// MustSubFS is helpful when dealing with `embed.FS` because for example `//go:embed assets/images` embeds files with +// paths including `assets/images` as their prefix. In that case use `fs := echo.MustSubFS(fs, "rootDirectory") to +// create sub fs which uses necessary prefix for directory path. +func MustSubFS(currentFs fs.FS, fsRoot string) fs.FS { + subFs, err := subFS(currentFs, fsRoot) + if err != nil { + panic(fmt.Errorf("can not create sub FS, invalid root given, err: %w", err)) + } + return subFs +} + +func sanitizeURI(uri string) string { + // double slash `\\`, `//` or even `\/` is absolute uri for browsers and by redirecting request to that uri + // we are vulnerable to open redirect attack. so replace all slashes from the beginning with single slash + if len(uri) > 1 && (uri[0] == '\\' || uri[0] == '/') && (uri[1] == '\\' || uri[1] == '/') { + uri = "/" + strings.TrimLeft(uri, `/\`) + } + return uri +} diff --git a/vendor/github.com/labstack/echo/v4/group.go b/vendor/github.com/labstack/echo/v4/group.go new file mode 100644 index 00000000..cb37b123 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/group.go @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package echo + +import ( + "net/http" +) + +// Group is a set of sub-routes for a specified route. It can be used for inner +// routes that share a common middleware or functionality that should be separate +// from the parent echo instance while still inheriting from it. +type Group struct { + common + host string + prefix string + echo *Echo + middleware []MiddlewareFunc +} + +// Use implements `Echo#Use()` for sub-routes within the Group. +func (g *Group) Use(middleware ...MiddlewareFunc) { + g.middleware = append(g.middleware, middleware...) + if len(g.middleware) == 0 { + return + } + // group level middlewares are different from Echo `Pre` and `Use` middlewares (those are global). Group level middlewares + // are only executed if they are added to the Router with route. + // So we register catch all route (404 is a safe way to emulate route match) for this group and now during routing the + // Router would find route to match our request path and therefore guarantee the middleware(s) will get executed. + g.RouteNotFound("", NotFoundHandler) + g.RouteNotFound("/*", NotFoundHandler) +} + +// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group. +func (g *Group) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(http.MethodConnect, path, h, m...) +} + +// DELETE implements `Echo#DELETE()` for sub-routes within the Group. +func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(http.MethodDelete, path, h, m...) +} + +// GET implements `Echo#GET()` for sub-routes within the Group. +func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(http.MethodGet, path, h, m...) +} + +// HEAD implements `Echo#HEAD()` for sub-routes within the Group. +func (g *Group) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(http.MethodHead, path, h, m...) +} + +// OPTIONS implements `Echo#OPTIONS()` for sub-routes within the Group. +func (g *Group) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(http.MethodOptions, path, h, m...) +} + +// PATCH implements `Echo#PATCH()` for sub-routes within the Group. +func (g *Group) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(http.MethodPatch, path, h, m...) +} + +// POST implements `Echo#POST()` for sub-routes within the Group. +func (g *Group) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(http.MethodPost, path, h, m...) +} + +// PUT implements `Echo#PUT()` for sub-routes within the Group. +func (g *Group) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(http.MethodPut, path, h, m...) +} + +// TRACE implements `Echo#TRACE()` for sub-routes within the Group. +func (g *Group) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(http.MethodTrace, path, h, m...) +} + +// Any implements `Echo#Any()` for sub-routes within the Group. +func (g *Group) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route { + routes := make([]*Route, len(methods)) + for i, m := range methods { + routes[i] = g.Add(m, path, handler, middleware...) + } + return routes +} + +// Match implements `Echo#Match()` for sub-routes within the Group. +func (g *Group) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route { + routes := make([]*Route, len(methods)) + for i, m := range methods { + routes[i] = g.Add(m, path, handler, middleware...) + } + return routes +} + +// Group creates a new sub-group with prefix and optional sub-group-level middleware. +func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) (sg *Group) { + m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware)) + m = append(m, g.middleware...) + m = append(m, middleware...) + sg = g.echo.Group(g.prefix+prefix, m...) + sg.host = g.host + return +} + +// File implements `Echo#File()` for sub-routes within the Group. +func (g *Group) File(path, file string) { + g.file(path, file, g.GET) +} + +// RouteNotFound implements `Echo#RouteNotFound()` for sub-routes within the Group. +// +// Example: `g.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })` +func (g *Group) RouteNotFound(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return g.Add(RouteNotFound, path, h, m...) +} + +// Add implements `Echo#Add()` for sub-routes within the Group. +func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route { + // Combine into a new slice to avoid accidentally passing the same slice for + // multiple routes, which would lead to later add() calls overwriting the + // middleware from earlier calls. + m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware)) + m = append(m, g.middleware...) + m = append(m, middleware...) + return g.echo.add(g.host, method, g.prefix+path, handler, m...) +} diff --git a/vendor/github.com/labstack/echo/v4/group_fs.go b/vendor/github.com/labstack/echo/v4/group_fs.go new file mode 100644 index 00000000..c1b7ec2d --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/group_fs.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package echo + +import ( + "io/fs" + "net/http" +) + +// Static implements `Echo#Static()` for sub-routes within the Group. +func (g *Group) Static(pathPrefix, fsRoot string) { + subFs := MustSubFS(g.echo.Filesystem, fsRoot) + g.StaticFS(pathPrefix, subFs) +} + +// StaticFS implements `Echo#StaticFS()` for sub-routes within the Group. +// +// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary +// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths +// including `assets/images` as their prefix. +func (g *Group) StaticFS(pathPrefix string, filesystem fs.FS) { + g.Add( + http.MethodGet, + pathPrefix+"*", + StaticDirectoryHandler(filesystem, false), + ) +} + +// FileFS implements `Echo#FileFS()` for sub-routes within the Group. +func (g *Group) FileFS(path, file string, filesystem fs.FS, m ...MiddlewareFunc) *Route { + return g.GET(path, StaticFileHandler(file, filesystem), m...) +} diff --git a/vendor/github.com/labstack/echo/v4/ip.go b/vendor/github.com/labstack/echo/v4/ip.go new file mode 100644 index 00000000..393a6c2d --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/ip.go @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package echo + +import ( + "net" + "net/http" + "strings" +) + +/** +By: https://github.com/tmshn (See: https://github.com/labstack/echo/pull/1478 , https://github.com/labstack/echox/pull/134 ) +Source: https://echo.labstack.com/guide/ip-address/ + +IP address plays fundamental role in HTTP; it's used for access control, auditing, geo-based access analysis and more. +Echo provides handy method [`Context#RealIP()`](https://godoc.org/github.com/labstack/echo#Context) for that. + +However, it is not trivial to retrieve the _real_ IP address from requests especially when you put L7 proxies before the application. +In such situation, _real_ IP needs to be relayed on HTTP layer from proxies to your app, but you must not trust HTTP headers unconditionally. +Otherwise, you might give someone a chance of deceiving you. **A security risk!** + +To retrieve IP address reliably/securely, you must let your application be aware of the entire architecture of your infrastructure. +In Echo, this can be done by configuring `Echo#IPExtractor` appropriately. +This guides show you why and how. + +> Note: if you dont' set `Echo#IPExtractor` explicitly, Echo fallback to legacy behavior, which is not a good choice. + +Let's start from two questions to know the right direction: + +1. Do you put any HTTP (L7) proxy in front of the application? + - It includes both cloud solutions (such as AWS ALB or GCP HTTP LB) and OSS ones (such as Nginx, Envoy or Istio ingress gateway). +2. If yes, what HTTP header do your proxies use to pass client IP to the application? + +## Case 1. With no proxy + +If you put no proxy (e.g.: directory facing to the internet), all you need to (and have to) see is IP address from network layer. +Any HTTP header is untrustable because the clients have full control what headers to be set. + +In this case, use `echo.ExtractIPDirect()`. + +```go +e.IPExtractor = echo.ExtractIPDirect() +``` + +## Case 2. With proxies using `X-Forwarded-For` header + +[`X-Forwared-For` (XFF)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) is the popular header +to relay clients' IP addresses. +At each hop on the proxies, they append the request IP address at the end of the header. + +Following example diagram illustrates this behavior. + +```text +┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ +│ "Origin" │───────────>│ Proxy 1 │───────────>│ Proxy 2 │───────────>│ Your app │ +│ (IP: a) │ │ (IP: b) │ │ (IP: c) │ │ │ +└──────────┘ └──────────┘ └──────────┘ └──────────┘ + +Case 1. +XFF: "" "a" "a, b" + ~~~~~~ +Case 2. +XFF: "x" "x, a" "x, a, b" + ~~~~~~~~~ + ↑ What your app will see +``` + +In this case, use **first _untrustable_ IP reading from right**. Never use first one reading from left, as it is +configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructure". +In above example, if `b` and `c` are trustable, the IP address of the client is `a` for both cases, never be `x`. + +In Echo, use `ExtractIPFromXFFHeader(...TrustOption)`. + +```go +e.IPExtractor = echo.ExtractIPFromXFFHeader() +``` + +By default, it trusts internal IP addresses (loopback, link-local unicast, private-use and unique local address +from [RFC6890](https://tools.ietf.org/html/rfc6890), [RFC4291](https://tools.ietf.org/html/rfc4291) and +[RFC4193](https://tools.ietf.org/html/rfc4193)). +To control this behavior, use [`TrustOption`](https://godoc.org/github.com/labstack/echo#TrustOption)s. + +E.g.: + +```go +e.IPExtractor = echo.ExtractIPFromXFFHeader( + TrustLinkLocal(false), + TrustIPRanges(lbIPRange), +) +``` + +- Ref: https://godoc.org/github.com/labstack/echo#TrustOption + +## Case 3. With proxies using `X-Real-IP` header + +`X-Real-IP` is another HTTP header to relay clients' IP addresses, but it carries only one address unlike XFF. + +If your proxies set this header, use `ExtractIPFromRealIPHeader(...TrustOption)`. + +```go +e.IPExtractor = echo.ExtractIPFromRealIPHeader() +``` + +Again, it trusts internal IP addresses by default (loopback, link-local unicast, private-use and unique local address +from [RFC6890](https://tools.ietf.org/html/rfc6890), [RFC4291](https://tools.ietf.org/html/rfc4291) and +[RFC4193](https://tools.ietf.org/html/rfc4193)). +To control this behavior, use [`TrustOption`](https://godoc.org/github.com/labstack/echo#TrustOption)s. + +- Ref: https://godoc.org/github.com/labstack/echo#TrustOption + +> **Never forget** to configure the outermost proxy (i.e.; at the edge of your infrastructure) **not to pass through incoming headers**. +> Otherwise there is a chance of fraud, as it is what clients can control. + +## About default behavior + +In default behavior, Echo sees all of first XFF header, X-Real-IP header and IP from network layer. + +As you might already notice, after reading this article, this is not good. +Sole reason this is default is just backward compatibility. + +## Private IP ranges + +See: https://en.wikipedia.org/wiki/Private_network + +Private IPv4 address ranges (RFC 1918): +* 10.0.0.0 – 10.255.255.255 (24-bit block) +* 172.16.0.0 – 172.31.255.255 (20-bit block) +* 192.168.0.0 – 192.168.255.255 (16-bit block) + +Private IPv6 address ranges: +* fc00::/7 address block = RFC 4193 Unique Local Addresses (ULA) + +*/ + +type ipChecker struct { + trustExtraRanges []*net.IPNet + trustLoopback bool + trustLinkLocal bool + trustPrivateNet bool +} + +// TrustOption is config for which IP address to trust +type TrustOption func(*ipChecker) + +// TrustLoopback configures if you trust loopback address (default: true). +func TrustLoopback(v bool) TrustOption { + return func(c *ipChecker) { + c.trustLoopback = v + } +} + +// TrustLinkLocal configures if you trust link-local address (default: true). +func TrustLinkLocal(v bool) TrustOption { + return func(c *ipChecker) { + c.trustLinkLocal = v + } +} + +// TrustPrivateNet configures if you trust private network address (default: true). +func TrustPrivateNet(v bool) TrustOption { + return func(c *ipChecker) { + c.trustPrivateNet = v + } +} + +// TrustIPRange add trustable IP ranges using CIDR notation. +func TrustIPRange(ipRange *net.IPNet) TrustOption { + return func(c *ipChecker) { + c.trustExtraRanges = append(c.trustExtraRanges, ipRange) + } +} + +func newIPChecker(configs []TrustOption) *ipChecker { + checker := &ipChecker{trustLoopback: true, trustLinkLocal: true, trustPrivateNet: true} + for _, configure := range configs { + configure(checker) + } + return checker +} + +// Go1.16+ added `ip.IsPrivate()` but until that use this implementation +func isPrivateIPRange(ip net.IP) bool { + if ip4 := ip.To4(); ip4 != nil { + return ip4[0] == 10 || + ip4[0] == 172 && ip4[1]&0xf0 == 16 || + ip4[0] == 192 && ip4[1] == 168 + } + return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc +} + +func (c *ipChecker) trust(ip net.IP) bool { + if c.trustLoopback && ip.IsLoopback() { + return true + } + if c.trustLinkLocal && ip.IsLinkLocalUnicast() { + return true + } + if c.trustPrivateNet && isPrivateIPRange(ip) { + return true + } + for _, trustedRange := range c.trustExtraRanges { + if trustedRange.Contains(ip) { + return true + } + } + return false +} + +// IPExtractor is a function to extract IP addr from http.Request. +// Set appropriate one to Echo#IPExtractor. +// See https://echo.labstack.com/guide/ip-address for more details. +type IPExtractor func(*http.Request) string + +// ExtractIPDirect extracts IP address using actual IP address. +// Use this if your server faces to internet directory (i.e.: uses no proxy). +func ExtractIPDirect() IPExtractor { + return extractIP +} + +func extractIP(req *http.Request) string { + ra, _, _ := net.SplitHostPort(req.RemoteAddr) + return ra +} + +// ExtractIPFromRealIPHeader extracts IP address using x-real-ip header. +// Use this if you put proxy which uses this header. +func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor { + checker := newIPChecker(options) + return func(req *http.Request) string { + directIP := extractIP(req) + realIP := req.Header.Get(HeaderXRealIP) + if realIP == "" { + return directIP + } + + if checker.trust(net.ParseIP(directIP)) { + realIP = strings.TrimPrefix(realIP, "[") + realIP = strings.TrimSuffix(realIP, "]") + if rIP := net.ParseIP(realIP); rIP != nil { + return realIP + } + } + + return directIP + } +} + +// ExtractIPFromXFFHeader extracts IP address using x-forwarded-for header. +// Use this if you put proxy which uses this header. +// This returns nearest untrustable IP. If all IPs are trustable, returns furthest one (i.e.: XFF[0]). +func ExtractIPFromXFFHeader(options ...TrustOption) IPExtractor { + checker := newIPChecker(options) + return func(req *http.Request) string { + directIP := extractIP(req) + xffs := req.Header[HeaderXForwardedFor] + if len(xffs) == 0 { + return directIP + } + ips := append(strings.Split(strings.Join(xffs, ","), ","), directIP) + for i := len(ips) - 1; i >= 0; i-- { + ips[i] = strings.TrimSpace(ips[i]) + ips[i] = strings.TrimPrefix(ips[i], "[") + ips[i] = strings.TrimSuffix(ips[i], "]") + ip := net.ParseIP(ips[i]) + if ip == nil { + // Unable to parse IP; cannot trust entire records + return directIP + } + if !checker.trust(ip) { + return ip.String() + } + } + // All of the IPs are trusted; return first element because it is furthest from server (best effort strategy). + return strings.TrimSpace(ips[0]) + } +} diff --git a/vendor/github.com/labstack/echo/v4/json.go b/vendor/github.com/labstack/echo/v4/json.go new file mode 100644 index 00000000..6da0aaf9 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/json.go @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package echo + +import ( + "encoding/json" + "fmt" + "net/http" +) + +// DefaultJSONSerializer implements JSON encoding using encoding/json. +type DefaultJSONSerializer struct{} + +// Serialize converts an interface into a json and writes it to the response. +// You can optionally use the indent parameter to produce pretty JSONs. +func (d DefaultJSONSerializer) Serialize(c Context, i interface{}, indent string) error { + enc := json.NewEncoder(c.Response()) + if indent != "" { + enc.SetIndent("", indent) + } + return enc.Encode(i) +} + +// Deserialize reads a JSON from a request body and converts it into an interface. +func (d DefaultJSONSerializer) Deserialize(c Context, i interface{}) error { + err := json.NewDecoder(c.Request().Body).Decode(i) + if ute, ok := err.(*json.UnmarshalTypeError); ok { + return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err) + } else if se, ok := err.(*json.SyntaxError); ok { + return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err) + } + return err +} diff --git a/vendor/github.com/labstack/echo/v4/log.go b/vendor/github.com/labstack/echo/v4/log.go new file mode 100644 index 00000000..0acd9ff0 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/log.go @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package echo + +import ( + "github.com/labstack/gommon/log" + "io" +) + +// Logger defines the logging interface. +type Logger interface { + Output() io.Writer + SetOutput(w io.Writer) + Prefix() string + SetPrefix(p string) + Level() log.Lvl + SetLevel(v log.Lvl) + SetHeader(h string) + Print(i ...interface{}) + Printf(format string, args ...interface{}) + Printj(j log.JSON) + Debug(i ...interface{}) + Debugf(format string, args ...interface{}) + Debugj(j log.JSON) + Info(i ...interface{}) + Infof(format string, args ...interface{}) + Infoj(j log.JSON) + Warn(i ...interface{}) + Warnf(format string, args ...interface{}) + Warnj(j log.JSON) + Error(i ...interface{}) + Errorf(format string, args ...interface{}) + Errorj(j log.JSON) + Fatal(i ...interface{}) + Fatalj(j log.JSON) + Fatalf(format string, args ...interface{}) + Panic(i ...interface{}) + Panicj(j log.JSON) + Panicf(format string, args ...interface{}) +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go b/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go new file mode 100644 index 00000000..9285f29f --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "encoding/base64" + "net/http" + "strconv" + "strings" + + "github.com/labstack/echo/v4" +) + +// BasicAuthConfig defines the config for BasicAuth middleware. +type BasicAuthConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Validator is a function to validate BasicAuth credentials. + // Required. + Validator BasicAuthValidator + + // Realm is a string to define realm attribute of BasicAuth. + // Default value "Restricted". + Realm string +} + +// BasicAuthValidator defines a function to validate BasicAuth credentials. +// The function should return a boolean indicating whether the credentials are valid, +// and an error if any error occurs during the validation process. +type BasicAuthValidator func(string, string, echo.Context) (bool, error) + +const ( + basic = "basic" + defaultRealm = "Restricted" +) + +// DefaultBasicAuthConfig is the default BasicAuth middleware config. +var DefaultBasicAuthConfig = BasicAuthConfig{ + Skipper: DefaultSkipper, + Realm: defaultRealm, +} + +// BasicAuth returns an BasicAuth middleware. +// +// For valid credentials it calls the next handler. +// For missing or invalid credentials, it sends "401 - Unauthorized" response. +func BasicAuth(fn BasicAuthValidator) echo.MiddlewareFunc { + c := DefaultBasicAuthConfig + c.Validator = fn + return BasicAuthWithConfig(c) +} + +// BasicAuthWithConfig returns an BasicAuth middleware with config. +// See `BasicAuth()`. +func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc { + // Defaults + if config.Validator == nil { + panic("echo: basic-auth middleware requires a validator function") + } + if config.Skipper == nil { + config.Skipper = DefaultBasicAuthConfig.Skipper + } + if config.Realm == "" { + config.Realm = defaultRealm + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + auth := c.Request().Header.Get(echo.HeaderAuthorization) + l := len(basic) + + if len(auth) > l+1 && strings.EqualFold(auth[:l], basic) { + // Invalid base64 shouldn't be treated as error + // instead should be treated as invalid client input + b, err := base64.StdEncoding.DecodeString(auth[l+1:]) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest).SetInternal(err) + } + + cred := string(b) + for i := 0; i < len(cred); i++ { + if cred[i] == ':' { + // Verify credentials + valid, err := config.Validator(cred[:i], cred[i+1:], c) + if err != nil { + return err + } else if valid { + return next(c) + } + break + } + } + } + + realm := defaultRealm + if config.Realm != defaultRealm { + realm = strconv.Quote(config.Realm) + } + + // Need to return `401` for browsers to pop-up login box. + c.Response().Header().Set(echo.HeaderWWWAuthenticate, basic+" realm="+realm) + return echo.ErrUnauthorized + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/body_dump.go b/vendor/github.com/labstack/echo/v4/middleware/body_dump.go new file mode 100644 index 00000000..e4119ec1 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/body_dump.go @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "bufio" + "bytes" + "errors" + "io" + "net" + "net/http" + + "github.com/labstack/echo/v4" +) + +// BodyDumpConfig defines the config for BodyDump middleware. +type BodyDumpConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Handler receives request and response payload. + // Required. + Handler BodyDumpHandler +} + +// BodyDumpHandler receives the request and response payload. +type BodyDumpHandler func(echo.Context, []byte, []byte) + +type bodyDumpResponseWriter struct { + io.Writer + http.ResponseWriter +} + +// DefaultBodyDumpConfig is the default BodyDump middleware config. +var DefaultBodyDumpConfig = BodyDumpConfig{ + Skipper: DefaultSkipper, +} + +// BodyDump returns a BodyDump middleware. +// +// BodyDump middleware captures the request and response payload and calls the +// registered handler. +func BodyDump(handler BodyDumpHandler) echo.MiddlewareFunc { + c := DefaultBodyDumpConfig + c.Handler = handler + return BodyDumpWithConfig(c) +} + +// BodyDumpWithConfig returns a BodyDump middleware with config. +// See: `BodyDump()`. +func BodyDumpWithConfig(config BodyDumpConfig) echo.MiddlewareFunc { + // Defaults + if config.Handler == nil { + panic("echo: body-dump middleware requires a handler function") + } + if config.Skipper == nil { + config.Skipper = DefaultBodyDumpConfig.Skipper + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (err error) { + if config.Skipper(c) { + return next(c) + } + + // Request + reqBody := []byte{} + if c.Request().Body != nil { // Read + reqBody, _ = io.ReadAll(c.Request().Body) + } + c.Request().Body = io.NopCloser(bytes.NewBuffer(reqBody)) // Reset + + // Response + resBody := new(bytes.Buffer) + mw := io.MultiWriter(c.Response().Writer, resBody) + writer := &bodyDumpResponseWriter{Writer: mw, ResponseWriter: c.Response().Writer} + c.Response().Writer = writer + + if err = next(c); err != nil { + c.Error(err) + } + + // Callback + config.Handler(c, reqBody, resBody.Bytes()) + + return + } + } +} + +func (w *bodyDumpResponseWriter) WriteHeader(code int) { + w.ResponseWriter.WriteHeader(code) +} + +func (w *bodyDumpResponseWriter) Write(b []byte) (int, error) { + return w.Writer.Write(b) +} + +func (w *bodyDumpResponseWriter) Flush() { + err := http.NewResponseController(w.ResponseWriter).Flush() + if err != nil && errors.Is(err, http.ErrNotSupported) { + panic(errors.New("response writer flushing is not supported")) + } +} + +func (w *bodyDumpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return http.NewResponseController(w.ResponseWriter).Hijack() +} + +func (w *bodyDumpResponseWriter) Unwrap() http.ResponseWriter { + return w.ResponseWriter +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/body_limit.go b/vendor/github.com/labstack/echo/v4/middleware/body_limit.go new file mode 100644 index 00000000..7d3c665f --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/body_limit.go @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "fmt" + "io" + "sync" + + "github.com/labstack/echo/v4" + "github.com/labstack/gommon/bytes" +) + +// BodyLimitConfig defines the config for BodyLimit middleware. +type BodyLimitConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Maximum allowed size for a request body, it can be specified + // as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P. + Limit string `yaml:"limit"` + limit int64 +} + +type limitedReader struct { + BodyLimitConfig + reader io.ReadCloser + read int64 +} + +// DefaultBodyLimitConfig is the default BodyLimit middleware config. +var DefaultBodyLimitConfig = BodyLimitConfig{ + Skipper: DefaultSkipper, +} + +// BodyLimit returns a BodyLimit middleware. +// +// BodyLimit middleware sets the maximum allowed size for a request body, if the +// size exceeds the configured limit, it sends "413 - Request Entity Too Large" +// response. The BodyLimit is determined based on both `Content-Length` request +// header and actual content read, which makes it super secure. +// Limit can be specified as `4x` or `4xB`, where x is one of the multiple from K, M, +// G, T or P. +func BodyLimit(limit string) echo.MiddlewareFunc { + c := DefaultBodyLimitConfig + c.Limit = limit + return BodyLimitWithConfig(c) +} + +// BodyLimitWithConfig returns a BodyLimit middleware with config. +// See: `BodyLimit()`. +func BodyLimitWithConfig(config BodyLimitConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultBodyLimitConfig.Skipper + } + + limit, err := bytes.Parse(config.Limit) + if err != nil { + panic(fmt.Errorf("echo: invalid body-limit=%s", config.Limit)) + } + config.limit = limit + pool := limitedReaderPool(config) + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + + // Based on content length + if req.ContentLength > config.limit { + return echo.ErrStatusRequestEntityTooLarge + } + + // Based on content read + r := pool.Get().(*limitedReader) + r.Reset(req.Body) + defer pool.Put(r) + req.Body = r + + return next(c) + } + } +} + +func (r *limitedReader) Read(b []byte) (n int, err error) { + n, err = r.reader.Read(b) + r.read += int64(n) + if r.read > r.limit { + return n, echo.ErrStatusRequestEntityTooLarge + } + return +} + +func (r *limitedReader) Close() error { + return r.reader.Close() +} + +func (r *limitedReader) Reset(reader io.ReadCloser) { + r.reader = reader + r.read = 0 +} + +func limitedReaderPool(c BodyLimitConfig) sync.Pool { + return sync.Pool{ + New: func() interface{} { + return &limitedReader{BodyLimitConfig: c} + }, + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/compress.go b/vendor/github.com/labstack/echo/v4/middleware/compress.go new file mode 100644 index 00000000..012b76b0 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/compress.go @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "bufio" + "bytes" + "compress/gzip" + "io" + "net" + "net/http" + "strings" + "sync" + + "github.com/labstack/echo/v4" +) + +// GzipConfig defines the config for Gzip middleware. +type GzipConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Gzip compression level. + // Optional. Default value -1. + Level int `yaml:"level"` + + // Length threshold before gzip compression is applied. + // Optional. Default value 0. + // + // Most of the time you will not need to change the default. Compressing + // a short response might increase the transmitted data because of the + // gzip format overhead. Compressing the response will also consume CPU + // and time on the server and the client (for decompressing). Depending on + // your use case such a threshold might be useful. + // + // See also: + // https://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits + MinLength int +} + +type gzipResponseWriter struct { + io.Writer + http.ResponseWriter + wroteHeader bool + wroteBody bool + minLength int + minLengthExceeded bool + buffer *bytes.Buffer + code int +} + +const ( + gzipScheme = "gzip" +) + +// DefaultGzipConfig is the default Gzip middleware config. +var DefaultGzipConfig = GzipConfig{ + Skipper: DefaultSkipper, + Level: -1, + MinLength: 0, +} + +// Gzip returns a middleware which compresses HTTP response using gzip compression +// scheme. +func Gzip() echo.MiddlewareFunc { + return GzipWithConfig(DefaultGzipConfig) +} + +// GzipWithConfig return Gzip middleware with config. +// See: `Gzip()`. +func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultGzipConfig.Skipper + } + if config.Level == 0 { + config.Level = DefaultGzipConfig.Level + } + if config.MinLength < 0 { + config.MinLength = DefaultGzipConfig.MinLength + } + + pool := gzipCompressPool(config) + bpool := bufferPool() + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + res := c.Response() + res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding) + if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), gzipScheme) { + i := pool.Get() + w, ok := i.(*gzip.Writer) + if !ok { + return echo.NewHTTPError(http.StatusInternalServerError, i.(error).Error()) + } + rw := res.Writer + w.Reset(rw) + + buf := bpool.Get().(*bytes.Buffer) + buf.Reset() + + grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw, minLength: config.MinLength, buffer: buf} + defer func() { + // There are different reasons for cases when we have not yet written response to the client and now need to do so. + // a) handler response had only response code and no response body (ala 404 or redirects etc). Response code need to be written now. + // b) body is shorter than our minimum length threshold and being buffered currently and needs to be written + if !grw.wroteBody { + if res.Header().Get(echo.HeaderContentEncoding) == gzipScheme { + res.Header().Del(echo.HeaderContentEncoding) + } + if grw.wroteHeader { + rw.WriteHeader(grw.code) + } + // We have to reset response to it's pristine state when + // nothing is written to body or error is returned. + // See issue #424, #407. + res.Writer = rw + w.Reset(io.Discard) + } else if !grw.minLengthExceeded { + // Write uncompressed response + res.Writer = rw + if grw.wroteHeader { + grw.ResponseWriter.WriteHeader(grw.code) + } + grw.buffer.WriteTo(rw) + w.Reset(io.Discard) + } + w.Close() + bpool.Put(buf) + pool.Put(w) + }() + res.Writer = grw + } + return next(c) + } + } +} + +func (w *gzipResponseWriter) WriteHeader(code int) { + w.Header().Del(echo.HeaderContentLength) // Issue #444 + + w.wroteHeader = true + + // Delay writing of the header until we know if we'll actually compress the response + w.code = code +} + +func (w *gzipResponseWriter) Write(b []byte) (int, error) { + if w.Header().Get(echo.HeaderContentType) == "" { + w.Header().Set(echo.HeaderContentType, http.DetectContentType(b)) + } + w.wroteBody = true + + if !w.minLengthExceeded { + n, err := w.buffer.Write(b) + + if w.buffer.Len() >= w.minLength { + w.minLengthExceeded = true + + // The minimum length is exceeded, add Content-Encoding header and write the header + w.Header().Set(echo.HeaderContentEncoding, gzipScheme) // Issue #806 + if w.wroteHeader { + w.ResponseWriter.WriteHeader(w.code) + } + + return w.Writer.Write(w.buffer.Bytes()) + } + + return n, err + } + + return w.Writer.Write(b) +} + +func (w *gzipResponseWriter) Flush() { + if !w.minLengthExceeded { + // Enforce compression because we will not know how much more data will come + w.minLengthExceeded = true + w.Header().Set(echo.HeaderContentEncoding, gzipScheme) // Issue #806 + if w.wroteHeader { + w.ResponseWriter.WriteHeader(w.code) + } + + w.Writer.Write(w.buffer.Bytes()) + } + + w.Writer.(*gzip.Writer).Flush() + _ = http.NewResponseController(w.ResponseWriter).Flush() +} + +func (w *gzipResponseWriter) Unwrap() http.ResponseWriter { + return w.ResponseWriter +} + +func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return http.NewResponseController(w.ResponseWriter).Hijack() +} + +func (w *gzipResponseWriter) Push(target string, opts *http.PushOptions) error { + if p, ok := w.ResponseWriter.(http.Pusher); ok { + return p.Push(target, opts) + } + return http.ErrNotSupported +} + +func gzipCompressPool(config GzipConfig) sync.Pool { + return sync.Pool{ + New: func() interface{} { + w, err := gzip.NewWriterLevel(io.Discard, config.Level) + if err != nil { + return err + } + return w + }, + } +} + +func bufferPool() sync.Pool { + return sync.Pool{ + New: func() interface{} { + b := &bytes.Buffer{} + return b + }, + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/context_timeout.go b/vendor/github.com/labstack/echo/v4/middleware/context_timeout.go new file mode 100644 index 00000000..e67173f2 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/context_timeout.go @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "context" + "errors" + "time" + + "github.com/labstack/echo/v4" +) + +// ContextTimeoutConfig defines the config for ContextTimeout middleware. +type ContextTimeoutConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // ErrorHandler is a function when error aries in middleware execution. + ErrorHandler func(err error, c echo.Context) error + + // Timeout configures a timeout for the middleware, defaults to 0 for no timeout + Timeout time.Duration +} + +// ContextTimeout returns a middleware which returns error (503 Service Unavailable error) to client +// when underlying method returns context.DeadlineExceeded error. +func ContextTimeout(timeout time.Duration) echo.MiddlewareFunc { + return ContextTimeoutWithConfig(ContextTimeoutConfig{Timeout: timeout}) +} + +// ContextTimeoutWithConfig returns a Timeout middleware with config. +func ContextTimeoutWithConfig(config ContextTimeoutConfig) echo.MiddlewareFunc { + mw, err := config.ToMiddleware() + if err != nil { + panic(err) + } + return mw +} + +// ToMiddleware converts Config to middleware. +func (config ContextTimeoutConfig) ToMiddleware() (echo.MiddlewareFunc, error) { + if config.Timeout == 0 { + return nil, errors.New("timeout must be set") + } + if config.Skipper == nil { + config.Skipper = DefaultSkipper + } + if config.ErrorHandler == nil { + config.ErrorHandler = func(err error, c echo.Context) error { + if err != nil && errors.Is(err, context.DeadlineExceeded) { + return echo.ErrServiceUnavailable.WithInternal(err) + } + return err + } + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + timeoutContext, cancel := context.WithTimeout(c.Request().Context(), config.Timeout) + defer cancel() + + c.SetRequest(c.Request().WithContext(timeoutContext)) + + if err := next(c); err != nil { + return config.ErrorHandler(err, c) + } + return nil + } + }, nil +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/cors.go b/vendor/github.com/labstack/echo/v4/middleware/cors.go new file mode 100644 index 00000000..a1f44532 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/cors.go @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "net/http" + "regexp" + "strconv" + "strings" + + "github.com/labstack/echo/v4" +) + +// CORSConfig defines the config for CORS middleware. +type CORSConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // AllowOrigins determines the value of the Access-Control-Allow-Origin + // response header. This header defines a list of origins that may access the + // resource. The wildcard characters '*' and '?' are supported and are + // converted to regex fragments '.*' and '.' accordingly. + // + // Security: use extreme caution when handling the origin, and carefully + // validate any logic. Remember that attackers may register hostile domain names. + // See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html + // + // Optional. Default value []string{"*"}. + // + // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin + AllowOrigins []string `yaml:"allow_origins"` + + // AllowOriginFunc is a custom function to validate the origin. It takes the + // origin as an argument and returns true if allowed or false otherwise. If + // an error is returned, it is returned by the handler. If this option is + // set, AllowOrigins is ignored. + // + // Security: use extreme caution when handling the origin, and carefully + // validate any logic. Remember that attackers may register hostile domain names. + // See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html + // + // Optional. + AllowOriginFunc func(origin string) (bool, error) `yaml:"-"` + + // AllowMethods determines the value of the Access-Control-Allow-Methods + // response header. This header specified the list of methods allowed when + // accessing the resource. This is used in response to a preflight request. + // + // Optional. Default value DefaultCORSConfig.AllowMethods. + // If `allowMethods` is left empty, this middleware will fill for preflight + // request `Access-Control-Allow-Methods` header value + // from `Allow` header that echo.Router set into context. + // + // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods + AllowMethods []string `yaml:"allow_methods"` + + // AllowHeaders determines the value of the Access-Control-Allow-Headers + // response header. This header is used in response to a preflight request to + // indicate which HTTP headers can be used when making the actual request. + // + // Optional. Default value []string{}. + // + // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers + AllowHeaders []string `yaml:"allow_headers"` + + // AllowCredentials determines the value of the + // Access-Control-Allow-Credentials response header. This header indicates + // whether or not the response to the request can be exposed when the + // credentials mode (Request.credentials) is true. When used as part of a + // response to a preflight request, this indicates whether or not the actual + // request can be made using credentials. See also + // [MDN: Access-Control-Allow-Credentials]. + // + // Optional. Default value false, in which case the header is not set. + // + // Security: avoid using `AllowCredentials = true` with `AllowOrigins = *`. + // See "Exploiting CORS misconfigurations for Bitcoins and bounties", + // https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html + // + // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials + AllowCredentials bool `yaml:"allow_credentials"` + + // UnsafeWildcardOriginWithAllowCredentials UNSAFE/INSECURE: allows wildcard '*' origin to be used with AllowCredentials + // flag. In that case we consider any origin allowed and send it back to the client with `Access-Control-Allow-Origin` header. + // + // This is INSECURE and potentially leads to [cross-origin](https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties) + // attacks. See: https://github.com/labstack/echo/issues/2400 for discussion on the subject. + // + // Optional. Default value is false. + UnsafeWildcardOriginWithAllowCredentials bool `yaml:"unsafe_wildcard_origin_with_allow_credentials"` + + // ExposeHeaders determines the value of Access-Control-Expose-Headers, which + // defines a list of headers that clients are allowed to access. + // + // Optional. Default value []string{}, in which case the header is not set. + // + // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Header + ExposeHeaders []string `yaml:"expose_headers"` + + // MaxAge determines the value of the Access-Control-Max-Age response header. + // This header indicates how long (in seconds) the results of a preflight + // request can be cached. + // The header is set only if MaxAge != 0, negative value sends "0" which instructs browsers not to cache that response. + // + // Optional. Default value 0 - meaning header is not sent. + // + // See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age + MaxAge int `yaml:"max_age"` +} + +// DefaultCORSConfig is the default CORS middleware config. +var DefaultCORSConfig = CORSConfig{ + Skipper: DefaultSkipper, + AllowOrigins: []string{"*"}, + AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete}, +} + +// CORS returns a Cross-Origin Resource Sharing (CORS) middleware. +// See also [MDN: Cross-Origin Resource Sharing (CORS)]. +// +// Security: Poorly configured CORS can compromise security because it allows +// relaxation of the browser's Same-Origin policy. See [Exploiting CORS +// misconfigurations for Bitcoins and bounties] and [Portswigger: Cross-origin +// resource sharing (CORS)] for more details. +// +// [MDN: Cross-Origin Resource Sharing (CORS)]: https://developer.mozilla.org/en/docs/Web/HTTP/Access_control_CORS +// [Exploiting CORS misconfigurations for Bitcoins and bounties]: https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html +// [Portswigger: Cross-origin resource sharing (CORS)]: https://portswigger.net/web-security/cors +func CORS() echo.MiddlewareFunc { + return CORSWithConfig(DefaultCORSConfig) +} + +// CORSWithConfig returns a CORS middleware with config. +// See: [CORS]. +func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultCORSConfig.Skipper + } + if len(config.AllowOrigins) == 0 { + config.AllowOrigins = DefaultCORSConfig.AllowOrigins + } + hasCustomAllowMethods := true + if len(config.AllowMethods) == 0 { + hasCustomAllowMethods = false + config.AllowMethods = DefaultCORSConfig.AllowMethods + } + + allowOriginPatterns := make([]*regexp.Regexp, 0, len(config.AllowOrigins)) + for _, origin := range config.AllowOrigins { + if origin == "*" { + continue // "*" is handled differently and does not need regexp + } + pattern := regexp.QuoteMeta(origin) + pattern = strings.ReplaceAll(pattern, "\\*", ".*") + pattern = strings.ReplaceAll(pattern, "\\?", ".") + pattern = "^" + pattern + "$" + + re, err := regexp.Compile(pattern) + if err != nil { + // this is to preserve previous behaviour - invalid patterns were just ignored. + // If we would turn this to panic, users with invalid patterns + // would have applications crashing in production due unrecovered panic. + // TODO: this should be turned to error/panic in `v5` + continue + } + allowOriginPatterns = append(allowOriginPatterns, re) + } + + allowMethods := strings.Join(config.AllowMethods, ",") + allowHeaders := strings.Join(config.AllowHeaders, ",") + exposeHeaders := strings.Join(config.ExposeHeaders, ",") + + maxAge := "0" + if config.MaxAge > 0 { + maxAge = strconv.Itoa(config.MaxAge) + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + res := c.Response() + origin := req.Header.Get(echo.HeaderOrigin) + allowOrigin := "" + + res.Header().Add(echo.HeaderVary, echo.HeaderOrigin) + + // Preflight request is an OPTIONS request, using three HTTP request headers: Access-Control-Request-Method, + // Access-Control-Request-Headers, and the Origin header. See: https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request + // For simplicity we just consider method type and later `Origin` header. + preflight := req.Method == http.MethodOptions + + // Although router adds special handler in case of OPTIONS method we avoid calling next for OPTIONS in this middleware + // as CORS requests do not have cookies / authentication headers by default, so we could get stuck in auth + // middlewares by calling next(c). + // But we still want to send `Allow` header as response in case of Non-CORS OPTIONS request as router default + // handler does. + routerAllowMethods := "" + if preflight { + tmpAllowMethods, ok := c.Get(echo.ContextKeyHeaderAllow).(string) + if ok && tmpAllowMethods != "" { + routerAllowMethods = tmpAllowMethods + c.Response().Header().Set(echo.HeaderAllow, routerAllowMethods) + } + } + + // No Origin provided. This is (probably) not request from actual browser - proceed executing middleware chain + if origin == "" { + if !preflight { + return next(c) + } + return c.NoContent(http.StatusNoContent) + } + + if config.AllowOriginFunc != nil { + allowed, err := config.AllowOriginFunc(origin) + if err != nil { + return err + } + if allowed { + allowOrigin = origin + } + } else { + // Check allowed origins + for _, o := range config.AllowOrigins { + if o == "*" && config.AllowCredentials && config.UnsafeWildcardOriginWithAllowCredentials { + allowOrigin = origin + break + } + if o == "*" || o == origin { + allowOrigin = o + break + } + if matchSubdomain(origin, o) { + allowOrigin = origin + break + } + } + + checkPatterns := false + if allowOrigin == "" { + // to avoid regex cost by invalid (long) domains (253 is domain name max limit) + if len(origin) <= (253+3+5) && strings.Contains(origin, "://") { + checkPatterns = true + } + } + if checkPatterns { + for _, re := range allowOriginPatterns { + if match := re.MatchString(origin); match { + allowOrigin = origin + break + } + } + } + } + + // Origin not allowed + if allowOrigin == "" { + if !preflight { + return next(c) + } + return c.NoContent(http.StatusNoContent) + } + + res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin) + if config.AllowCredentials { + res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true") + } + + // Simple request + if !preflight { + if exposeHeaders != "" { + res.Header().Set(echo.HeaderAccessControlExposeHeaders, exposeHeaders) + } + return next(c) + } + + // Preflight request + res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestMethod) + res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestHeaders) + + if !hasCustomAllowMethods && routerAllowMethods != "" { + res.Header().Set(echo.HeaderAccessControlAllowMethods, routerAllowMethods) + } else { + res.Header().Set(echo.HeaderAccessControlAllowMethods, allowMethods) + } + + if allowHeaders != "" { + res.Header().Set(echo.HeaderAccessControlAllowHeaders, allowHeaders) + } else { + h := req.Header.Get(echo.HeaderAccessControlRequestHeaders) + if h != "" { + res.Header().Set(echo.HeaderAccessControlAllowHeaders, h) + } + } + if config.MaxAge != 0 { + res.Header().Set(echo.HeaderAccessControlMaxAge, maxAge) + } + return c.NoContent(http.StatusNoContent) + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/csrf.go b/vendor/github.com/labstack/echo/v4/middleware/csrf.go new file mode 100644 index 00000000..92f4019d --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/csrf.go @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "crypto/subtle" + "net/http" + "time" + + "github.com/labstack/echo/v4" +) + +// CSRFConfig defines the config for CSRF middleware. +type CSRFConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // TokenLength is the length of the generated token. + TokenLength uint8 `yaml:"token_length"` + // Optional. Default value 32. + + // TokenLookup is a string in the form of ":" or ":,:" that is used + // to extract token from the request. + // Optional. Default value "header:X-CSRF-Token". + // Possible values: + // - "header:" or "header::" + // - "query:" + // - "form:" + // Multiple sources example: + // - "header:X-CSRF-Token,query:csrf" + TokenLookup string `yaml:"token_lookup"` + + // Context key to store generated CSRF token into context. + // Optional. Default value "csrf". + ContextKey string `yaml:"context_key"` + + // Name of the CSRF cookie. This cookie will store CSRF token. + // Optional. Default value "csrf". + CookieName string `yaml:"cookie_name"` + + // Domain of the CSRF cookie. + // Optional. Default value none. + CookieDomain string `yaml:"cookie_domain"` + + // Path of the CSRF cookie. + // Optional. Default value none. + CookiePath string `yaml:"cookie_path"` + + // Max age (in seconds) of the CSRF cookie. + // Optional. Default value 86400 (24hr). + CookieMaxAge int `yaml:"cookie_max_age"` + + // Indicates if CSRF cookie is secure. + // Optional. Default value false. + CookieSecure bool `yaml:"cookie_secure"` + + // Indicates if CSRF cookie is HTTP only. + // Optional. Default value false. + CookieHTTPOnly bool `yaml:"cookie_http_only"` + + // Indicates SameSite mode of the CSRF cookie. + // Optional. Default value SameSiteDefaultMode. + CookieSameSite http.SameSite `yaml:"cookie_same_site"` + + // ErrorHandler defines a function which is executed for returning custom errors. + ErrorHandler CSRFErrorHandler +} + +// CSRFErrorHandler is a function which is executed for creating custom errors. +type CSRFErrorHandler func(err error, c echo.Context) error + +// ErrCSRFInvalid is returned when CSRF check fails +var ErrCSRFInvalid = echo.NewHTTPError(http.StatusForbidden, "invalid csrf token") + +// DefaultCSRFConfig is the default CSRF middleware config. +var DefaultCSRFConfig = CSRFConfig{ + Skipper: DefaultSkipper, + TokenLength: 32, + TokenLookup: "header:" + echo.HeaderXCSRFToken, + ContextKey: "csrf", + CookieName: "_csrf", + CookieMaxAge: 86400, + CookieSameSite: http.SameSiteDefaultMode, +} + +// CSRF returns a Cross-Site Request Forgery (CSRF) middleware. +// See: https://en.wikipedia.org/wiki/Cross-site_request_forgery +func CSRF() echo.MiddlewareFunc { + c := DefaultCSRFConfig + return CSRFWithConfig(c) +} + +// CSRFWithConfig returns a CSRF middleware with config. +// See `CSRF()`. +func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultCSRFConfig.Skipper + } + if config.TokenLength == 0 { + config.TokenLength = DefaultCSRFConfig.TokenLength + } + + if config.TokenLookup == "" { + config.TokenLookup = DefaultCSRFConfig.TokenLookup + } + if config.ContextKey == "" { + config.ContextKey = DefaultCSRFConfig.ContextKey + } + if config.CookieName == "" { + config.CookieName = DefaultCSRFConfig.CookieName + } + if config.CookieMaxAge == 0 { + config.CookieMaxAge = DefaultCSRFConfig.CookieMaxAge + } + if config.CookieSameSite == http.SameSiteNoneMode { + config.CookieSecure = true + } + + extractors, cErr := CreateExtractors(config.TokenLookup) + if cErr != nil { + panic(cErr) + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + token := "" + if k, err := c.Cookie(config.CookieName); err != nil { + token = randomString(config.TokenLength) + } else { + token = k.Value // Reuse token + } + + switch c.Request().Method { + case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace: + default: + // Validate token only for requests which are not defined as 'safe' by RFC7231 + var lastExtractorErr error + var lastTokenErr error + outer: + for _, extractor := range extractors { + clientTokens, err := extractor(c) + if err != nil { + lastExtractorErr = err + continue + } + + for _, clientToken := range clientTokens { + if validateCSRFToken(token, clientToken) { + lastTokenErr = nil + lastExtractorErr = nil + break outer + } + lastTokenErr = ErrCSRFInvalid + } + } + var finalErr error + if lastTokenErr != nil { + finalErr = lastTokenErr + } else if lastExtractorErr != nil { + // ugly part to preserve backwards compatible errors. someone could rely on them + if lastExtractorErr == errQueryExtractorValueMissing { + lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in the query string") + } else if lastExtractorErr == errFormExtractorValueMissing { + lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in the form parameter") + } else if lastExtractorErr == errHeaderExtractorValueMissing { + lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in request header") + } else { + lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, lastExtractorErr.Error()) + } + finalErr = lastExtractorErr + } + + if finalErr != nil { + if config.ErrorHandler != nil { + return config.ErrorHandler(finalErr, c) + } + return finalErr + } + } + + // Set CSRF cookie + cookie := new(http.Cookie) + cookie.Name = config.CookieName + cookie.Value = token + if config.CookiePath != "" { + cookie.Path = config.CookiePath + } + if config.CookieDomain != "" { + cookie.Domain = config.CookieDomain + } + if config.CookieSameSite != http.SameSiteDefaultMode { + cookie.SameSite = config.CookieSameSite + } + cookie.Expires = time.Now().Add(time.Duration(config.CookieMaxAge) * time.Second) + cookie.Secure = config.CookieSecure + cookie.HttpOnly = config.CookieHTTPOnly + c.SetCookie(cookie) + + // Store token in the context + c.Set(config.ContextKey, token) + + // Protect clients from caching the response + c.Response().Header().Add(echo.HeaderVary, echo.HeaderCookie) + + return next(c) + } + } +} + +func validateCSRFToken(token, clientToken string) bool { + return subtle.ConstantTimeCompare([]byte(token), []byte(clientToken)) == 1 +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/decompress.go b/vendor/github.com/labstack/echo/v4/middleware/decompress.go new file mode 100644 index 00000000..0c56176e --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/decompress.go @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "compress/gzip" + "io" + "net/http" + "sync" + + "github.com/labstack/echo/v4" +) + +// DecompressConfig defines the config for Decompress middleware. +type DecompressConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // GzipDecompressPool defines an interface to provide the sync.Pool used to create/store Gzip readers + GzipDecompressPool Decompressor +} + +// GZIPEncoding content-encoding header if set to "gzip", decompress body contents. +const GZIPEncoding string = "gzip" + +// Decompressor is used to get the sync.Pool used by the middleware to get Gzip readers +type Decompressor interface { + gzipDecompressPool() sync.Pool +} + +// DefaultDecompressConfig defines the config for decompress middleware +var DefaultDecompressConfig = DecompressConfig{ + Skipper: DefaultSkipper, + GzipDecompressPool: &DefaultGzipDecompressPool{}, +} + +// DefaultGzipDecompressPool is the default implementation of Decompressor interface +type DefaultGzipDecompressPool struct { +} + +func (d *DefaultGzipDecompressPool) gzipDecompressPool() sync.Pool { + return sync.Pool{New: func() interface{} { return new(gzip.Reader) }} +} + +// Decompress decompresses request body based if content encoding type is set to "gzip" with default config +func Decompress() echo.MiddlewareFunc { + return DecompressWithConfig(DefaultDecompressConfig) +} + +// DecompressWithConfig decompresses request body based if content encoding type is set to "gzip" with config +func DecompressWithConfig(config DecompressConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultGzipConfig.Skipper + } + if config.GzipDecompressPool == nil { + config.GzipDecompressPool = DefaultDecompressConfig.GzipDecompressPool + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + pool := config.GzipDecompressPool.gzipDecompressPool() + + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + if c.Request().Header.Get(echo.HeaderContentEncoding) != GZIPEncoding { + return next(c) + } + + i := pool.Get() + gr, ok := i.(*gzip.Reader) + if !ok || gr == nil { + return echo.NewHTTPError(http.StatusInternalServerError, i.(error).Error()) + } + defer pool.Put(gr) + + b := c.Request().Body + defer b.Close() + + if err := gr.Reset(b); err != nil { + if err == io.EOF { //ignore if body is empty + return next(c) + } + return err + } + + // only Close gzip reader if it was set to a proper gzip source otherwise it will panic on close. + defer gr.Close() + + c.Request().Body = gr + + return next(c) + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/extractor.go b/vendor/github.com/labstack/echo/v4/middleware/extractor.go new file mode 100644 index 00000000..3f274140 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/extractor.go @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "errors" + "fmt" + "github.com/labstack/echo/v4" + "net/textproto" + "strings" +) + +const ( + // extractorLimit is arbitrary number to limit values extractor can return. this limits possible resource exhaustion + // attack vector + extractorLimit = 20 +) + +var errHeaderExtractorValueMissing = errors.New("missing value in request header") +var errHeaderExtractorValueInvalid = errors.New("invalid value in request header") +var errQueryExtractorValueMissing = errors.New("missing value in the query string") +var errParamExtractorValueMissing = errors.New("missing value in path params") +var errCookieExtractorValueMissing = errors.New("missing value in cookies") +var errFormExtractorValueMissing = errors.New("missing value in the form") + +// ValuesExtractor defines a function for extracting values (keys/tokens) from the given context. +type ValuesExtractor func(c echo.Context) ([]string, error) + +// CreateExtractors creates ValuesExtractors from given lookups. +// Lookups is a string in the form of ":" or ":,:" that is used +// to extract key from the request. +// Possible values: +// - "header:" or "header::" +// `` is argument value to cut/trim prefix of the extracted value. This is useful if header +// value has static prefix like `Authorization: ` where part that we +// want to cut is ` ` note the space at the end. +// In case of basic authentication `Authorization: Basic ` prefix we want to remove is `Basic `. +// - "query:" +// - "param:" +// - "form:" +// - "cookie:" +// +// Multiple sources example: +// - "header:Authorization,header:X-Api-Key" +func CreateExtractors(lookups string) ([]ValuesExtractor, error) { + return createExtractors(lookups, "") +} + +func createExtractors(lookups string, authScheme string) ([]ValuesExtractor, error) { + if lookups == "" { + return nil, nil + } + sources := strings.Split(lookups, ",") + var extractors = make([]ValuesExtractor, 0) + for _, source := range sources { + parts := strings.Split(source, ":") + if len(parts) < 2 { + return nil, fmt.Errorf("extractor source for lookup could not be split into needed parts: %v", source) + } + + switch parts[0] { + case "query": + extractors = append(extractors, valuesFromQuery(parts[1])) + case "param": + extractors = append(extractors, valuesFromParam(parts[1])) + case "cookie": + extractors = append(extractors, valuesFromCookie(parts[1])) + case "form": + extractors = append(extractors, valuesFromForm(parts[1])) + case "header": + prefix := "" + if len(parts) > 2 { + prefix = parts[2] + } else if authScheme != "" && parts[1] == echo.HeaderAuthorization { + // backwards compatibility for JWT and KeyAuth: + // * we only apply this fix to Authorization as header we use and uses prefixes like "Bearer " etc + // * previously header extractor assumed that auth-scheme/prefix had a space as suffix we need to retain that + // behaviour for default values and Authorization header. + prefix = authScheme + if !strings.HasSuffix(prefix, " ") { + prefix += " " + } + } + extractors = append(extractors, valuesFromHeader(parts[1], prefix)) + } + } + return extractors, nil +} + +// valuesFromHeader returns a functions that extracts values from the request header. +// valuePrefix is parameter to remove first part (prefix) of the extracted value. This is useful if header value has static +// prefix like `Authorization: ` where part that we want to remove is ` ` +// note the space at the end. In case of basic authentication `Authorization: Basic ` prefix we want to remove +// is `Basic `. In case of JWT tokens `Authorization: Bearer ` prefix is `Bearer `. +// If prefix is left empty the whole value is returned. +func valuesFromHeader(header string, valuePrefix string) ValuesExtractor { + prefixLen := len(valuePrefix) + // standard library parses http.Request header keys in canonical form but we may provide something else so fix this + header = textproto.CanonicalMIMEHeaderKey(header) + return func(c echo.Context) ([]string, error) { + values := c.Request().Header.Values(header) + if len(values) == 0 { + return nil, errHeaderExtractorValueMissing + } + + result := make([]string, 0) + for i, value := range values { + if prefixLen == 0 { + result = append(result, value) + if i >= extractorLimit-1 { + break + } + continue + } + if len(value) > prefixLen && strings.EqualFold(value[:prefixLen], valuePrefix) { + result = append(result, value[prefixLen:]) + if i >= extractorLimit-1 { + break + } + } + } + + if len(result) == 0 { + if prefixLen > 0 { + return nil, errHeaderExtractorValueInvalid + } + return nil, errHeaderExtractorValueMissing + } + return result, nil + } +} + +// valuesFromQuery returns a function that extracts values from the query string. +func valuesFromQuery(param string) ValuesExtractor { + return func(c echo.Context) ([]string, error) { + result := c.QueryParams()[param] + if len(result) == 0 { + return nil, errQueryExtractorValueMissing + } else if len(result) > extractorLimit-1 { + result = result[:extractorLimit] + } + return result, nil + } +} + +// valuesFromParam returns a function that extracts values from the url param string. +func valuesFromParam(param string) ValuesExtractor { + return func(c echo.Context) ([]string, error) { + result := make([]string, 0) + paramVales := c.ParamValues() + for i, p := range c.ParamNames() { + if param == p { + result = append(result, paramVales[i]) + if i >= extractorLimit-1 { + break + } + } + } + if len(result) == 0 { + return nil, errParamExtractorValueMissing + } + return result, nil + } +} + +// valuesFromCookie returns a function that extracts values from the named cookie. +func valuesFromCookie(name string) ValuesExtractor { + return func(c echo.Context) ([]string, error) { + cookies := c.Cookies() + if len(cookies) == 0 { + return nil, errCookieExtractorValueMissing + } + + result := make([]string, 0) + for i, cookie := range cookies { + if name == cookie.Name { + result = append(result, cookie.Value) + if i >= extractorLimit-1 { + break + } + } + } + if len(result) == 0 { + return nil, errCookieExtractorValueMissing + } + return result, nil + } +} + +// valuesFromForm returns a function that extracts values from the form field. +func valuesFromForm(name string) ValuesExtractor { + return func(c echo.Context) ([]string, error) { + if c.Request().Form == nil { + _ = c.Request().ParseMultipartForm(32 << 20) // same what `c.Request().FormValue(name)` does + } + values := c.Request().Form[name] + if len(values) == 0 { + return nil, errFormExtractorValueMissing + } + if len(values) > extractorLimit-1 { + values = values[:extractorLimit] + } + result := append([]string{}, values...) + return result, nil + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/key_auth.go b/vendor/github.com/labstack/echo/v4/middleware/key_auth.go new file mode 100644 index 00000000..79bee207 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/key_auth.go @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "errors" + "github.com/labstack/echo/v4" + "net/http" +) + +// KeyAuthConfig defines the config for KeyAuth middleware. +type KeyAuthConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // KeyLookup is a string in the form of ":" or ":,:" that is used + // to extract key from the request. + // Optional. Default value "header:Authorization". + // Possible values: + // - "header:" or "header::" + // `` is argument value to cut/trim prefix of the extracted value. This is useful if header + // value has static prefix like `Authorization: ` where part that we + // want to cut is ` ` note the space at the end. + // In case of basic authentication `Authorization: Basic ` prefix we want to remove is `Basic `. + // - "query:" + // - "form:" + // - "cookie:" + // Multiple sources example: + // - "header:Authorization,header:X-Api-Key" + KeyLookup string + + // AuthScheme to be used in the Authorization header. + // Optional. Default value "Bearer". + AuthScheme string + + // Validator is a function to validate key. + // Required. + Validator KeyAuthValidator + + // ErrorHandler defines a function which is executed for an invalid key. + // It may be used to define a custom error. + ErrorHandler KeyAuthErrorHandler + + // ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandler decides to + // ignore the error (by returning `nil`). + // This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality. + // In that case you can use ErrorHandler to set a default public key auth value in the request context + // and continue. Some logic down the remaining execution chain needs to check that (public) key auth value then. + ContinueOnIgnoredError bool +} + +// KeyAuthValidator defines a function to validate KeyAuth credentials. +type KeyAuthValidator func(auth string, c echo.Context) (bool, error) + +// KeyAuthErrorHandler defines a function which is executed for an invalid key. +type KeyAuthErrorHandler func(err error, c echo.Context) error + +// ErrKeyAuthMissing is error type when KeyAuth middleware is unable to extract value from lookups +type ErrKeyAuthMissing struct { + Err error +} + +// DefaultKeyAuthConfig is the default KeyAuth middleware config. +var DefaultKeyAuthConfig = KeyAuthConfig{ + Skipper: DefaultSkipper, + KeyLookup: "header:" + echo.HeaderAuthorization, + AuthScheme: "Bearer", +} + +// Error returns errors text +func (e *ErrKeyAuthMissing) Error() string { + return e.Err.Error() +} + +// Unwrap unwraps error +func (e *ErrKeyAuthMissing) Unwrap() error { + return e.Err +} + +// KeyAuth returns an KeyAuth middleware. +// +// For valid key it calls the next handler. +// For invalid key, it sends "401 - Unauthorized" response. +// For missing key, it sends "400 - Bad Request" response. +func KeyAuth(fn KeyAuthValidator) echo.MiddlewareFunc { + c := DefaultKeyAuthConfig + c.Validator = fn + return KeyAuthWithConfig(c) +} + +// KeyAuthWithConfig returns an KeyAuth middleware with config. +// See `KeyAuth()`. +func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultKeyAuthConfig.Skipper + } + // Defaults + if config.AuthScheme == "" { + config.AuthScheme = DefaultKeyAuthConfig.AuthScheme + } + if config.KeyLookup == "" { + config.KeyLookup = DefaultKeyAuthConfig.KeyLookup + } + if config.Validator == nil { + panic("echo: key-auth middleware requires a validator function") + } + + extractors, cErr := createExtractors(config.KeyLookup, config.AuthScheme) + if cErr != nil { + panic(cErr) + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + var lastExtractorErr error + var lastValidatorErr error + for _, extractor := range extractors { + keys, err := extractor(c) + if err != nil { + lastExtractorErr = err + continue + } + for _, key := range keys { + valid, err := config.Validator(key, c) + if err != nil { + lastValidatorErr = err + continue + } + if valid { + return next(c) + } + lastValidatorErr = errors.New("invalid key") + } + } + + // we are here only when we did not successfully extract and validate any of keys + err := lastValidatorErr + if err == nil { // prioritize validator errors over extracting errors + // ugly part to preserve backwards compatible errors. someone could rely on them + if lastExtractorErr == errQueryExtractorValueMissing { + err = errors.New("missing key in the query string") + } else if lastExtractorErr == errCookieExtractorValueMissing { + err = errors.New("missing key in cookies") + } else if lastExtractorErr == errFormExtractorValueMissing { + err = errors.New("missing key in the form") + } else if lastExtractorErr == errHeaderExtractorValueMissing { + err = errors.New("missing key in request header") + } else if lastExtractorErr == errHeaderExtractorValueInvalid { + err = errors.New("invalid key in the request header") + } else { + err = lastExtractorErr + } + err = &ErrKeyAuthMissing{Err: err} + } + + if config.ErrorHandler != nil { + tmpErr := config.ErrorHandler(err, c) + if config.ContinueOnIgnoredError && tmpErr == nil { + return next(c) + } + return tmpErr + } + if lastValidatorErr != nil { // prioritize validator errors over extracting errors + return &echo.HTTPError{ + Code: http.StatusUnauthorized, + Message: "Unauthorized", + Internal: lastValidatorErr, + } + } + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/logger.go b/vendor/github.com/labstack/echo/v4/middleware/logger.go new file mode 100644 index 00000000..910fce8c --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/logger.go @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "bytes" + "encoding/json" + "io" + "strconv" + "strings" + "sync" + "time" + + "github.com/labstack/echo/v4" + "github.com/labstack/gommon/color" + "github.com/valyala/fasttemplate" +) + +// LoggerConfig defines the config for Logger middleware. +type LoggerConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Tags to construct the logger format. + // + // - time_unix + // - time_unix_milli + // - time_unix_micro + // - time_unix_nano + // - time_rfc3339 + // - time_rfc3339_nano + // - time_custom + // - id (Request ID) + // - remote_ip + // - uri + // - host + // - method + // - path + // - route + // - protocol + // - referer + // - user_agent + // - status + // - error + // - latency (In nanoseconds) + // - latency_human (Human readable) + // - bytes_in (Bytes received) + // - bytes_out (Bytes sent) + // - header: + // - query: + // - form: + // - custom (see CustomTagFunc field) + // + // Example "${remote_ip} ${status}" + // + // Optional. Default value DefaultLoggerConfig.Format. + Format string `yaml:"format"` + + // Optional. Default value DefaultLoggerConfig.CustomTimeFormat. + CustomTimeFormat string `yaml:"custom_time_format"` + + // CustomTagFunc is function called for `${custom}` tag to output user implemented text by writing it to buf. + // Make sure that outputted text creates valid JSON string with other logged tags. + // Optional. + CustomTagFunc func(c echo.Context, buf *bytes.Buffer) (int, error) + + // Output is a writer where logs in JSON format are written. + // Optional. Default value os.Stdout. + Output io.Writer + + template *fasttemplate.Template + colorer *color.Color + pool *sync.Pool +} + +// DefaultLoggerConfig is the default Logger middleware config. +var DefaultLoggerConfig = LoggerConfig{ + Skipper: DefaultSkipper, + Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` + + `"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` + + `"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` + + `,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n", + CustomTimeFormat: "2006-01-02 15:04:05.00000", + colorer: color.New(), +} + +// Logger returns a middleware that logs HTTP requests. +func Logger() echo.MiddlewareFunc { + return LoggerWithConfig(DefaultLoggerConfig) +} + +// LoggerWithConfig returns a Logger middleware with config. +// See: `Logger()`. +func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultLoggerConfig.Skipper + } + if config.Format == "" { + config.Format = DefaultLoggerConfig.Format + } + if config.Output == nil { + config.Output = DefaultLoggerConfig.Output + } + + config.template = fasttemplate.New(config.Format, "${", "}") + config.colorer = color.New() + config.colorer.SetOutput(config.Output) + config.pool = &sync.Pool{ + New: func() interface{} { + return bytes.NewBuffer(make([]byte, 256)) + }, + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (err error) { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + res := c.Response() + start := time.Now() + if err = next(c); err != nil { + c.Error(err) + } + stop := time.Now() + buf := config.pool.Get().(*bytes.Buffer) + buf.Reset() + defer config.pool.Put(buf) + + if _, err = config.template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) { + switch tag { + case "custom": + if config.CustomTagFunc == nil { + return 0, nil + } + return config.CustomTagFunc(c, buf) + case "time_unix": + return buf.WriteString(strconv.FormatInt(time.Now().Unix(), 10)) + case "time_unix_milli": + // go 1.17 or later, it supports time#UnixMilli() + return buf.WriteString(strconv.FormatInt(time.Now().UnixNano()/1000000, 10)) + case "time_unix_micro": + // go 1.17 or later, it supports time#UnixMicro() + return buf.WriteString(strconv.FormatInt(time.Now().UnixNano()/1000, 10)) + case "time_unix_nano": + return buf.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10)) + case "time_rfc3339": + return buf.WriteString(time.Now().Format(time.RFC3339)) + case "time_rfc3339_nano": + return buf.WriteString(time.Now().Format(time.RFC3339Nano)) + case "time_custom": + return buf.WriteString(time.Now().Format(config.CustomTimeFormat)) + case "id": + id := req.Header.Get(echo.HeaderXRequestID) + if id == "" { + id = res.Header().Get(echo.HeaderXRequestID) + } + return buf.WriteString(id) + case "remote_ip": + return buf.WriteString(c.RealIP()) + case "host": + return buf.WriteString(req.Host) + case "uri": + return buf.WriteString(req.RequestURI) + case "method": + return buf.WriteString(req.Method) + case "path": + p := req.URL.Path + if p == "" { + p = "/" + } + return buf.WriteString(p) + case "route": + return buf.WriteString(c.Path()) + case "protocol": + return buf.WriteString(req.Proto) + case "referer": + return buf.WriteString(req.Referer()) + case "user_agent": + return buf.WriteString(req.UserAgent()) + case "status": + n := res.Status + s := config.colorer.Green(n) + switch { + case n >= 500: + s = config.colorer.Red(n) + case n >= 400: + s = config.colorer.Yellow(n) + case n >= 300: + s = config.colorer.Cyan(n) + } + return buf.WriteString(s) + case "error": + if err != nil { + // Error may contain invalid JSON e.g. `"` + b, _ := json.Marshal(err.Error()) + b = b[1 : len(b)-1] + return buf.Write(b) + } + case "latency": + l := stop.Sub(start) + return buf.WriteString(strconv.FormatInt(int64(l), 10)) + case "latency_human": + return buf.WriteString(stop.Sub(start).String()) + case "bytes_in": + cl := req.Header.Get(echo.HeaderContentLength) + if cl == "" { + cl = "0" + } + return buf.WriteString(cl) + case "bytes_out": + return buf.WriteString(strconv.FormatInt(res.Size, 10)) + default: + switch { + case strings.HasPrefix(tag, "header:"): + return buf.Write([]byte(c.Request().Header.Get(tag[7:]))) + case strings.HasPrefix(tag, "query:"): + return buf.Write([]byte(c.QueryParam(tag[6:]))) + case strings.HasPrefix(tag, "form:"): + return buf.Write([]byte(c.FormValue(tag[5:]))) + case strings.HasPrefix(tag, "cookie:"): + cookie, err := c.Cookie(tag[7:]) + if err == nil { + return buf.Write([]byte(cookie.Value)) + } + } + } + return 0, nil + }); err != nil { + return + } + + if config.Output == nil { + _, err = c.Logger().Output().Write(buf.Bytes()) + return + } + _, err = config.Output.Write(buf.Bytes()) + return + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/method_override.go b/vendor/github.com/labstack/echo/v4/middleware/method_override.go new file mode 100644 index 00000000..3991e102 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/method_override.go @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +// MethodOverrideConfig defines the config for MethodOverride middleware. +type MethodOverrideConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Getter is a function that gets overridden method from the request. + // Optional. Default values MethodFromHeader(echo.HeaderXHTTPMethodOverride). + Getter MethodOverrideGetter +} + +// MethodOverrideGetter is a function that gets overridden method from the request +type MethodOverrideGetter func(echo.Context) string + +// DefaultMethodOverrideConfig is the default MethodOverride middleware config. +var DefaultMethodOverrideConfig = MethodOverrideConfig{ + Skipper: DefaultSkipper, + Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride), +} + +// MethodOverride returns a MethodOverride middleware. +// MethodOverride middleware checks for the overridden method from the request and +// uses it instead of the original method. +// +// For security reasons, only `POST` method can be overridden. +func MethodOverride() echo.MiddlewareFunc { + return MethodOverrideWithConfig(DefaultMethodOverrideConfig) +} + +// MethodOverrideWithConfig returns a MethodOverride middleware with config. +// See: `MethodOverride()`. +func MethodOverrideWithConfig(config MethodOverrideConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultMethodOverrideConfig.Skipper + } + if config.Getter == nil { + config.Getter = DefaultMethodOverrideConfig.Getter + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + if req.Method == http.MethodPost { + m := config.Getter(c) + if m != "" { + req.Method = m + } + } + return next(c) + } + } +} + +// MethodFromHeader is a `MethodOverrideGetter` that gets overridden method from +// the request header. +func MethodFromHeader(header string) MethodOverrideGetter { + return func(c echo.Context) string { + return c.Request().Header.Get(header) + } +} + +// MethodFromForm is a `MethodOverrideGetter` that gets overridden method from the +// form parameter. +func MethodFromForm(param string) MethodOverrideGetter { + return func(c echo.Context) string { + return c.FormValue(param) + } +} + +// MethodFromQuery is a `MethodOverrideGetter` that gets overridden method from +// the query parameter. +func MethodFromQuery(param string) MethodOverrideGetter { + return func(c echo.Context) string { + return c.QueryParam(param) + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/middleware.go b/vendor/github.com/labstack/echo/v4/middleware/middleware.go new file mode 100644 index 00000000..6f33cc5c --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/middleware.go @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "net/http" + "regexp" + "strconv" + "strings" + + "github.com/labstack/echo/v4" +) + +// Skipper defines a function to skip middleware. Returning true skips processing +// the middleware. +type Skipper func(c echo.Context) bool + +// BeforeFunc defines a function which is executed just before the middleware. +type BeforeFunc func(c echo.Context) + +func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer { + groups := pattern.FindAllStringSubmatch(input, -1) + if groups == nil { + return nil + } + values := groups[0][1:] + replace := make([]string, 2*len(values)) + for i, v := range values { + j := 2 * i + replace[j] = "$" + strconv.Itoa(i+1) + replace[j+1] = v + } + return strings.NewReplacer(replace...) +} + +func rewriteRulesRegex(rewrite map[string]string) map[*regexp.Regexp]string { + // Initialize + rulesRegex := map[*regexp.Regexp]string{} + for k, v := range rewrite { + k = regexp.QuoteMeta(k) + k = strings.ReplaceAll(k, `\*`, "(.*?)") + if strings.HasPrefix(k, `\^`) { + k = strings.ReplaceAll(k, `\^`, "^") + } + k = k + "$" + rulesRegex[regexp.MustCompile(k)] = v + } + return rulesRegex +} + +func rewriteURL(rewriteRegex map[*regexp.Regexp]string, req *http.Request) error { + if len(rewriteRegex) == 0 { + return nil + } + + // Depending on how HTTP request is sent RequestURI could contain Scheme://Host/path or be just /path. + // We only want to use path part for rewriting and therefore trim prefix if it exists + rawURI := req.RequestURI + if rawURI != "" && rawURI[0] != '/' { + prefix := "" + if req.URL.Scheme != "" { + prefix = req.URL.Scheme + "://" + } + if req.URL.Host != "" { + prefix += req.URL.Host // host or host:port + } + if prefix != "" { + rawURI = strings.TrimPrefix(rawURI, prefix) + } + } + + for k, v := range rewriteRegex { + if replacer := captureTokens(k, rawURI); replacer != nil { + url, err := req.URL.Parse(replacer.Replace(v)) + if err != nil { + return err + } + req.URL = url + + return nil // rewrite only once + } + } + return nil +} + +// DefaultSkipper returns false which processes the middleware. +func DefaultSkipper(echo.Context) bool { + return false +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/proxy.go b/vendor/github.com/labstack/echo/v4/middleware/proxy.go new file mode 100644 index 00000000..495970ac --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/proxy.go @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "context" + "fmt" + "io" + "math/rand" + "net" + "net/http" + "net/http/httputil" + "net/url" + "regexp" + "strings" + "sync" + "time" + + "github.com/labstack/echo/v4" +) + +// TODO: Handle TLS proxy + +// ProxyConfig defines the config for Proxy middleware. +type ProxyConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Balancer defines a load balancing technique. + // Required. + Balancer ProxyBalancer + + // RetryCount defines the number of times a failed proxied request should be retried + // using the next available ProxyTarget. Defaults to 0, meaning requests are never retried. + RetryCount int + + // RetryFilter defines a function used to determine if a failed request to a + // ProxyTarget should be retried. The RetryFilter will only be called when the number + // of previous retries is less than RetryCount. If the function returns true, the + // request will be retried. The provided error indicates the reason for the request + // failure. When the ProxyTarget is unavailable, the error will be an instance of + // echo.HTTPError with a Code of http.StatusBadGateway. In all other cases, the error + // will indicate an internal error in the Proxy middleware. When a RetryFilter is not + // specified, all requests that fail with http.StatusBadGateway will be retried. A custom + // RetryFilter can be provided to only retry specific requests. Note that RetryFilter is + // only called when the request to the target fails, or an internal error in the Proxy + // middleware has occurred. Successful requests that return a non-200 response code cannot + // be retried. + RetryFilter func(c echo.Context, e error) bool + + // ErrorHandler defines a function which can be used to return custom errors from + // the Proxy middleware. ErrorHandler is only invoked when there has been + // either an internal error in the Proxy middleware or the ProxyTarget is + // unavailable. Due to the way requests are proxied, ErrorHandler is not invoked + // when a ProxyTarget returns a non-200 response. In these cases, the response + // is already written so errors cannot be modified. ErrorHandler is only + // invoked after all retry attempts have been exhausted. + ErrorHandler func(c echo.Context, err error) error + + // Rewrite defines URL path rewrite rules. The values captured in asterisk can be + // retrieved by index e.g. $1, $2 and so on. + // Examples: + // "/old": "/new", + // "/api/*": "/$1", + // "/js/*": "/public/javascripts/$1", + // "/users/*/orders/*": "/user/$1/order/$2", + Rewrite map[string]string + + // RegexRewrite defines rewrite rules using regexp.Rexexp with captures + // Every capture group in the values can be retrieved by index e.g. $1, $2 and so on. + // Example: + // "^/old/[0.9]+/": "/new", + // "^/api/.+?/(.*)": "/v2/$1", + RegexRewrite map[*regexp.Regexp]string + + // Context key to store selected ProxyTarget into context. + // Optional. Default value "target". + ContextKey string + + // To customize the transport to remote. + // Examples: If custom TLS certificates are required. + Transport http.RoundTripper + + // ModifyResponse defines function to modify response from ProxyTarget. + ModifyResponse func(*http.Response) error +} + +// ProxyTarget defines the upstream target. +type ProxyTarget struct { + Name string + URL *url.URL + Meta echo.Map +} + +// ProxyBalancer defines an interface to implement a load balancing technique. +type ProxyBalancer interface { + AddTarget(*ProxyTarget) bool + RemoveTarget(string) bool + Next(echo.Context) *ProxyTarget +} + +// TargetProvider defines an interface that gives the opportunity for balancer +// to return custom errors when selecting target. +type TargetProvider interface { + NextTarget(echo.Context) (*ProxyTarget, error) +} + +type commonBalancer struct { + targets []*ProxyTarget + mutex sync.Mutex +} + +// RandomBalancer implements a random load balancing technique. +type randomBalancer struct { + commonBalancer + random *rand.Rand +} + +// RoundRobinBalancer implements a round-robin load balancing technique. +type roundRobinBalancer struct { + commonBalancer + // tracking the index on `targets` slice for the next `*ProxyTarget` to be used + i int +} + +// DefaultProxyConfig is the default Proxy middleware config. +var DefaultProxyConfig = ProxyConfig{ + Skipper: DefaultSkipper, + ContextKey: "target", +} + +func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + in, _, err := c.Response().Hijack() + if err != nil { + c.Set("_error", fmt.Errorf("proxy raw, hijack error=%w, url=%s", err, t.URL)) + return + } + defer in.Close() + + out, err := net.Dial("tcp", t.URL.Host) + if err != nil { + c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", err, t.URL))) + return + } + defer out.Close() + + // Write header + err = r.Write(out) + if err != nil { + c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", err, t.URL))) + return + } + + errCh := make(chan error, 2) + cp := func(dst io.Writer, src io.Reader) { + _, err = io.Copy(dst, src) + errCh <- err + } + + go cp(out, in) + go cp(in, out) + err = <-errCh + if err != nil && err != io.EOF { + c.Set("_error", fmt.Errorf("proxy raw, copy body error=%w, url=%s", err, t.URL)) + } + }) +} + +// NewRandomBalancer returns a random proxy balancer. +func NewRandomBalancer(targets []*ProxyTarget) ProxyBalancer { + b := randomBalancer{} + b.targets = targets + b.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) + return &b +} + +// NewRoundRobinBalancer returns a round-robin proxy balancer. +func NewRoundRobinBalancer(targets []*ProxyTarget) ProxyBalancer { + b := roundRobinBalancer{} + b.targets = targets + return &b +} + +// AddTarget adds an upstream target to the list and returns `true`. +// +// However, if a target with the same name already exists then the operation is aborted returning `false`. +func (b *commonBalancer) AddTarget(target *ProxyTarget) bool { + b.mutex.Lock() + defer b.mutex.Unlock() + for _, t := range b.targets { + if t.Name == target.Name { + return false + } + } + b.targets = append(b.targets, target) + return true +} + +// RemoveTarget removes an upstream target from the list by name. +// +// Returns `true` on success, `false` if no target with the name is found. +func (b *commonBalancer) RemoveTarget(name string) bool { + b.mutex.Lock() + defer b.mutex.Unlock() + for i, t := range b.targets { + if t.Name == name { + b.targets = append(b.targets[:i], b.targets[i+1:]...) + return true + } + } + return false +} + +// Next randomly returns an upstream target. +// +// Note: `nil` is returned in case upstream target list is empty. +func (b *randomBalancer) Next(c echo.Context) *ProxyTarget { + b.mutex.Lock() + defer b.mutex.Unlock() + if len(b.targets) == 0 { + return nil + } else if len(b.targets) == 1 { + return b.targets[0] + } + return b.targets[b.random.Intn(len(b.targets))] +} + +// Next returns an upstream target using round-robin technique. In the case +// where a previously failed request is being retried, the round-robin +// balancer will attempt to use the next target relative to the original +// request. If the list of targets held by the balancer is modified while a +// failed request is being retried, it is possible that the balancer will +// return the original failed target. +// +// Note: `nil` is returned in case upstream target list is empty. +func (b *roundRobinBalancer) Next(c echo.Context) *ProxyTarget { + b.mutex.Lock() + defer b.mutex.Unlock() + if len(b.targets) == 0 { + return nil + } else if len(b.targets) == 1 { + return b.targets[0] + } + + var i int + const lastIdxKey = "_round_robin_last_index" + // This request is a retry, start from the index of the previous + // target to ensure we don't attempt to retry the request with + // the same failed target + if c.Get(lastIdxKey) != nil { + i = c.Get(lastIdxKey).(int) + i++ + if i >= len(b.targets) { + i = 0 + } + } else { + // This is a first time request, use the global index + if b.i >= len(b.targets) { + b.i = 0 + } + i = b.i + b.i++ + } + + c.Set(lastIdxKey, i) + return b.targets[i] +} + +// Proxy returns a Proxy middleware. +// +// Proxy middleware forwards the request to upstream server using a configured load balancing technique. +func Proxy(balancer ProxyBalancer) echo.MiddlewareFunc { + c := DefaultProxyConfig + c.Balancer = balancer + return ProxyWithConfig(c) +} + +// ProxyWithConfig returns a Proxy middleware with config. +// See: `Proxy()` +func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc { + if config.Balancer == nil { + panic("echo: proxy middleware requires balancer") + } + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultProxyConfig.Skipper + } + if config.RetryFilter == nil { + config.RetryFilter = func(c echo.Context, e error) bool { + if httpErr, ok := e.(*echo.HTTPError); ok { + return httpErr.Code == http.StatusBadGateway + } + return false + } + } + if config.ErrorHandler == nil { + config.ErrorHandler = func(c echo.Context, err error) error { + return err + } + } + if config.Rewrite != nil { + if config.RegexRewrite == nil { + config.RegexRewrite = make(map[*regexp.Regexp]string) + } + for k, v := range rewriteRulesRegex(config.Rewrite) { + config.RegexRewrite[k] = v + } + } + + provider, isTargetProvider := config.Balancer.(TargetProvider) + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + res := c.Response() + if err := rewriteURL(config.RegexRewrite, req); err != nil { + return config.ErrorHandler(c, err) + } + + // Fix header + // Basically it's not good practice to unconditionally pass incoming x-real-ip header to upstream. + // However, for backward compatibility, legacy behavior is preserved unless you configure Echo#IPExtractor. + if req.Header.Get(echo.HeaderXRealIP) == "" || c.Echo().IPExtractor != nil { + req.Header.Set(echo.HeaderXRealIP, c.RealIP()) + } + if req.Header.Get(echo.HeaderXForwardedProto) == "" { + req.Header.Set(echo.HeaderXForwardedProto, c.Scheme()) + } + if c.IsWebSocket() && req.Header.Get(echo.HeaderXForwardedFor) == "" { // For HTTP, it is automatically set by Go HTTP reverse proxy. + req.Header.Set(echo.HeaderXForwardedFor, c.RealIP()) + } + + retries := config.RetryCount + for { + var tgt *ProxyTarget + var err error + if isTargetProvider { + tgt, err = provider.NextTarget(c) + if err != nil { + return config.ErrorHandler(c, err) + } + } else { + tgt = config.Balancer.Next(c) + } + + c.Set(config.ContextKey, tgt) + + //If retrying a failed request, clear any previous errors from + //context here so that balancers have the option to check for + //errors that occurred using previous target + if retries < config.RetryCount { + c.Set("_error", nil) + } + + // This is needed for ProxyConfig.ModifyResponse and/or ProxyConfig.Transport to be able to process the Request + // that Balancer may have replaced with c.SetRequest. + req = c.Request() + + // Proxy + switch { + case c.IsWebSocket(): + proxyRaw(tgt, c).ServeHTTP(res, req) + default: // even SSE requests + proxyHTTP(tgt, c, config).ServeHTTP(res, req) + } + + err, hasError := c.Get("_error").(error) + if !hasError { + return nil + } + + retry := retries > 0 && config.RetryFilter(c, err) + if !retry { + return config.ErrorHandler(c, err) + } + + retries-- + } + } + } +} + +// StatusCodeContextCanceled is a custom HTTP status code for situations +// where a client unexpectedly closed the connection to the server. +// As there is no standard error code for "client closed connection", but +// various well-known HTTP clients and server implement this HTTP code we use +// 499 too instead of the more problematic 5xx, which does not allow to detect this situation +const StatusCodeContextCanceled = 499 + +func proxyHTTP(tgt *ProxyTarget, c echo.Context, config ProxyConfig) http.Handler { + proxy := httputil.NewSingleHostReverseProxy(tgt.URL) + proxy.ErrorHandler = func(resp http.ResponseWriter, req *http.Request, err error) { + desc := tgt.URL.String() + if tgt.Name != "" { + desc = fmt.Sprintf("%s(%s)", tgt.Name, tgt.URL.String()) + } + // If the client canceled the request (usually by closing the connection), we can report a + // client error (4xx) instead of a server error (5xx) to correctly identify the situation. + // The Go standard library (at of late 2020) wraps the exported, standard + // context.Canceled error with unexported garbage value requiring a substring check, see + // https://github.com/golang/go/blob/6965b01ea248cabb70c3749fd218b36089a21efb/src/net/net.go#L416-L430 + if err == context.Canceled || strings.Contains(err.Error(), "operation was canceled") { + httpError := echo.NewHTTPError(StatusCodeContextCanceled, fmt.Sprintf("client closed connection: %v", err)) + httpError.Internal = err + c.Set("_error", httpError) + } else { + httpError := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("remote %s unreachable, could not forward: %v", desc, err)) + httpError.Internal = err + c.Set("_error", httpError) + } + } + proxy.Transport = config.Transport + proxy.ModifyResponse = config.ModifyResponse + return proxy +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go b/vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go new file mode 100644 index 00000000..d4724fd2 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "net/http" + "sync" + "time" + + "github.com/labstack/echo/v4" + "golang.org/x/time/rate" +) + +// RateLimiterStore is the interface to be implemented by custom stores. +type RateLimiterStore interface { + // Stores for the rate limiter have to implement the Allow method + Allow(identifier string) (bool, error) +} + +// RateLimiterConfig defines the configuration for the rate limiter +type RateLimiterConfig struct { + Skipper Skipper + BeforeFunc BeforeFunc + // IdentifierExtractor uses echo.Context to extract the identifier for a visitor + IdentifierExtractor Extractor + // Store defines a store for the rate limiter + Store RateLimiterStore + // ErrorHandler provides a handler to be called when IdentifierExtractor returns an error + ErrorHandler func(context echo.Context, err error) error + // DenyHandler provides a handler to be called when RateLimiter denies access + DenyHandler func(context echo.Context, identifier string, err error) error +} + +// Extractor is used to extract data from echo.Context +type Extractor func(context echo.Context) (string, error) + +// ErrRateLimitExceeded denotes an error raised when rate limit is exceeded +var ErrRateLimitExceeded = echo.NewHTTPError(http.StatusTooManyRequests, "rate limit exceeded") + +// ErrExtractorError denotes an error raised when extractor function is unsuccessful +var ErrExtractorError = echo.NewHTTPError(http.StatusForbidden, "error while extracting identifier") + +// DefaultRateLimiterConfig defines default values for RateLimiterConfig +var DefaultRateLimiterConfig = RateLimiterConfig{ + Skipper: DefaultSkipper, + IdentifierExtractor: func(ctx echo.Context) (string, error) { + id := ctx.RealIP() + return id, nil + }, + ErrorHandler: func(context echo.Context, err error) error { + return &echo.HTTPError{ + Code: ErrExtractorError.Code, + Message: ErrExtractorError.Message, + Internal: err, + } + }, + DenyHandler: func(context echo.Context, identifier string, err error) error { + return &echo.HTTPError{ + Code: ErrRateLimitExceeded.Code, + Message: ErrRateLimitExceeded.Message, + Internal: err, + } + }, +} + +/* +RateLimiter returns a rate limiting middleware + + e := echo.New() + + limiterStore := middleware.NewRateLimiterMemoryStore(20) + + e.GET("/rate-limited", func(c echo.Context) error { + return c.String(http.StatusOK, "test") + }, RateLimiter(limiterStore)) +*/ +func RateLimiter(store RateLimiterStore) echo.MiddlewareFunc { + config := DefaultRateLimiterConfig + config.Store = store + + return RateLimiterWithConfig(config) +} + +/* +RateLimiterWithConfig returns a rate limiting middleware + + e := echo.New() + + config := middleware.RateLimiterConfig{ + Skipper: DefaultSkipper, + Store: middleware.NewRateLimiterMemoryStore( + middleware.RateLimiterMemoryStoreConfig{Rate: 10, Burst: 30, ExpiresIn: 3 * time.Minute} + ) + IdentifierExtractor: func(ctx echo.Context) (string, error) { + id := ctx.RealIP() + return id, nil + }, + ErrorHandler: func(context echo.Context, err error) error { + return context.JSON(http.StatusTooManyRequests, nil) + }, + DenyHandler: func(context echo.Context, identifier string) error { + return context.JSON(http.StatusForbidden, nil) + }, + } + + e.GET("/rate-limited", func(c echo.Context) error { + return c.String(http.StatusOK, "test") + }, middleware.RateLimiterWithConfig(config)) +*/ +func RateLimiterWithConfig(config RateLimiterConfig) echo.MiddlewareFunc { + if config.Skipper == nil { + config.Skipper = DefaultRateLimiterConfig.Skipper + } + if config.IdentifierExtractor == nil { + config.IdentifierExtractor = DefaultRateLimiterConfig.IdentifierExtractor + } + if config.ErrorHandler == nil { + config.ErrorHandler = DefaultRateLimiterConfig.ErrorHandler + } + if config.DenyHandler == nil { + config.DenyHandler = DefaultRateLimiterConfig.DenyHandler + } + if config.Store == nil { + panic("Store configuration must be provided") + } + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + if config.BeforeFunc != nil { + config.BeforeFunc(c) + } + + identifier, err := config.IdentifierExtractor(c) + if err != nil { + c.Error(config.ErrorHandler(c, err)) + return nil + } + + if allow, err := config.Store.Allow(identifier); !allow { + c.Error(config.DenyHandler(c, identifier, err)) + return nil + } + return next(c) + } + } +} + +// RateLimiterMemoryStore is the built-in store implementation for RateLimiter +type RateLimiterMemoryStore struct { + visitors map[string]*Visitor + mutex sync.Mutex + rate rate.Limit // for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit. + + burst int + expiresIn time.Duration + lastCleanup time.Time + + timeNow func() time.Time +} + +// Visitor signifies a unique user's limiter details +type Visitor struct { + *rate.Limiter + lastSeen time.Time +} + +/* +NewRateLimiterMemoryStore returns an instance of RateLimiterMemoryStore with +the provided rate (as req/s). +for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit. + +Burst and ExpiresIn will be set to default values. + +Note that if the provided rate is a float number and Burst is zero, Burst will be treated as the rounded down value of the rate. + +Example (with 20 requests/sec): + + limiterStore := middleware.NewRateLimiterMemoryStore(20) +*/ +func NewRateLimiterMemoryStore(rate rate.Limit) (store *RateLimiterMemoryStore) { + return NewRateLimiterMemoryStoreWithConfig(RateLimiterMemoryStoreConfig{ + Rate: rate, + }) +} + +/* +NewRateLimiterMemoryStoreWithConfig returns an instance of RateLimiterMemoryStore +with the provided configuration. Rate must be provided. Burst will be set to the rounded down value of +the configured rate if not provided or set to 0. + +The build-in memory store is usually capable for modest loads. For higher loads other +store implementations should be considered. + +Characteristics: +* Concurrency above 100 parallel requests may causes measurable lock contention +* A high number of different IP addresses (above 16000) may be impacted by the internally used Go map +* A high number of requests from a single IP address may cause lock contention + +Example: + + limiterStore := middleware.NewRateLimiterMemoryStoreWithConfig( + middleware.RateLimiterMemoryStoreConfig{Rate: 50, Burst: 200, ExpiresIn: 5 * time.Minute}, + ) +*/ +func NewRateLimiterMemoryStoreWithConfig(config RateLimiterMemoryStoreConfig) (store *RateLimiterMemoryStore) { + store = &RateLimiterMemoryStore{} + + store.rate = config.Rate + store.burst = config.Burst + store.expiresIn = config.ExpiresIn + if config.ExpiresIn == 0 { + store.expiresIn = DefaultRateLimiterMemoryStoreConfig.ExpiresIn + } + if config.Burst == 0 { + store.burst = int(config.Rate) + } + store.visitors = make(map[string]*Visitor) + store.timeNow = time.Now + store.lastCleanup = store.timeNow() + return +} + +// RateLimiterMemoryStoreConfig represents configuration for RateLimiterMemoryStore +type RateLimiterMemoryStoreConfig struct { + Rate rate.Limit // Rate of requests allowed to pass as req/s. For more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit. + Burst int // Burst is maximum number of requests to pass at the same moment. It additionally allows a number of requests to pass when rate limit is reached. + ExpiresIn time.Duration // ExpiresIn is the duration after that a rate limiter is cleaned up +} + +// DefaultRateLimiterMemoryStoreConfig provides default configuration values for RateLimiterMemoryStore +var DefaultRateLimiterMemoryStoreConfig = RateLimiterMemoryStoreConfig{ + ExpiresIn: 3 * time.Minute, +} + +// Allow implements RateLimiterStore.Allow +func (store *RateLimiterMemoryStore) Allow(identifier string) (bool, error) { + store.mutex.Lock() + limiter, exists := store.visitors[identifier] + if !exists { + limiter = new(Visitor) + limiter.Limiter = rate.NewLimiter(store.rate, store.burst) + store.visitors[identifier] = limiter + } + now := store.timeNow() + limiter.lastSeen = now + if now.Sub(store.lastCleanup) > store.expiresIn { + store.cleanupStaleVisitors() + } + store.mutex.Unlock() + return limiter.AllowN(store.timeNow(), 1), nil +} + +/* +cleanupStaleVisitors helps manage the size of the visitors map by removing stale records +of users who haven't visited again after the configured expiry time has elapsed +*/ +func (store *RateLimiterMemoryStore) cleanupStaleVisitors() { + for id, visitor := range store.visitors { + if store.timeNow().Sub(visitor.lastSeen) > store.expiresIn { + delete(store.visitors, id) + } + } + store.lastCleanup = store.timeNow() +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/recover.go b/vendor/github.com/labstack/echo/v4/middleware/recover.go new file mode 100644 index 00000000..e6a5940e --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/recover.go @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "fmt" + "net/http" + "runtime" + + "github.com/labstack/echo/v4" + "github.com/labstack/gommon/log" +) + +// LogErrorFunc defines a function for custom logging in the middleware. +type LogErrorFunc func(c echo.Context, err error, stack []byte) error + +// RecoverConfig defines the config for Recover middleware. +type RecoverConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Size of the stack to be printed. + // Optional. Default value 4KB. + StackSize int `yaml:"stack_size"` + + // DisableStackAll disables formatting stack traces of all other goroutines + // into buffer after the trace for the current goroutine. + // Optional. Default value false. + DisableStackAll bool `yaml:"disable_stack_all"` + + // DisablePrintStack disables printing stack trace. + // Optional. Default value as false. + DisablePrintStack bool `yaml:"disable_print_stack"` + + // LogLevel is log level to printing stack trace. + // Optional. Default value 0 (Print). + LogLevel log.Lvl + + // LogErrorFunc defines a function for custom logging in the middleware. + // If it's set you don't need to provide LogLevel for config. + // If this function returns nil, the centralized HTTPErrorHandler will not be called. + LogErrorFunc LogErrorFunc + + // DisableErrorHandler disables the call to centralized HTTPErrorHandler. + // The recovered error is then passed back to upstream middleware, instead of swallowing the error. + // Optional. Default value false. + DisableErrorHandler bool `yaml:"disable_error_handler"` +} + +// DefaultRecoverConfig is the default Recover middleware config. +var DefaultRecoverConfig = RecoverConfig{ + Skipper: DefaultSkipper, + StackSize: 4 << 10, // 4 KB + DisableStackAll: false, + DisablePrintStack: false, + LogLevel: 0, + LogErrorFunc: nil, + DisableErrorHandler: false, +} + +// Recover returns a middleware which recovers from panics anywhere in the chain +// and handles the control to the centralized HTTPErrorHandler. +func Recover() echo.MiddlewareFunc { + return RecoverWithConfig(DefaultRecoverConfig) +} + +// RecoverWithConfig returns a Recover middleware with config. +// See: `Recover()`. +func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultRecoverConfig.Skipper + } + if config.StackSize == 0 { + config.StackSize = DefaultRecoverConfig.StackSize + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (returnErr error) { + if config.Skipper(c) { + return next(c) + } + + defer func() { + if r := recover(); r != nil { + if r == http.ErrAbortHandler { + panic(r) + } + err, ok := r.(error) + if !ok { + err = fmt.Errorf("%v", r) + } + var stack []byte + var length int + + if !config.DisablePrintStack { + stack = make([]byte, config.StackSize) + length = runtime.Stack(stack, !config.DisableStackAll) + stack = stack[:length] + } + + if config.LogErrorFunc != nil { + err = config.LogErrorFunc(c, err, stack) + } else if !config.DisablePrintStack { + msg := fmt.Sprintf("[PANIC RECOVER] %v %s\n", err, stack[:length]) + switch config.LogLevel { + case log.DEBUG: + c.Logger().Debug(msg) + case log.INFO: + c.Logger().Info(msg) + case log.WARN: + c.Logger().Warn(msg) + case log.ERROR: + c.Logger().Error(msg) + case log.OFF: + // None. + default: + c.Logger().Print(msg) + } + } + + if err != nil && !config.DisableErrorHandler { + c.Error(err) + } else { + returnErr = err + } + } + }() + return next(c) + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/redirect.go b/vendor/github.com/labstack/echo/v4/middleware/redirect.go new file mode 100644 index 00000000..b772ac13 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/redirect.go @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "net/http" + "strings" + + "github.com/labstack/echo/v4" +) + +// RedirectConfig defines the config for Redirect middleware. +type RedirectConfig struct { + // Skipper defines a function to skip middleware. + Skipper + + // Status code to be used when redirecting the request. + // Optional. Default value http.StatusMovedPermanently. + Code int `yaml:"code"` +} + +// redirectLogic represents a function that given a scheme, host and uri +// can both: 1) determine if redirect is needed (will set ok accordingly) and +// 2) return the appropriate redirect url. +type redirectLogic func(scheme, host, uri string) (ok bool, url string) + +const www = "www." + +// DefaultRedirectConfig is the default Redirect middleware config. +var DefaultRedirectConfig = RedirectConfig{ + Skipper: DefaultSkipper, + Code: http.StatusMovedPermanently, +} + +// HTTPSRedirect redirects http requests to https. +// For example, http://labstack.com will be redirect to https://labstack.com. +// +// Usage `Echo#Pre(HTTPSRedirect())` +func HTTPSRedirect() echo.MiddlewareFunc { + return HTTPSRedirectWithConfig(DefaultRedirectConfig) +} + +// HTTPSRedirectWithConfig returns an HTTPSRedirect middleware with config. +// See `HTTPSRedirect()`. +func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { + return redirect(config, func(scheme, host, uri string) (bool, string) { + if scheme != "https" { + return true, "https://" + host + uri + } + return false, "" + }) +} + +// HTTPSWWWRedirect redirects http requests to https www. +// For example, http://labstack.com will be redirect to https://www.labstack.com. +// +// Usage `Echo#Pre(HTTPSWWWRedirect())` +func HTTPSWWWRedirect() echo.MiddlewareFunc { + return HTTPSWWWRedirectWithConfig(DefaultRedirectConfig) +} + +// HTTPSWWWRedirectWithConfig returns an HTTPSRedirect middleware with config. +// See `HTTPSWWWRedirect()`. +func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { + return redirect(config, func(scheme, host, uri string) (bool, string) { + if scheme != "https" && !strings.HasPrefix(host, www) { + return true, "https://www." + host + uri + } + return false, "" + }) +} + +// HTTPSNonWWWRedirect redirects http requests to https non www. +// For example, http://www.labstack.com will be redirect to https://labstack.com. +// +// Usage `Echo#Pre(HTTPSNonWWWRedirect())` +func HTTPSNonWWWRedirect() echo.MiddlewareFunc { + return HTTPSNonWWWRedirectWithConfig(DefaultRedirectConfig) +} + +// HTTPSNonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config. +// See `HTTPSNonWWWRedirect()`. +func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { + return redirect(config, func(scheme, host, uri string) (ok bool, url string) { + if scheme != "https" { + host = strings.TrimPrefix(host, www) + return true, "https://" + host + uri + } + return false, "" + }) +} + +// WWWRedirect redirects non www requests to www. +// For example, http://labstack.com will be redirect to http://www.labstack.com. +// +// Usage `Echo#Pre(WWWRedirect())` +func WWWRedirect() echo.MiddlewareFunc { + return WWWRedirectWithConfig(DefaultRedirectConfig) +} + +// WWWRedirectWithConfig returns an HTTPSRedirect middleware with config. +// See `WWWRedirect()`. +func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { + return redirect(config, func(scheme, host, uri string) (bool, string) { + if !strings.HasPrefix(host, www) { + return true, scheme + "://www." + host + uri + } + return false, "" + }) +} + +// NonWWWRedirect redirects www requests to non www. +// For example, http://www.labstack.com will be redirect to http://labstack.com. +// +// Usage `Echo#Pre(NonWWWRedirect())` +func NonWWWRedirect() echo.MiddlewareFunc { + return NonWWWRedirectWithConfig(DefaultRedirectConfig) +} + +// NonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config. +// See `NonWWWRedirect()`. +func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { + return redirect(config, func(scheme, host, uri string) (bool, string) { + if strings.HasPrefix(host, www) { + return true, scheme + "://" + host[4:] + uri + } + return false, "" + }) +} + +func redirect(config RedirectConfig, cb redirectLogic) echo.MiddlewareFunc { + if config.Skipper == nil { + config.Skipper = DefaultRedirectConfig.Skipper + } + if config.Code == 0 { + config.Code = DefaultRedirectConfig.Code + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req, scheme := c.Request(), c.Scheme() + host := req.Host + if ok, url := cb(scheme, host, req.RequestURI); ok { + return c.Redirect(config.Code, url) + } + + return next(c) + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/request_id.go b/vendor/github.com/labstack/echo/v4/middleware/request_id.go new file mode 100644 index 00000000..14bd4fd1 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/request_id.go @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "github.com/labstack/echo/v4" +) + +// RequestIDConfig defines the config for RequestID middleware. +type RequestIDConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Generator defines a function to generate an ID. + // Optional. Defaults to generator for random string of length 32. + Generator func() string + + // RequestIDHandler defines a function which is executed for a request id. + RequestIDHandler func(echo.Context, string) + + // TargetHeader defines what header to look for to populate the id + TargetHeader string +} + +// DefaultRequestIDConfig is the default RequestID middleware config. +var DefaultRequestIDConfig = RequestIDConfig{ + Skipper: DefaultSkipper, + Generator: generator, + TargetHeader: echo.HeaderXRequestID, +} + +// RequestID returns a X-Request-ID middleware. +func RequestID() echo.MiddlewareFunc { + return RequestIDWithConfig(DefaultRequestIDConfig) +} + +// RequestIDWithConfig returns a X-Request-ID middleware with config. +func RequestIDWithConfig(config RequestIDConfig) echo.MiddlewareFunc { + // Defaults + if config.Skipper == nil { + config.Skipper = DefaultRequestIDConfig.Skipper + } + if config.Generator == nil { + config.Generator = generator + } + if config.TargetHeader == "" { + config.TargetHeader = echo.HeaderXRequestID + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + res := c.Response() + rid := req.Header.Get(config.TargetHeader) + if rid == "" { + rid = config.Generator() + } + res.Header().Set(config.TargetHeader, rid) + if config.RequestIDHandler != nil { + config.RequestIDHandler(c, rid) + } + + return next(c) + } + } +} + +func generator() string { + return randomString(32) +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/request_logger.go b/vendor/github.com/labstack/echo/v4/middleware/request_logger.go new file mode 100644 index 00000000..7c18200b --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/request_logger.go @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "errors" + "net/http" + "time" + + "github.com/labstack/echo/v4" +) + +// Example for `slog` https://pkg.go.dev/log/slog +// logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) +// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ +// LogStatus: true, +// LogURI: true, +// LogError: true, +// HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code +// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { +// if v.Error == nil { +// logger.LogAttrs(context.Background(), slog.LevelInfo, "REQUEST", +// slog.String("uri", v.URI), +// slog.Int("status", v.Status), +// ) +// } else { +// logger.LogAttrs(context.Background(), slog.LevelError, "REQUEST_ERROR", +// slog.String("uri", v.URI), +// slog.Int("status", v.Status), +// slog.String("err", v.Error.Error()), +// ) +// } +// return nil +// }, +// })) +// +// Example for `fmt.Printf` +// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ +// LogStatus: true, +// LogURI: true, +// LogError: true, +// HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code +// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { +// if v.Error == nil { +// fmt.Printf("REQUEST: uri: %v, status: %v\n", v.URI, v.Status) +// } else { +// fmt.Printf("REQUEST_ERROR: uri: %v, status: %v, err: %v\n", v.URI, v.Status, v.Error) +// } +// return nil +// }, +// })) +// +// Example for Zerolog (https://github.com/rs/zerolog) +// logger := zerolog.New(os.Stdout) +// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ +// LogURI: true, +// LogStatus: true, +// LogError: true, +// HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code +// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { +// if v.Error == nil { +// logger.Info(). +// Str("URI", v.URI). +// Int("status", v.Status). +// Msg("request") +// } else { +// logger.Error(). +// Err(v.Error). +// Str("URI", v.URI). +// Int("status", v.Status). +// Msg("request error") +// } +// return nil +// }, +// })) +// +// Example for Zap (https://github.com/uber-go/zap) +// logger, _ := zap.NewProduction() +// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ +// LogURI: true, +// LogStatus: true, +// LogError: true, +// HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code +// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { +// if v.Error == nil { +// logger.Info("request", +// zap.String("URI", v.URI), +// zap.Int("status", v.Status), +// ) +// } else { +// logger.Error("request error", +// zap.String("URI", v.URI), +// zap.Int("status", v.Status), +// zap.Error(v.Error), +// ) +// } +// return nil +// }, +// })) +// +// Example for Logrus (https://github.com/sirupsen/logrus) +// log := logrus.New() +// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ +// LogURI: true, +// LogStatus: true, +// LogError: true, +// HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code +// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { +// if v.Error == nil { +// log.WithFields(logrus.Fields{ +// "URI": v.URI, +// "status": v.Status, +// }).Info("request") +// } else { +// log.WithFields(logrus.Fields{ +// "URI": v.URI, +// "status": v.Status, +// "error": v.Error, +// }).Error("request error") +// } +// return nil +// }, +// })) + +// RequestLoggerConfig is configuration for Request Logger middleware. +type RequestLoggerConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // BeforeNextFunc defines a function that is called before next middleware or handler is called in chain. + BeforeNextFunc func(c echo.Context) + // LogValuesFunc defines a function that is called with values extracted by logger from request/response. + // Mandatory. + LogValuesFunc func(c echo.Context, v RequestLoggerValues) error + + // HandleError instructs logger to call global error handler when next middleware/handler returns an error. + // This is useful when you have custom error handler that can decide to use different status codes. + // + // A side-effect of calling global error handler is that now Response has been committed and sent to the client + // and middlewares up in chain can not change Response status code or response body. + HandleError bool + + // LogLatency instructs logger to record duration it took to execute rest of the handler chain (next(c) call). + LogLatency bool + // LogProtocol instructs logger to extract request protocol (i.e. `HTTP/1.1` or `HTTP/2`) + LogProtocol bool + // LogRemoteIP instructs logger to extract request remote IP. See `echo.Context.RealIP()` for implementation details. + LogRemoteIP bool + // LogHost instructs logger to extract request host value (i.e. `example.com`) + LogHost bool + // LogMethod instructs logger to extract request method value (i.e. `GET` etc) + LogMethod bool + // LogURI instructs logger to extract request URI (i.e. `/list?lang=en&page=1`) + LogURI bool + // LogURIPath instructs logger to extract request URI path part (i.e. `/list`) + LogURIPath bool + // LogRoutePath instructs logger to extract route path part to which request was matched to (i.e. `/user/:id`) + LogRoutePath bool + // LogRequestID instructs logger to extract request ID from request `X-Request-ID` header or response if request did not have value. + LogRequestID bool + // LogReferer instructs logger to extract request referer values. + LogReferer bool + // LogUserAgent instructs logger to extract request user agent values. + LogUserAgent bool + // LogStatus instructs logger to extract response status code. If handler chain returns an echo.HTTPError, + // the status code is extracted from the echo.HTTPError returned + LogStatus bool + // LogError instructs logger to extract error returned from executed handler chain. + LogError bool + // LogContentLength instructs logger to extract content length header value. Note: this value could be different from + // actual request body size as it could be spoofed etc. + LogContentLength bool + // LogResponseSize instructs logger to extract response content length value. Note: when used with Gzip middleware + // this value may not be always correct. + LogResponseSize bool + // LogHeaders instructs logger to extract given list of headers from request. Note: request can contain more than + // one header with same value so slice of values is been logger for each given header. + // + // Note: header values are converted to canonical form with http.CanonicalHeaderKey as this how request parser converts header + // names to. For example, the canonical key for "accept-encoding" is "Accept-Encoding". + LogHeaders []string + // LogQueryParams instructs logger to extract given list of query parameters from request URI. Note: request can + // contain more than one query parameter with same name so slice of values is been logger for each given query param name. + LogQueryParams []string + // LogFormValues instructs logger to extract given list of form values from request body+URI. Note: request can + // contain more than one form value with same name so slice of values is been logger for each given form value name. + LogFormValues []string + + timeNow func() time.Time +} + +// RequestLoggerValues contains extracted values from logger. +type RequestLoggerValues struct { + // StartTime is time recorded before next middleware/handler is executed. + StartTime time.Time + // Latency is duration it took to execute rest of the handler chain (next(c) call). + Latency time.Duration + // Protocol is request protocol (i.e. `HTTP/1.1` or `HTTP/2`) + Protocol string + // RemoteIP is request remote IP. See `echo.Context.RealIP()` for implementation details. + RemoteIP string + // Host is request host value (i.e. `example.com`) + Host string + // Method is request method value (i.e. `GET` etc) + Method string + // URI is request URI (i.e. `/list?lang=en&page=1`) + URI string + // URIPath is request URI path part (i.e. `/list`) + URIPath string + // RoutePath is route path part to which request was matched to (i.e. `/user/:id`) + RoutePath string + // RequestID is request ID from request `X-Request-ID` header or response if request did not have value. + RequestID string + // Referer is request referer values. + Referer string + // UserAgent is request user agent values. + UserAgent string + // Status is response status code. Then handler returns an echo.HTTPError then code from there. + Status int + // Error is error returned from executed handler chain. + Error error + // ContentLength is content length header value. Note: this value could be different from actual request body size + // as it could be spoofed etc. + ContentLength string + // ResponseSize is response content length value. Note: when used with Gzip middleware this value may not be always correct. + ResponseSize int64 + // Headers are list of headers from request. Note: request can contain more than one header with same value so slice + // of values is been logger for each given header. + // Note: header values are converted to canonical form with http.CanonicalHeaderKey as this how request parser converts header + // names to. For example, the canonical key for "accept-encoding" is "Accept-Encoding". + Headers map[string][]string + // QueryParams are list of query parameters from request URI. Note: request can contain more than one query parameter + // with same name so slice of values is been logger for each given query param name. + QueryParams map[string][]string + // FormValues are list of form values from request body+URI. Note: request can contain more than one form value with + // same name so slice of values is been logger for each given form value name. + FormValues map[string][]string +} + +// RequestLoggerWithConfig returns a RequestLogger middleware with config. +func RequestLoggerWithConfig(config RequestLoggerConfig) echo.MiddlewareFunc { + mw, err := config.ToMiddleware() + if err != nil { + panic(err) + } + return mw +} + +// ToMiddleware converts RequestLoggerConfig into middleware or returns an error for invalid configuration. +func (config RequestLoggerConfig) ToMiddleware() (echo.MiddlewareFunc, error) { + if config.Skipper == nil { + config.Skipper = DefaultSkipper + } + now := time.Now + if config.timeNow != nil { + now = config.timeNow + } + + if config.LogValuesFunc == nil { + return nil, errors.New("missing LogValuesFunc callback function for request logger middleware") + } + + logHeaders := len(config.LogHeaders) > 0 + headers := append([]string(nil), config.LogHeaders...) + for i, v := range headers { + headers[i] = http.CanonicalHeaderKey(v) + } + + logQueryParams := len(config.LogQueryParams) > 0 + logFormValues := len(config.LogFormValues) > 0 + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + req := c.Request() + res := c.Response() + start := now() + + if config.BeforeNextFunc != nil { + config.BeforeNextFunc(c) + } + err := next(c) + if err != nil && config.HandleError { + c.Error(err) + } + + v := RequestLoggerValues{ + StartTime: start, + } + if config.LogLatency { + v.Latency = now().Sub(start) + } + if config.LogProtocol { + v.Protocol = req.Proto + } + if config.LogRemoteIP { + v.RemoteIP = c.RealIP() + } + if config.LogHost { + v.Host = req.Host + } + if config.LogMethod { + v.Method = req.Method + } + if config.LogURI { + v.URI = req.RequestURI + } + if config.LogURIPath { + p := req.URL.Path + if p == "" { + p = "/" + } + v.URIPath = p + } + if config.LogRoutePath { + v.RoutePath = c.Path() + } + if config.LogRequestID { + id := req.Header.Get(echo.HeaderXRequestID) + if id == "" { + id = res.Header().Get(echo.HeaderXRequestID) + } + v.RequestID = id + } + if config.LogReferer { + v.Referer = req.Referer() + } + if config.LogUserAgent { + v.UserAgent = req.UserAgent() + } + if config.LogStatus { + v.Status = res.Status + if err != nil && !config.HandleError { + // this block should not be executed in case of HandleError=true as the global error handler will decide + // the status code. In that case status code could be different from what err contains. + var httpErr *echo.HTTPError + if errors.As(err, &httpErr) { + v.Status = httpErr.Code + } + } + } + if config.LogError && err != nil { + v.Error = err + } + if config.LogContentLength { + v.ContentLength = req.Header.Get(echo.HeaderContentLength) + } + if config.LogResponseSize { + v.ResponseSize = res.Size + } + if logHeaders { + v.Headers = map[string][]string{} + for _, header := range headers { + if values, ok := req.Header[header]; ok { + v.Headers[header] = values + } + } + } + if logQueryParams { + queryParams := c.QueryParams() + v.QueryParams = map[string][]string{} + for _, param := range config.LogQueryParams { + if values, ok := queryParams[param]; ok { + v.QueryParams[param] = values + } + } + } + if logFormValues { + v.FormValues = map[string][]string{} + for _, formValue := range config.LogFormValues { + if values, ok := req.Form[formValue]; ok { + v.FormValues[formValue] = values + } + } + } + + if errOnLog := config.LogValuesFunc(c, v); errOnLog != nil { + return errOnLog + } + + // in case of HandleError=true we are returning the error that we already have handled with global error handler + // this is deliberate as this error could be useful for upstream middlewares and default global error handler + // will ignore that error when it bubbles up in middleware chain. + return err + } + }, nil +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/rewrite.go b/vendor/github.com/labstack/echo/v4/middleware/rewrite.go new file mode 100644 index 00000000..4c19cc1c --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/rewrite.go @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "regexp" + + "github.com/labstack/echo/v4" +) + +// RewriteConfig defines the config for Rewrite middleware. +type RewriteConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Rules defines the URL path rewrite rules. The values captured in asterisk can be + // retrieved by index e.g. $1, $2 and so on. + // Example: + // "/old": "/new", + // "/api/*": "/$1", + // "/js/*": "/public/javascripts/$1", + // "/users/*/orders/*": "/user/$1/order/$2", + // Required. + Rules map[string]string `yaml:"rules"` + + // RegexRules defines the URL path rewrite rules using regexp.Rexexp with captures + // Every capture group in the values can be retrieved by index e.g. $1, $2 and so on. + // Example: + // "^/old/[0.9]+/": "/new", + // "^/api/.+?/(.*)": "/v2/$1", + RegexRules map[*regexp.Regexp]string `yaml:"-"` +} + +// DefaultRewriteConfig is the default Rewrite middleware config. +var DefaultRewriteConfig = RewriteConfig{ + Skipper: DefaultSkipper, +} + +// Rewrite returns a Rewrite middleware. +// +// Rewrite middleware rewrites the URL path based on the provided rules. +func Rewrite(rules map[string]string) echo.MiddlewareFunc { + c := DefaultRewriteConfig + c.Rules = rules + return RewriteWithConfig(c) +} + +// RewriteWithConfig returns a Rewrite middleware with config. +// See: `Rewrite()`. +func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc { + // Defaults + if config.Rules == nil && config.RegexRules == nil { + panic("echo: rewrite middleware requires url path rewrite rules or regex rules") + } + + if config.Skipper == nil { + config.Skipper = DefaultBodyDumpConfig.Skipper + } + + if config.RegexRules == nil { + config.RegexRules = make(map[*regexp.Regexp]string) + } + for k, v := range rewriteRulesRegex(config.Rules) { + config.RegexRules[k] = v + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (err error) { + if config.Skipper(c) { + return next(c) + } + + if err := rewriteURL(config.RegexRules, c.Request()); err != nil { + return err + } + return next(c) + } + } +} diff --git a/vendor/github.com/labstack/echo/v4/middleware/secure.go b/vendor/github.com/labstack/echo/v4/middleware/secure.go new file mode 100644 index 00000000..c904abf1 --- /dev/null +++ b/vendor/github.com/labstack/echo/v4/middleware/secure.go @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors + +package middleware + +import ( + "fmt" + + "github.com/labstack/echo/v4" +) + +// SecureConfig defines the config for Secure middleware. +type SecureConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // XSSProtection provides protection against cross-site scripting attack (XSS) + // by setting the `X-XSS-Protection` header. + // Optional. Default value "1; mode=block". + XSSProtection string `yaml:"xss_protection"` + + // ContentTypeNosniff provides protection against overriding Content-Type + // header by setting the `X-Content-Type-Options` header. + // Optional. Default value "nosniff". + ContentTypeNosniff string `yaml:"content_type_nosniff"` + + // XFrameOptions can be used to indicate whether or not a browser should + // be allowed to render a page in a ,