From b950a067ef2eb2a1bb08ea1658bedeb1b600e722 Mon Sep 17 00:00:00 2001 From: Michael Valdron Date: Mon, 8 Apr 2024 10:39:57 -0400 Subject: [PATCH] Feature: Index Schema Field Filtering (#202) * new parameters and schemas added for field based filtering Signed-off-by: Michael Valdron * new types and endpoint signatures to support additional filter parameters Signed-off-by: Michael Valdron * setmap collection type and test cases defined Signed-off-by: Michael Valdron * multi field filtering changes Signed-off-by: Michael Valdron * setmap changes dynamic typing and union Signed-off-by: Michael Valdron * AndFilter and FilterResult Signed-off-by: Michael Valdron * filters use lazy evaluation Signed-off-by: Michael Valdron * StructToMap function Signed-off-by: Michael Valdron * replace setmap (using built-in map) with hashicorp hashset data structure Signed-off-by: Michael Valdron * revert schema changes Signed-off-by: Michael Valdron * use fields instead of reverted getter functions Signed-off-by: Michael Valdron * array fuzzy filter & filter options Signed-off-by: Michael Valdron * attribute names field test cases Signed-off-by: Michael Valdron * simplify type signatures under filter.go Signed-off-by: Michael Valdron * filter resources test cases Signed-off-by: Michael Valdron * fix filterDevfileArrayFuzzy to consider version schema results before filtering out index schema entry Signed-off-by: Michael Valdron * filter by starter projects test cases Signed-off-by: Michael Valdron * filter by link names test cases Signed-off-by: Michael Valdron * fix link getters to return the link label rather than the link itself Signed-off-by: Michael Valdron * filter by command groups test cases Signed-off-by: Michael Valdron * filter by git remote names test cases Signed-off-by: Michael Valdron * add missing string field filter test case definitions Signed-off-by: Michael Valdron * Parameter label sets and getters Signed-off-by: Michael Valdron * filter index parameters Signed-off-by: Michael Valdron * fix icon parameter conflict Signed-off-by: Michael Valdron * revert error returns for FilterDevfileSchemaVersion Signed-off-by: Michael Valdron * add iconUri field to IndexParams Signed-off-by: Michael Valdron * add filter result name field and return error if eval fails Signed-off-by: Michael Valdron * only perform field filtering with IndexParams tied to a field Signed-off-by: Michael Valdron * fix version regex in openapi spec Signed-off-by: Michael Valdron * '/v2index/all?arch=arm64&language=java' call integration test case Signed-off-by: Michael Valdron * REST doc update Signed-off-by: Michael Valdron * update generated source from doc changes Signed-off-by: Michael Valdron * remove minSchemaVersion and maxSchemaVersion from v1 param conversion Signed-off-by: Michael Valdron * fix k8s.io/utils as direct dependency Signed-off-by: Michael Valdron * method not allowed query params removed Signed-off-by: Michael Valdron * string pointer set check function Signed-off-by: Michael Valdron * minVersion & maxVersion filtering with range filtering & unit test fixups Signed-off-by: Michael Valdron * add missing schema version range filters to devfile endpoints Signed-off-by: Michael Valdron * remove fuzzy sets and punctuation removal Signed-off-by: Michael Valdron * /v2index?minVersion=1.1&maxVersion=1.1 integration test cases added Signed-off-by: Michael Valdron * /index?provider=Red%22Hat&resources=.zip integrartion test case added Signed-off-by: Michael Valdron * skip new test cases outside test registry Signed-off-by: Michael Valdron * /index/sample?description=Hello%22World integration test case added Signed-off-by: Michael Valdron * /index/all?description=node integration test case added Signed-off-by: Michael Valdron * /v2index?name=java&default=true integration test case added Signed-off-by: Michael Valdron * /v2index/sample?description=java&default=true integration test case added Signed-off-by: Michael Valdron * /v2index/all?description=java&default=true integration test case added Signed-off-by: Michael Valdron * remove lazy eval from FilterResult Signed-off-by: Michael Valdron * remove unused MakeMockFilterResult Signed-off-by: Michael Valdron * AndFilter params by ref instead of by value Signed-off-by: Michael Valdron * add deprecated query param Signed-off-by: Michael Valdron * add FilterDevfileDeprecated filter function and test cases Signed-off-by: Michael Valdron * update test stacks to test against deprecated parameter Signed-off-by: Michael Valdron * update build index to filter based on deprecated stacks if deprecated parameter is set Signed-off-by: Michael Valdron * integration test cases for deprecated filter parameter Signed-off-by: Michael Valdron --------- Signed-off-by: Michael Valdron --- index/server/go.mod | 7 +- index/server/go.sum | 3 + index/server/openapi.yaml | 502 ++- index/server/pkg/server/endpoint.gen.go | 1196 ++++-- index/server/pkg/server/endpoint.go | 134 +- index/server/pkg/server/endpoint_test.go | 271 +- index/server/pkg/server/filter.go | 61 + index/server/pkg/server/filter_test.go | 859 +++++ index/server/pkg/server/types.gen.go | 644 +++- index/server/pkg/server/types.go | 138 + index/server/pkg/util/filter.go | 760 +++- index/server/pkg/util/filter_test.go | 3315 ++++++++++++++++- index/server/pkg/util/util.go | 50 + index/server/pkg/util/util_test.go | 102 + index/server/registry-REST-API.adoc | 920 ++++- .../github.com/hashicorp/go-set/.gitignore | 23 + .../hashicorp/go-set/.golangci.yaml | 17 + .../github.com/hashicorp/go-set/LICENSE | 375 ++ .../github.com/hashicorp/go-set/README.md | 228 ++ .../github.com/hashicorp/go-set/common.go | 65 + .../github.com/hashicorp/go-set/hashset.go | 342 ++ .../hashicorp/go-set/serialization.go | 22 + .../vendor/github.com/hashicorp/go-set/set.go | 350 ++ .../github.com/hashicorp/go-set/stack.go | 36 + .../github.com/hashicorp/go-set/treeset.go | 1026 +++++ index/server/vendor/modules.txt | 3 + .../pkg/tests/indexserver_tests.go | 350 ++ tests/registry/stacks/go/1.0.2/devfile.yaml | 1 + tests/registry/stacks/go/1.2.0/devfile.yaml | 1 + 29 files changed, 11140 insertions(+), 661 deletions(-) create mode 100644 index/server/pkg/server/filter.go create mode 100644 index/server/pkg/server/filter_test.go create mode 100644 index/server/pkg/server/types.go create mode 100644 index/server/vendor/github.com/hashicorp/go-set/.gitignore create mode 100644 index/server/vendor/github.com/hashicorp/go-set/.golangci.yaml create mode 100644 index/server/vendor/github.com/hashicorp/go-set/LICENSE create mode 100644 index/server/vendor/github.com/hashicorp/go-set/README.md create mode 100644 index/server/vendor/github.com/hashicorp/go-set/common.go create mode 100644 index/server/vendor/github.com/hashicorp/go-set/hashset.go create mode 100644 index/server/vendor/github.com/hashicorp/go-set/serialization.go create mode 100644 index/server/vendor/github.com/hashicorp/go-set/set.go create mode 100644 index/server/vendor/github.com/hashicorp/go-set/stack.go create mode 100644 index/server/vendor/github.com/hashicorp/go-set/treeset.go diff --git a/index/server/go.mod b/index/server/go.mod index 2563d2551..637ced923 100644 --- a/index/server/go.mod +++ b/index/server/go.mod @@ -9,14 +9,18 @@ require ( github.com/devfile/registry-support/index/generator v0.0.0 github.com/getkin/kin-openapi v0.117.0 github.com/gin-gonic/gin v1.9.0 + github.com/hashicorp/go-set v0.1.13 github.com/hashicorp/go-version v1.4.0 github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/prometheus/client_golang v1.14.0 golang.org/x/text v0.14.0 gopkg.in/segmentio/analytics-go.v3 v3.1.0 + k8s.io/apiextensions-apiserver v0.26.10 k8s.io/apimachinery v0.26.10 + k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 oras.land/oras-go v1.2.5 ) @@ -96,7 +100,6 @@ require ( github.com/moby/locker v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/openshift/api v0.0.0-20200930075302-db52bc4ef99f // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect @@ -143,12 +146,10 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.26.10 // indirect - k8s.io/apiextensions-apiserver v0.26.10 // indirect k8s.io/client-go v0.26.10 // indirect k8s.io/klog v1.0.0 // indirect k8s.io/klog/v2 v2.90.1 // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect - k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect sigs.k8s.io/controller-runtime v0.14.7 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect diff --git a/index/server/go.sum b/index/server/go.sum index bed04108b..30410717e 100644 --- a/index/server/go.sum +++ b/index/server/go.sum @@ -306,6 +306,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/hashicorp/go-set v0.1.13 h1:k1B5goY3c7OKEzpK+gwAhJexxzAJwDN8kId8YvWrihA= +github.com/hashicorp/go-set v0.1.13/go.mod h1:0/D+R4MFUzJ6XmvjU7liXtznF1eQDxh84GJlhXw+lvo= github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -463,6 +465,7 @@ github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3 h1:ZuhckGJ10ula github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= diff --git a/index/server/openapi.yaml b/index/server/openapi.yaml index 37aa70f7d..b0f9a8f8e 100644 --- a/index/server/openapi.yaml +++ b/index/server/openapi.yaml @@ -98,8 +98,29 @@ paths: description: The request body must be empty. content: {} parameters: + - $ref: '#/components/parameters/nameParam' + - $ref: '#/components/parameters/displayNameParam' + - $ref: '#/components/parameters/descriptionParam' + - $ref: '#/components/parameters/attributeNamesParam' + - $ref: '#/components/parameters/tagsParam' - $ref: '#/components/parameters/archParam' - $ref: '#/components/parameters/iconParam' + - $ref: '#/components/parameters/iconUriParam' + - $ref: '#/components/parameters/projectTypeParam' + - $ref: '#/components/parameters/languageParam' + - $ref: '#/components/parameters/deprecatedParam' + - $ref: '#/components/parameters/resourcesParam' + - $ref: '#/components/parameters/starterProjectsParam' + - $ref: '#/components/parameters/linkNamesParam' + - $ref: '#/components/parameters/linksParam' + - $ref: '#/components/parameters/gitRemoteNamesParam' + - $ref: '#/components/parameters/gitRemotesParam' + - $ref: '#/components/parameters/gitUrlParam' + - $ref: '#/components/parameters/gitRemoteNameParam' + - $ref: '#/components/parameters/gitSubDirParam' + - $ref: '#/components/parameters/gitRevisionParam' + - $ref: '#/components/parameters/providerParam' + - $ref: '#/components/parameters/supportUrlParam' responses: 200: $ref: '#/components/responses/indexResponse' @@ -142,8 +163,29 @@ paths: type: string x-go-name: IndexType x-go-name: IndexType + - $ref: '#/components/parameters/nameParam' + - $ref: '#/components/parameters/displayNameParam' + - $ref: '#/components/parameters/descriptionParam' + - $ref: '#/components/parameters/attributeNamesParam' + - $ref: '#/components/parameters/tagsParam' - $ref: '#/components/parameters/archParam' - $ref: '#/components/parameters/iconParam' + - $ref: '#/components/parameters/iconUriParam' + - $ref: '#/components/parameters/projectTypeParam' + - $ref: '#/components/parameters/languageParam' + - $ref: '#/components/parameters/deprecatedParam' + - $ref: '#/components/parameters/resourcesParam' + - $ref: '#/components/parameters/starterProjectsParam' + - $ref: '#/components/parameters/linkNamesParam' + - $ref: '#/components/parameters/linksParam' + - $ref: '#/components/parameters/gitRemoteNamesParam' + - $ref: '#/components/parameters/gitRemotesParam' + - $ref: '#/components/parameters/gitUrlParam' + - $ref: '#/components/parameters/gitRemoteNameParam' + - $ref: '#/components/parameters/gitSubDirParam' + - $ref: '#/components/parameters/gitRevisionParam' + - $ref: '#/components/parameters/providerParam' + - $ref: '#/components/parameters/supportUrlParam' responses: 200: $ref: '#/components/responses/indexResponse' @@ -161,8 +203,6 @@ paths: type: string x-go-name: IndexType x-go-name: IndexType - - $ref: '#/components/parameters/archParam' - - $ref: '#/components/parameters/iconParam' responses: 405: $ref: '#/components/responses/methodNotAllowedResponse' @@ -177,8 +217,6 @@ paths: type: string x-go-name: IndexType x-go-name: IndexType - - $ref: '#/components/parameters/archParam' - - $ref: '#/components/parameters/iconParam' responses: 405: $ref: '#/components/responses/methodNotAllowedResponse' @@ -193,8 +231,6 @@ paths: type: string x-go-name: IndexType x-go-name: IndexType - - $ref: '#/components/parameters/archParam' - - $ref: '#/components/parameters/iconParam' responses: 405: $ref: '#/components/responses/methodNotAllowedResponse' @@ -211,8 +247,35 @@ paths: description: The request body must be empty. content: {} parameters: + - $ref: '#/components/parameters/nameParam' + - $ref: '#/components/parameters/displayNameParam' + - $ref: '#/components/parameters/descriptionParam' + - $ref: '#/components/parameters/attributeNamesParam' + - $ref: '#/components/parameters/tagsParam' - $ref: '#/components/parameters/archParam' - $ref: '#/components/parameters/iconParam' + - $ref: '#/components/parameters/iconUriParam' + - $ref: '#/components/parameters/projectTypeParam' + - $ref: '#/components/parameters/languageParam' + - $ref: '#/components/parameters/minVersionParam' + - $ref: '#/components/parameters/maxVersionParam' + - $ref: '#/components/parameters/minSchemaVersionParam' + - $ref: '#/components/parameters/maxSchemaVersionParam' + - $ref: '#/components/parameters/deprecatedParam' + - $ref: '#/components/parameters/defaultParam' + - $ref: '#/components/parameters/resourcesParam' + - $ref: '#/components/parameters/starterProjectsParam' + - $ref: '#/components/parameters/linkNamesParam' + - $ref: '#/components/parameters/linksParam' + - $ref: '#/components/parameters/commandGroupsParam' + - $ref: '#/components/parameters/gitRemoteNamesParam' + - $ref: '#/components/parameters/gitRemotesParam' + - $ref: '#/components/parameters/gitUrlParam' + - $ref: '#/components/parameters/gitRemoteNameParam' + - $ref: '#/components/parameters/gitSubDirParam' + - $ref: '#/components/parameters/gitRevisionParam' + - $ref: '#/components/parameters/providerParam' + - $ref: '#/components/parameters/supportUrlParam' responses: 200: $ref: '#/components/responses/v2IndexResponse' @@ -255,8 +318,35 @@ paths: type: string x-go-name: IndexType x-go-name: IndexType + - $ref: '#/components/parameters/nameParam' + - $ref: '#/components/parameters/displayNameParam' + - $ref: '#/components/parameters/descriptionParam' + - $ref: '#/components/parameters/attributeNamesParam' + - $ref: '#/components/parameters/tagsParam' - $ref: '#/components/parameters/archParam' - - $ref: '#/components/parameters/iconParam' + - $ref: '#/components/parameters/iconParam' + - $ref: '#/components/parameters/iconUriParam' + - $ref: '#/components/parameters/projectTypeParam' + - $ref: '#/components/parameters/languageParam' + - $ref: '#/components/parameters/minVersionParam' + - $ref: '#/components/parameters/maxVersionParam' + - $ref: '#/components/parameters/minSchemaVersionParam' + - $ref: '#/components/parameters/maxSchemaVersionParam' + - $ref: '#/components/parameters/deprecatedParam' + - $ref: '#/components/parameters/defaultParam' + - $ref: '#/components/parameters/resourcesParam' + - $ref: '#/components/parameters/starterProjectsParam' + - $ref: '#/components/parameters/linkNamesParam' + - $ref: '#/components/parameters/linksParam' + - $ref: '#/components/parameters/commandGroupsParam' + - $ref: '#/components/parameters/gitRemoteNamesParam' + - $ref: '#/components/parameters/gitRemotesParam' + - $ref: '#/components/parameters/gitUrlParam' + - $ref: '#/components/parameters/gitRemoteNameParam' + - $ref: '#/components/parameters/gitSubDirParam' + - $ref: '#/components/parameters/gitRevisionParam' + - $ref: '#/components/parameters/providerParam' + - $ref: '#/components/parameters/supportUrlParam' responses: 200: $ref: '#/components/responses/v2IndexResponse' @@ -274,8 +364,6 @@ paths: type: string x-go-name: IndexType x-go-name: IndexType - - $ref: '#/components/parameters/archParam' - - $ref: '#/components/parameters/iconParam' responses: 405: $ref: '#/components/responses/methodNotAllowedResponse' @@ -290,8 +378,6 @@ paths: type: string x-go-name: IndexType x-go-name: IndexType - - $ref: '#/components/parameters/archParam' - - $ref: '#/components/parameters/iconParam' responses: 405: $ref: '#/components/responses/methodNotAllowedResponse' @@ -306,8 +392,6 @@ paths: type: string x-go-name: IndexType x-go-name: IndexType - - $ref: '#/components/parameters/archParam' - - $ref: '#/components/parameters/iconParam' responses: 405: $ref: '#/components/responses/methodNotAllowedResponse' @@ -327,6 +411,8 @@ paths: type: string x-go-name: Stack x-go-name: Stack + - $ref: '#/components/parameters/minSchemaVersionParam' + - $ref: '#/components/parameters/maxSchemaVersionParam' requestBody: description: The request body must be empty. content: {} @@ -403,6 +489,8 @@ paths: type: string x-go-name: Version x-go-name: Version + - $ref: '#/components/parameters/minSchemaVersionParam' + - $ref: '#/components/parameters/maxSchemaVersionParam' requestBody: description: The request body must be empty. content: {} @@ -503,6 +591,8 @@ paths: type: string x-go-name: StarterProject x-go-name: StarterProject + - $ref: '#/components/parameters/minSchemaVersionParam' + - $ref: '#/components/parameters/maxSchemaVersionParam' responses: '200': $ref: '#/components/responses/starterProjectResponse' @@ -610,6 +700,8 @@ paths: type: string x-go-name: StarterProject x-go-name: StarterProject + - $ref: '#/components/parameters/minSchemaVersionParam' + - $ref: '#/components/parameters/maxSchemaVersionParam' responses: '200': $ref: '#/components/responses/starterProjectResponse' @@ -726,12 +818,92 @@ components: description: IndexParams defines parameters for index endpoints. type: object properties: + name: + $ref: '#/components/schemas/Name' + displayName: + $ref: '#/components/schemas/DisplayName' + description: + $ref: '#/components/schemas/Description' + attributeNames: + $ref: '#/components/schemas/AttributeNames' + tags: + $ref: '#/components/schemas/Tags' icon: $ref: '#/components/schemas/Icon' + iconUri: + $ref: '#/components/schemas/IconUri' arch: $ref: '#/components/schemas/Architectures' + projectType: + $ref: '#/components/schemas/ProjectType' + language: + $ref: '#/components/schemas/Language' + minVersion: + $ref: '#/components/schemas/Version' + maxVersion: + $ref: '#/components/schemas/Version' + minSchemaVersion: + $ref: '#/components/schemas/SchemaVersion' + maxSchemaVersion: + $ref: '#/components/schemas/SchemaVersion' + deprecated: + $ref: '#/components/schemas/Deprecated' + default: + $ref: '#/components/schemas/Default' + resources: + $ref: '#/components/schemas/Resources' + starterProjects: + $ref: '#/components/schemas/StarterProjects' + linkNames: + $ref: '#/components/schemas/LinkNames' + links: + $ref: '#/components/schemas/Links' + commandGroups: + $ref: '#/components/schemas/CommandGroups' + gitRemoteNames: + $ref: '#/components/schemas/GitRemoteNames' + gitRemotes: + $ref: '#/components/schemas/GitRemotes' + gitUrl: + $ref: '#/components/schemas/Url' + gitRemoteName: + $ref: '#/components/schemas/GitRemoteName' + gitSubDir: + $ref: '#/components/schemas/GitSubDir' + gitRevision: + $ref: '#/components/schemas/GitRevision' + provider: + $ref: '#/components/schemas/Provider' + supportUrl: + $ref: '#/components/schemas/Url' + Name: + description: Name of devfile registry entry + type: string + DisplayName: + description: User readable name of devfile registry entry + type: string + Description: + description: Description of devfile registry entry + type: string + AttributeNames: + description: List of the YAML free-form attribute names + type: array + uniqueItems: true + items: + description: YAML free-form attribute name + type: string + Tags: + description: List of devfile subject tags + type: array + uniqueItems: false + items: + description: A subject tag to describe the devfile workspace + type: string Icon: - description: Optional devfile icon, can be a URI or a relative path in the project + description: Optional devfile icon encoding type + type: string + IconUri: + description: Optional devfile icon uri, can be a URL or a relative path in the project type: string Architectures: description: Optional list of processor architectures that the devfile supports, empty list suggests that the devfile can be used on any architecture @@ -745,21 +917,319 @@ components: - "arm64" - "ppc64le" - "s390x" + ProjectType: + description: Type of project the devfile supports + type: string + Language: + description: Programming language of the devfile workspace + type: string + Version: + description: Devfile registry entry version number + type: string + pattern: '^[0-9]+\.[0-9]+(\.[0-9]+)?$' + example: 1.4.0 + SchemaVersion: + description: Devfile schema version number + type: string + pattern: '^[0-9]+\.[0-9]+(\.[0-9]+(\-alpha)?)?$' + example: 2.2.0, 2.2.3-alpha + Deprecated: + description: Flag for deprecated devfile registry entry + type: boolean + Default: + description: Flag for default devfile registry entry version + type: boolean + Resources: + description: List of file resources for the devfile + type: array + uniqueItems: false + items: + type: string + StarterProjects: + description: List of starter project names + type: array + uniqueItems: true + items: + description: Name of starter project resources + type: string + Url: + description: Url field type + type: string + LinkNames: + description: Names of devfile links + type: array + uniqueItems: true + items: + type: string + Links: + description: List of devfile links + type: array + uniqueItems: false + items: + $ref: '#/components/schemas/Url' + CommandGroups: + description: List of command groups defined in devfile + type: array + uniqueItems: true + items: + type: string + enum: + - build + - run + - test + - debug + - deploy + GitRemoteName: + description: Git repository remote name + type: string + GitRemoteNames: + description: List of git repository remote names + type: array + uniqueItems: true + items: + $ref: '#/components/schemas/GitRemoteName' + GitRemotes: + description: List of git repository remote urls + type: array + uniqueItems: false + items: + $ref: '#/components/schemas/Url' + GitSubDir: + description: Subdirectory of git repository to use as reference + type: string + GitRevision: + description: Branch, tag, or commit reference + type: string + Provider: + description: Name of provider of the devfile registry entry + type: string parameters: + nameParam: + name: name + in: query + required: false + description: Search string to filter stacks by their name + schema: + $ref: '#/components/schemas/Name' + displayNameParam: + name: displayName + in: query + required: false + description: Search string to filter stacks by their display names + schema: + $ref: '#/components/schemas/DisplayName' + descriptionParam: + name: description + in: query + required: false + description: Search string to filter stacks by the description text + schema: + $ref: '#/components/schemas/Description' + attributeNamesParam: + name: attributeNames + in: query + required: false + description: |- + Collection of search strings to filter stacks by the names of + defined free-form attributes + schema: + type: array + items: + type: string + tagsParam: + name: tags + in: query + required: false + description: Collection of search strings to filter stacks by their tags + schema: + type: array + items: + type: string iconParam: name: icon in: query - description: The icon type filter + description: Toggle on encoding content passed required: false schema: $ref: '#/components/schemas/Icon' + iconUriParam: + name: iconUri + in: query + description: Search string to filter stacks by their icon uri + required: false + schema: + $ref: '#/components/schemas/IconUri' archParam: name: arch in: query - description: The target architecture filter + description: Collection of search strings to filter stacks by their architectures required: false schema: $ref: '#/components/schemas/Architectures' + projectTypeParam: + name: projectType + in: query + required: false + description: Search string to filter stacks by their project type + schema: + $ref: '#/components/schemas/ProjectType' + languageParam: + name: language + in: query + required: false + description: Search string to filter stacks by their programming language + schema: + $ref: '#/components/schemas/Language' + minVersionParam: + name: minVersion + in: query + required: false + description: The minimum stack version + schema: + $ref: '#/components/schemas/Version' + maxVersionParam: + name: maxVersion + in: query + required: false + description: The maximum stack version + schema: + $ref: '#/components/schemas/Version' + minSchemaVersionParam: + name: minSchemaVersion + in: query + required: false + description: The minimum devfile schema version + schema: + $ref: '#/components/schemas/SchemaVersion' + maxSchemaVersionParam: + name: maxSchemaVersion + in: query + required: false + description: The maximum devfile schema version + schema: + $ref: '#/components/schemas/SchemaVersion' + deprecatedParam: + name: deprecated + in: query + required: false + description: Boolean to filter stacks if they are deprecated or not + schema: + $ref: '#/components/schemas/Deprecated' + defaultParam: + name: default + in: query + required: false + description: Boolean to filter stacks if they are default or not + schema: + $ref: '#/components/schemas/Default' + resourcesParam: + name: resources + in: query + required: false + description: |- + Collection of search strings to filter stacks by their + resource files + schema: + $ref: '#/components/schemas/Resources' + starterProjectsParam: + name: starterProjects + in: query + required: false + description: |- + Collection of search strings to filter stacks by the names + of the starter projects + schema: + $ref: '#/components/schemas/StarterProjects' + linkNamesParam: + name: linkNames + in: query + required: false + description: |- + Collection of search strings to filter stacks by the names + of the link sources + schema: + $ref: '#/components/schemas/LinkNames' + linksParam: + name: links + in: query + required: false + description: |- + Collection of search strings to filter stacks by their link + sources + schema: + $ref: '#/components/schemas/Links' + commandGroupsParam: + name: commandGroups + in: query + required: false + description: |- + Collection of search strings to filter stacks by their present command + groups + schema: + $ref: '#/components/schemas/CommandGroups' + gitRemoteNamesParam: + name: gitRemoteNames + in: query + required: false + description: |- + Collection of search strings to filter stacks by the names of + the git remotes + schema: + $ref: '#/components/schemas/GitRemoteNames' + gitRemotesParam: + name: gitRemotes + in: query + required: false + description: |- + Collection of search strings to filter stacks by the URIs of + the git remotes + schema: + $ref: '#/components/schemas/GitRemotes' + gitUrlParam: + name: gitUrl + in: query + required: false + description: Search string to filter stacks by their git urls + schema: + $ref: '#/components/schemas/Url' + gitRemoteNameParam: + name: gitRemoteName + in: query + required: false + description: |- + Search string to filter stacks by their git remote name + schema: + $ref: '#/components/schemas/GitRemoteName' + gitSubDirParam: + name: gitSubDir + in: query + required: false + description: |- + Search string to filter stacks by their target subdirectory + of the git repository + schema: + $ref: '#/components/schemas/GitSubDir' + gitRevisionParam: + name: gitRevision + in: query + required: false + description: Search string to filter stacks by their git revision + schema: + $ref: '#/components/schemas/GitRevision' + providerParam: + name: provider + in: query + required: false + description: Search string to filter stacks by the stack provider + schema: + $ref: '#/components/schemas/Provider' + supportUrlParam: + name: supportUrl + in: query + required: false + description: Search string to filter stacks by their given support url + schema: + $ref: '#/components/schemas/Url' responses: devfileErrorResponse: description: Failed to get the devfile. diff --git a/index/server/pkg/server/endpoint.gen.go b/index/server/pkg/server/endpoint.gen.go index 8f0e472a8..66a9c4451 100644 --- a/index/server/pkg/server/endpoint.gen.go +++ b/index/server/pkg/server/endpoint.gen.go @@ -47,7 +47,7 @@ type ServerInterface interface { DeleteDevfile(c *gin.Context, stack string) // Get devfile by stack name. // (GET /devfiles/{stack}) - ServeDevfile(c *gin.Context, stack string) + ServeDevfile(c *gin.Context, stack string, params ServeDevfileParams) // (POST /devfiles/{stack}) PostDevfile(c *gin.Context, stack string) @@ -59,7 +59,7 @@ type ServerInterface interface { DeleteDevfileStarterProject(c *gin.Context, stack string, starterProject string) // Fetches starter project by stack and project name // (GET /devfiles/{stack}/starter-projects/{starterProject}) - ServeDevfileStarterProject(c *gin.Context, stack string, starterProject string) + ServeDevfileStarterProject(c *gin.Context, stack string, starterProject string, params ServeDevfileStarterProjectParams) // (POST /devfiles/{stack}/starter-projects/{starterProject}) PostDevfileStarterProject(c *gin.Context, stack string, starterProject string) @@ -71,7 +71,7 @@ type ServerInterface interface { DeleteDevfileWithVersion(c *gin.Context, stack string, version string) // Get devfile by stack name. // (GET /devfiles/{stack}/{version}) - ServeDevfileWithVersion(c *gin.Context, stack string, version string) + ServeDevfileWithVersion(c *gin.Context, stack string, version string, params ServeDevfileWithVersionParams) // (POST /devfiles/{stack}/{version}) PostDevfileWithVersion(c *gin.Context, stack string, version string) @@ -83,7 +83,7 @@ type ServerInterface interface { DeleteDevfileStarterProjectWithVersion(c *gin.Context, stack string, version string, starterProject string) // Fetches starter project by stack name, stack version, and project name // (GET /devfiles/{stack}/{version}/starter-projects/{starterProject}) - ServeDevfileStarterProjectWithVersion(c *gin.Context, stack string, version string, starterProject string) + ServeDevfileStarterProjectWithVersion(c *gin.Context, stack string, version string, starterProject string, params ServeDevfileStarterProjectWithVersionParams) // (POST /devfiles/{stack}/{version}/starter-projects/{starterProject}) PostDevfileStarterProjectWithVersion(c *gin.Context, stack string, version string, starterProject string) @@ -116,16 +116,16 @@ type ServerInterface interface { PutDevfileIndexV1(c *gin.Context) // (DELETE /index/{indexType}) - DeleteDevfileIndexV1WithType(c *gin.Context, indexType string, params DeleteDevfileIndexV1WithTypeParams) + DeleteDevfileIndexV1WithType(c *gin.Context, indexType string) // Gets index schemas of the devfiles of specific type. // (GET /index/{indexType}) ServeDevfileIndexV1WithType(c *gin.Context, indexType string, params ServeDevfileIndexV1WithTypeParams) // (POST /index/{indexType}) - PostDevfileIndexV1WithType(c *gin.Context, indexType string, params PostDevfileIndexV1WithTypeParams) + PostDevfileIndexV1WithType(c *gin.Context, indexType string) // (PUT /index/{indexType}) - PutDevfileIndexV1WithType(c *gin.Context, indexType string, params PutDevfileIndexV1WithTypeParams) + PutDevfileIndexV1WithType(c *gin.Context, indexType string) // (DELETE /v2index) DeleteDevfileIndexV2(c *gin.Context) @@ -140,16 +140,16 @@ type ServerInterface interface { PutDevfileIndexV2(c *gin.Context) // (DELETE /v2index/{indexType}) - DeleteDevfileIndexV2WithType(c *gin.Context, indexType string, params DeleteDevfileIndexV2WithTypeParams) + DeleteDevfileIndexV2WithType(c *gin.Context, indexType string) // Gets V2 index schemas of the devfiles of specific type. // (GET /v2index/{indexType}) ServeDevfileIndexV2WithType(c *gin.Context, indexType string, params ServeDevfileIndexV2WithTypeParams) // (POST /v2index/{indexType}) - PostDevfileIndexV2WithType(c *gin.Context, indexType string, params PostDevfileIndexV2WithTypeParams) + PostDevfileIndexV2WithType(c *gin.Context, indexType string) // (PUT /v2index/{indexType}) - PutDevfileIndexV2WithType(c *gin.Context, indexType string, params PutDevfileIndexV2WithTypeParams) + PutDevfileIndexV2WithType(c *gin.Context, indexType string) } // ServerInterfaceWrapper converts contexts to parameters. @@ -236,11 +236,30 @@ func (siw *ServerInterfaceWrapper) ServeDevfile(c *gin.Context) { return } + // Parameter object where we will unmarshal all parameters from the context + var params ServeDevfileParams + + // ------------- Optional query parameter "minSchemaVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "minSchemaVersion", c.Request.URL.Query(), ¶ms.MinSchemaVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter minSchemaVersion: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "maxSchemaVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "maxSchemaVersion", c.Request.URL.Query(), ¶ms.MaxSchemaVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter maxSchemaVersion: %s", err), http.StatusBadRequest) + return + } + for _, middleware := range siw.HandlerMiddlewares { middleware(c) } - siw.Handler.ServeDevfile(c, stack) + siw.Handler.ServeDevfile(c, stack, params) } // PostDevfile operation middleware @@ -338,11 +357,30 @@ func (siw *ServerInterfaceWrapper) ServeDevfileStarterProject(c *gin.Context) { return } + // Parameter object where we will unmarshal all parameters from the context + var params ServeDevfileStarterProjectParams + + // ------------- Optional query parameter "minSchemaVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "minSchemaVersion", c.Request.URL.Query(), ¶ms.MinSchemaVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter minSchemaVersion: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "maxSchemaVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "maxSchemaVersion", c.Request.URL.Query(), ¶ms.MaxSchemaVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter maxSchemaVersion: %s", err), http.StatusBadRequest) + return + } + for _, middleware := range siw.HandlerMiddlewares { middleware(c) } - siw.Handler.ServeDevfileStarterProject(c, stack, starterProject) + siw.Handler.ServeDevfileStarterProject(c, stack, starterProject, params) } // PostDevfileStarterProject operation middleware @@ -458,11 +496,30 @@ func (siw *ServerInterfaceWrapper) ServeDevfileWithVersion(c *gin.Context) { return } + // Parameter object where we will unmarshal all parameters from the context + var params ServeDevfileWithVersionParams + + // ------------- Optional query parameter "minSchemaVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "minSchemaVersion", c.Request.URL.Query(), ¶ms.MinSchemaVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter minSchemaVersion: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "maxSchemaVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "maxSchemaVersion", c.Request.URL.Query(), ¶ms.MaxSchemaVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter maxSchemaVersion: %s", err), http.StatusBadRequest) + return + } + for _, middleware := range siw.HandlerMiddlewares { middleware(c) } - siw.Handler.ServeDevfileWithVersion(c, stack, version) + siw.Handler.ServeDevfileWithVersion(c, stack, version, params) } // PostDevfileWithVersion operation middleware @@ -596,11 +653,30 @@ func (siw *ServerInterfaceWrapper) ServeDevfileStarterProjectWithVersion(c *gin. return } + // Parameter object where we will unmarshal all parameters from the context + var params ServeDevfileStarterProjectWithVersionParams + + // ------------- Optional query parameter "minSchemaVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "minSchemaVersion", c.Request.URL.Query(), ¶ms.MinSchemaVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter minSchemaVersion: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "maxSchemaVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "maxSchemaVersion", c.Request.URL.Query(), ¶ms.MaxSchemaVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter maxSchemaVersion: %s", err), http.StatusBadRequest) + return + } + for _, middleware := range siw.HandlerMiddlewares { middleware(c) } - siw.Handler.ServeDevfileStarterProjectWithVersion(c, stack, version, starterProject) + siw.Handler.ServeDevfileStarterProjectWithVersion(c, stack, version, starterProject, params) } // PostDevfileStarterProjectWithVersion operation middleware @@ -739,66 +815,46 @@ func (siw *ServerInterfaceWrapper) ServeDevfileIndexV1(c *gin.Context) { // Parameter object where we will unmarshal all parameters from the context var params ServeDevfileIndexV1Params - // ------------- Optional query parameter "arch" ------------- + // ------------- Optional query parameter "name" ------------- - err = runtime.BindQueryParameter("form", true, false, "arch", c.Request.URL.Query(), ¶ms.Arch) + err = runtime.BindQueryParameter("form", true, false, "name", c.Request.URL.Query(), ¶ms.Name) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter arch: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter name: %s", err), http.StatusBadRequest) return } - // ------------- Optional query parameter "icon" ------------- + // ------------- Optional query parameter "displayName" ------------- - err = runtime.BindQueryParameter("form", true, false, "icon", c.Request.URL.Query(), ¶ms.Icon) + err = runtime.BindQueryParameter("form", true, false, "displayName", c.Request.URL.Query(), ¶ms.DisplayName) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter icon: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter displayName: %s", err), http.StatusBadRequest) return } - for _, middleware := range siw.HandlerMiddlewares { - middleware(c) - } - - siw.Handler.ServeDevfileIndexV1(c, params) -} - -// PostDevfileIndexV1 operation middleware -func (siw *ServerInterfaceWrapper) PostDevfileIndexV1(c *gin.Context) { + // ------------- Optional query parameter "description" ------------- - for _, middleware := range siw.HandlerMiddlewares { - middleware(c) + err = runtime.BindQueryParameter("form", true, false, "description", c.Request.URL.Query(), ¶ms.Description) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter description: %s", err), http.StatusBadRequest) + return } - siw.Handler.PostDevfileIndexV1(c) -} - -// PutDevfileIndexV1 operation middleware -func (siw *ServerInterfaceWrapper) PutDevfileIndexV1(c *gin.Context) { + // ------------- Optional query parameter "attributeNames" ------------- - for _, middleware := range siw.HandlerMiddlewares { - middleware(c) + err = runtime.BindQueryParameter("form", true, false, "attributeNames", c.Request.URL.Query(), ¶ms.AttributeNames) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter attributeNames: %s", err), http.StatusBadRequest) + return } - siw.Handler.PutDevfileIndexV1(c) -} - -// DeleteDevfileIndexV1WithType operation middleware -func (siw *ServerInterfaceWrapper) DeleteDevfileIndexV1WithType(c *gin.Context) { - - var err error - - // ------------- Path parameter "indexType" ------------- - var indexType string + // ------------- Optional query parameter "tags" ------------- - err = runtime.BindStyledParameter("simple", false, "indexType", c.Param("indexType"), &indexType) + err = runtime.BindQueryParameter("form", true, false, "tags", c.Request.URL.Query(), ¶ms.Tags) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter indexType: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter tags: %s", err), http.StatusBadRequest) return } - // Parameter object where we will unmarshal all parameters from the context - var params DeleteDevfileIndexV1WithTypeParams - // ------------- Optional query parameter "arch" ------------- err = runtime.BindQueryParameter("form", true, false, "arch", c.Request.URL.Query(), ¶ms.Arch) @@ -815,123 +871,131 @@ func (siw *ServerInterfaceWrapper) DeleteDevfileIndexV1WithType(c *gin.Context) return } - for _, middleware := range siw.HandlerMiddlewares { - middleware(c) - } + // ------------- Optional query parameter "iconUri" ------------- - siw.Handler.DeleteDevfileIndexV1WithType(c, indexType, params) -} + err = runtime.BindQueryParameter("form", true, false, "iconUri", c.Request.URL.Query(), ¶ms.IconUri) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter iconUri: %s", err), http.StatusBadRequest) + return + } -// ServeDevfileIndexV1WithType operation middleware -func (siw *ServerInterfaceWrapper) ServeDevfileIndexV1WithType(c *gin.Context) { + // ------------- Optional query parameter "projectType" ------------- - var err error + err = runtime.BindQueryParameter("form", true, false, "projectType", c.Request.URL.Query(), ¶ms.ProjectType) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter projectType: %s", err), http.StatusBadRequest) + return + } - // ------------- Path parameter "indexType" ------------- - var indexType string + // ------------- Optional query parameter "language" ------------- - err = runtime.BindStyledParameter("simple", false, "indexType", c.Param("indexType"), &indexType) + err = runtime.BindQueryParameter("form", true, false, "language", c.Request.URL.Query(), ¶ms.Language) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter indexType: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter language: %s", err), http.StatusBadRequest) return } - // Parameter object where we will unmarshal all parameters from the context - var params ServeDevfileIndexV1WithTypeParams - - // ------------- Optional query parameter "arch" ------------- + // ------------- Optional query parameter "deprecated" ------------- - err = runtime.BindQueryParameter("form", true, false, "arch", c.Request.URL.Query(), ¶ms.Arch) + err = runtime.BindQueryParameter("form", true, false, "deprecated", c.Request.URL.Query(), ¶ms.Deprecated) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter arch: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter deprecated: %s", err), http.StatusBadRequest) return } - // ------------- Optional query parameter "icon" ------------- + // ------------- Optional query parameter "resources" ------------- - err = runtime.BindQueryParameter("form", true, false, "icon", c.Request.URL.Query(), ¶ms.Icon) + err = runtime.BindQueryParameter("form", true, false, "resources", c.Request.URL.Query(), ¶ms.Resources) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter icon: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter resources: %s", err), http.StatusBadRequest) return } - for _, middleware := range siw.HandlerMiddlewares { - middleware(c) - } + // ------------- Optional query parameter "starterProjects" ------------- - siw.Handler.ServeDevfileIndexV1WithType(c, indexType, params) -} + err = runtime.BindQueryParameter("form", true, false, "starterProjects", c.Request.URL.Query(), ¶ms.StarterProjects) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter starterProjects: %s", err), http.StatusBadRequest) + return + } -// PostDevfileIndexV1WithType operation middleware -func (siw *ServerInterfaceWrapper) PostDevfileIndexV1WithType(c *gin.Context) { + // ------------- Optional query parameter "linkNames" ------------- - var err error + err = runtime.BindQueryParameter("form", true, false, "linkNames", c.Request.URL.Query(), ¶ms.LinkNames) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter linkNames: %s", err), http.StatusBadRequest) + return + } - // ------------- Path parameter "indexType" ------------- - var indexType string + // ------------- Optional query parameter "links" ------------- - err = runtime.BindStyledParameter("simple", false, "indexType", c.Param("indexType"), &indexType) + err = runtime.BindQueryParameter("form", true, false, "links", c.Request.URL.Query(), ¶ms.Links) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter indexType: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter links: %s", err), http.StatusBadRequest) return } - // Parameter object where we will unmarshal all parameters from the context - var params PostDevfileIndexV1WithTypeParams - - // ------------- Optional query parameter "arch" ------------- + // ------------- Optional query parameter "gitRemoteNames" ------------- - err = runtime.BindQueryParameter("form", true, false, "arch", c.Request.URL.Query(), ¶ms.Arch) + err = runtime.BindQueryParameter("form", true, false, "gitRemoteNames", c.Request.URL.Query(), ¶ms.GitRemoteNames) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter arch: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRemoteNames: %s", err), http.StatusBadRequest) return } - // ------------- Optional query parameter "icon" ------------- + // ------------- Optional query parameter "gitRemotes" ------------- - err = runtime.BindQueryParameter("form", true, false, "icon", c.Request.URL.Query(), ¶ms.Icon) + err = runtime.BindQueryParameter("form", true, false, "gitRemotes", c.Request.URL.Query(), ¶ms.GitRemotes) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter icon: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRemotes: %s", err), http.StatusBadRequest) return } - for _, middleware := range siw.HandlerMiddlewares { - middleware(c) - } + // ------------- Optional query parameter "gitUrl" ------------- - siw.Handler.PostDevfileIndexV1WithType(c, indexType, params) -} + err = runtime.BindQueryParameter("form", true, false, "gitUrl", c.Request.URL.Query(), ¶ms.GitUrl) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitUrl: %s", err), http.StatusBadRequest) + return + } -// PutDevfileIndexV1WithType operation middleware -func (siw *ServerInterfaceWrapper) PutDevfileIndexV1WithType(c *gin.Context) { + // ------------- Optional query parameter "gitRemoteName" ------------- - var err error + err = runtime.BindQueryParameter("form", true, false, "gitRemoteName", c.Request.URL.Query(), ¶ms.GitRemoteName) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRemoteName: %s", err), http.StatusBadRequest) + return + } - // ------------- Path parameter "indexType" ------------- - var indexType string + // ------------- Optional query parameter "gitSubDir" ------------- - err = runtime.BindStyledParameter("simple", false, "indexType", c.Param("indexType"), &indexType) + err = runtime.BindQueryParameter("form", true, false, "gitSubDir", c.Request.URL.Query(), ¶ms.GitSubDir) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter indexType: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitSubDir: %s", err), http.StatusBadRequest) return } - // Parameter object where we will unmarshal all parameters from the context - var params PutDevfileIndexV1WithTypeParams + // ------------- Optional query parameter "gitRevision" ------------- - // ------------- Optional query parameter "arch" ------------- + err = runtime.BindQueryParameter("form", true, false, "gitRevision", c.Request.URL.Query(), ¶ms.GitRevision) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRevision: %s", err), http.StatusBadRequest) + return + } - err = runtime.BindQueryParameter("form", true, false, "arch", c.Request.URL.Query(), ¶ms.Arch) + // ------------- Optional query parameter "provider" ------------- + + err = runtime.BindQueryParameter("form", true, false, "provider", c.Request.URL.Query(), ¶ms.Provider) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter arch: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter provider: %s", err), http.StatusBadRequest) return } - // ------------- Optional query parameter "icon" ------------- + // ------------- Optional query parameter "supportUrl" ------------- - err = runtime.BindQueryParameter("form", true, false, "icon", c.Request.URL.Query(), ¶ms.Icon) + err = runtime.BindQueryParameter("form", true, false, "supportUrl", c.Request.URL.Query(), ¶ms.SupportUrl) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter icon: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter supportUrl: %s", err), http.StatusBadRequest) return } @@ -939,72 +1003,52 @@ func (siw *ServerInterfaceWrapper) PutDevfileIndexV1WithType(c *gin.Context) { middleware(c) } - siw.Handler.PutDevfileIndexV1WithType(c, indexType, params) + siw.Handler.ServeDevfileIndexV1(c, params) } -// DeleteDevfileIndexV2 operation middleware -func (siw *ServerInterfaceWrapper) DeleteDevfileIndexV2(c *gin.Context) { +// PostDevfileIndexV1 operation middleware +func (siw *ServerInterfaceWrapper) PostDevfileIndexV1(c *gin.Context) { for _, middleware := range siw.HandlerMiddlewares { middleware(c) } - siw.Handler.DeleteDevfileIndexV2(c) + siw.Handler.PostDevfileIndexV1(c) } -// ServeDevfileIndexV2 operation middleware -func (siw *ServerInterfaceWrapper) ServeDevfileIndexV2(c *gin.Context) { - - var err error - - // Parameter object where we will unmarshal all parameters from the context - var params ServeDevfileIndexV2Params - - // ------------- Optional query parameter "arch" ------------- - - err = runtime.BindQueryParameter("form", true, false, "arch", c.Request.URL.Query(), ¶ms.Arch) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter arch: %s", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "icon" ------------- - - err = runtime.BindQueryParameter("form", true, false, "icon", c.Request.URL.Query(), ¶ms.Icon) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter icon: %s", err), http.StatusBadRequest) - return - } +// PutDevfileIndexV1 operation middleware +func (siw *ServerInterfaceWrapper) PutDevfileIndexV1(c *gin.Context) { for _, middleware := range siw.HandlerMiddlewares { middleware(c) } - siw.Handler.ServeDevfileIndexV2(c, params) + siw.Handler.PutDevfileIndexV1(c) } -// PostDevfileIndexV2 operation middleware -func (siw *ServerInterfaceWrapper) PostDevfileIndexV2(c *gin.Context) { +// DeleteDevfileIndexV1WithType operation middleware +func (siw *ServerInterfaceWrapper) DeleteDevfileIndexV1WithType(c *gin.Context) { - for _, middleware := range siw.HandlerMiddlewares { - middleware(c) - } + var err error - siw.Handler.PostDevfileIndexV2(c) -} + // ------------- Path parameter "indexType" ------------- + var indexType string -// PutDevfileIndexV2 operation middleware -func (siw *ServerInterfaceWrapper) PutDevfileIndexV2(c *gin.Context) { + err = runtime.BindStyledParameter("simple", false, "indexType", c.Param("indexType"), &indexType) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter indexType: %s", err), http.StatusBadRequest) + return + } for _, middleware := range siw.HandlerMiddlewares { middleware(c) } - siw.Handler.PutDevfileIndexV2(c) + siw.Handler.DeleteDevfileIndexV1WithType(c, indexType) } -// DeleteDevfileIndexV2WithType operation middleware -func (siw *ServerInterfaceWrapper) DeleteDevfileIndexV2WithType(c *gin.Context) { +// ServeDevfileIndexV1WithType operation middleware +func (siw *ServerInterfaceWrapper) ServeDevfileIndexV1WithType(c *gin.Context) { var err error @@ -1018,48 +1062,48 @@ func (siw *ServerInterfaceWrapper) DeleteDevfileIndexV2WithType(c *gin.Context) } // Parameter object where we will unmarshal all parameters from the context - var params DeleteDevfileIndexV2WithTypeParams + var params ServeDevfileIndexV1WithTypeParams - // ------------- Optional query parameter "arch" ------------- + // ------------- Optional query parameter "name" ------------- - err = runtime.BindQueryParameter("form", true, false, "arch", c.Request.URL.Query(), ¶ms.Arch) + err = runtime.BindQueryParameter("form", true, false, "name", c.Request.URL.Query(), ¶ms.Name) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter arch: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter name: %s", err), http.StatusBadRequest) return } - // ------------- Optional query parameter "icon" ------------- + // ------------- Optional query parameter "displayName" ------------- - err = runtime.BindQueryParameter("form", true, false, "icon", c.Request.URL.Query(), ¶ms.Icon) + err = runtime.BindQueryParameter("form", true, false, "displayName", c.Request.URL.Query(), ¶ms.DisplayName) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter icon: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter displayName: %s", err), http.StatusBadRequest) return } - for _, middleware := range siw.HandlerMiddlewares { - middleware(c) - } + // ------------- Optional query parameter "description" ------------- - siw.Handler.DeleteDevfileIndexV2WithType(c, indexType, params) -} + err = runtime.BindQueryParameter("form", true, false, "description", c.Request.URL.Query(), ¶ms.Description) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter description: %s", err), http.StatusBadRequest) + return + } -// ServeDevfileIndexV2WithType operation middleware -func (siw *ServerInterfaceWrapper) ServeDevfileIndexV2WithType(c *gin.Context) { + // ------------- Optional query parameter "attributeNames" ------------- - var err error + err = runtime.BindQueryParameter("form", true, false, "attributeNames", c.Request.URL.Query(), ¶ms.AttributeNames) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter attributeNames: %s", err), http.StatusBadRequest) + return + } - // ------------- Path parameter "indexType" ------------- - var indexType string + // ------------- Optional query parameter "tags" ------------- - err = runtime.BindStyledParameter("simple", false, "indexType", c.Param("indexType"), &indexType) + err = runtime.BindQueryParameter("form", true, false, "tags", c.Request.URL.Query(), ¶ms.Tags) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter indexType: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter tags: %s", err), http.StatusBadRequest) return } - // Parameter object where we will unmarshal all parameters from the context - var params ServeDevfileIndexV2WithTypeParams - // ------------- Optional query parameter "arch" ------------- err = runtime.BindQueryParameter("form", true, false, "arch", c.Request.URL.Query(), ¶ms.Arch) @@ -1076,15 +1120,143 @@ func (siw *ServerInterfaceWrapper) ServeDevfileIndexV2WithType(c *gin.Context) { return } - for _, middleware := range siw.HandlerMiddlewares { + // ------------- Optional query parameter "iconUri" ------------- + + err = runtime.BindQueryParameter("form", true, false, "iconUri", c.Request.URL.Query(), ¶ms.IconUri) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter iconUri: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "projectType" ------------- + + err = runtime.BindQueryParameter("form", true, false, "projectType", c.Request.URL.Query(), ¶ms.ProjectType) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter projectType: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "language" ------------- + + err = runtime.BindQueryParameter("form", true, false, "language", c.Request.URL.Query(), ¶ms.Language) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter language: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "deprecated" ------------- + + err = runtime.BindQueryParameter("form", true, false, "deprecated", c.Request.URL.Query(), ¶ms.Deprecated) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter deprecated: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "resources" ------------- + + err = runtime.BindQueryParameter("form", true, false, "resources", c.Request.URL.Query(), ¶ms.Resources) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter resources: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "starterProjects" ------------- + + err = runtime.BindQueryParameter("form", true, false, "starterProjects", c.Request.URL.Query(), ¶ms.StarterProjects) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter starterProjects: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "linkNames" ------------- + + err = runtime.BindQueryParameter("form", true, false, "linkNames", c.Request.URL.Query(), ¶ms.LinkNames) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter linkNames: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "links" ------------- + + err = runtime.BindQueryParameter("form", true, false, "links", c.Request.URL.Query(), ¶ms.Links) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter links: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitRemoteNames" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitRemoteNames", c.Request.URL.Query(), ¶ms.GitRemoteNames) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRemoteNames: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitRemotes" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitRemotes", c.Request.URL.Query(), ¶ms.GitRemotes) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRemotes: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitUrl" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitUrl", c.Request.URL.Query(), ¶ms.GitUrl) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitUrl: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitRemoteName" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitRemoteName", c.Request.URL.Query(), ¶ms.GitRemoteName) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRemoteName: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitSubDir" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitSubDir", c.Request.URL.Query(), ¶ms.GitSubDir) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitSubDir: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitRevision" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitRevision", c.Request.URL.Query(), ¶ms.GitRevision) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRevision: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "provider" ------------- + + err = runtime.BindQueryParameter("form", true, false, "provider", c.Request.URL.Query(), ¶ms.Provider) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter provider: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "supportUrl" ------------- + + err = runtime.BindQueryParameter("form", true, false, "supportUrl", c.Request.URL.Query(), ¶ms.SupportUrl) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter supportUrl: %s", err), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { middleware(c) } - siw.Handler.ServeDevfileIndexV2WithType(c, indexType, params) + siw.Handler.ServeDevfileIndexV1WithType(c, indexType, params) } -// PostDevfileIndexV2WithType operation middleware -func (siw *ServerInterfaceWrapper) PostDevfileIndexV2WithType(c *gin.Context) { +// PostDevfileIndexV1WithType operation middleware +func (siw *ServerInterfaceWrapper) PostDevfileIndexV1WithType(c *gin.Context) { var err error @@ -1097,8 +1269,91 @@ func (siw *ServerInterfaceWrapper) PostDevfileIndexV2WithType(c *gin.Context) { return } + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.PostDevfileIndexV1WithType(c, indexType) +} + +// PutDevfileIndexV1WithType operation middleware +func (siw *ServerInterfaceWrapper) PutDevfileIndexV1WithType(c *gin.Context) { + + var err error + + // ------------- Path parameter "indexType" ------------- + var indexType string + + err = runtime.BindStyledParameter("simple", false, "indexType", c.Param("indexType"), &indexType) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter indexType: %s", err), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.PutDevfileIndexV1WithType(c, indexType) +} + +// DeleteDevfileIndexV2 operation middleware +func (siw *ServerInterfaceWrapper) DeleteDevfileIndexV2(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.DeleteDevfileIndexV2(c) +} + +// ServeDevfileIndexV2 operation middleware +func (siw *ServerInterfaceWrapper) ServeDevfileIndexV2(c *gin.Context) { + + var err error + // Parameter object where we will unmarshal all parameters from the context - var params PostDevfileIndexV2WithTypeParams + var params ServeDevfileIndexV2Params + + // ------------- Optional query parameter "name" ------------- + + err = runtime.BindQueryParameter("form", true, false, "name", c.Request.URL.Query(), ¶ms.Name) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter name: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "displayName" ------------- + + err = runtime.BindQueryParameter("form", true, false, "displayName", c.Request.URL.Query(), ¶ms.DisplayName) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter displayName: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "description" ------------- + + err = runtime.BindQueryParameter("form", true, false, "description", c.Request.URL.Query(), ¶ms.Description) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter description: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "attributeNames" ------------- + + err = runtime.BindQueryParameter("form", true, false, "attributeNames", c.Request.URL.Query(), ¶ms.AttributeNames) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter attributeNames: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "tags" ------------- + + err = runtime.BindQueryParameter("form", true, false, "tags", c.Request.URL.Query(), ¶ms.Tags) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter tags: %s", err), http.StatusBadRequest) + return + } // ------------- Optional query parameter "arch" ------------- @@ -1116,15 +1371,232 @@ func (siw *ServerInterfaceWrapper) PostDevfileIndexV2WithType(c *gin.Context) { return } + // ------------- Optional query parameter "iconUri" ------------- + + err = runtime.BindQueryParameter("form", true, false, "iconUri", c.Request.URL.Query(), ¶ms.IconUri) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter iconUri: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "projectType" ------------- + + err = runtime.BindQueryParameter("form", true, false, "projectType", c.Request.URL.Query(), ¶ms.ProjectType) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter projectType: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "language" ------------- + + err = runtime.BindQueryParameter("form", true, false, "language", c.Request.URL.Query(), ¶ms.Language) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter language: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "minVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "minVersion", c.Request.URL.Query(), ¶ms.MinVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter minVersion: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "maxVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "maxVersion", c.Request.URL.Query(), ¶ms.MaxVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter maxVersion: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "minSchemaVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "minSchemaVersion", c.Request.URL.Query(), ¶ms.MinSchemaVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter minSchemaVersion: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "maxSchemaVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "maxSchemaVersion", c.Request.URL.Query(), ¶ms.MaxSchemaVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter maxSchemaVersion: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "deprecated" ------------- + + err = runtime.BindQueryParameter("form", true, false, "deprecated", c.Request.URL.Query(), ¶ms.Deprecated) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter deprecated: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "default" ------------- + + err = runtime.BindQueryParameter("form", true, false, "default", c.Request.URL.Query(), ¶ms.Default) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter default: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "resources" ------------- + + err = runtime.BindQueryParameter("form", true, false, "resources", c.Request.URL.Query(), ¶ms.Resources) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter resources: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "starterProjects" ------------- + + err = runtime.BindQueryParameter("form", true, false, "starterProjects", c.Request.URL.Query(), ¶ms.StarterProjects) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter starterProjects: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "linkNames" ------------- + + err = runtime.BindQueryParameter("form", true, false, "linkNames", c.Request.URL.Query(), ¶ms.LinkNames) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter linkNames: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "links" ------------- + + err = runtime.BindQueryParameter("form", true, false, "links", c.Request.URL.Query(), ¶ms.Links) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter links: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "commandGroups" ------------- + + err = runtime.BindQueryParameter("form", true, false, "commandGroups", c.Request.URL.Query(), ¶ms.CommandGroups) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter commandGroups: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitRemoteNames" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitRemoteNames", c.Request.URL.Query(), ¶ms.GitRemoteNames) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRemoteNames: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitRemotes" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitRemotes", c.Request.URL.Query(), ¶ms.GitRemotes) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRemotes: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitUrl" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitUrl", c.Request.URL.Query(), ¶ms.GitUrl) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitUrl: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitRemoteName" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitRemoteName", c.Request.URL.Query(), ¶ms.GitRemoteName) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRemoteName: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitSubDir" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitSubDir", c.Request.URL.Query(), ¶ms.GitSubDir) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitSubDir: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitRevision" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitRevision", c.Request.URL.Query(), ¶ms.GitRevision) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRevision: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "provider" ------------- + + err = runtime.BindQueryParameter("form", true, false, "provider", c.Request.URL.Query(), ¶ms.Provider) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter provider: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "supportUrl" ------------- + + err = runtime.BindQueryParameter("form", true, false, "supportUrl", c.Request.URL.Query(), ¶ms.SupportUrl) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter supportUrl: %s", err), http.StatusBadRequest) + return + } + for _, middleware := range siw.HandlerMiddlewares { middleware(c) } - siw.Handler.PostDevfileIndexV2WithType(c, indexType, params) + siw.Handler.ServeDevfileIndexV2(c, params) } -// PutDevfileIndexV2WithType operation middleware -func (siw *ServerInterfaceWrapper) PutDevfileIndexV2WithType(c *gin.Context) { +// PostDevfileIndexV2 operation middleware +func (siw *ServerInterfaceWrapper) PostDevfileIndexV2(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.PostDevfileIndexV2(c) +} + +// PutDevfileIndexV2 operation middleware +func (siw *ServerInterfaceWrapper) PutDevfileIndexV2(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.PutDevfileIndexV2(c) +} + +// DeleteDevfileIndexV2WithType operation middleware +func (siw *ServerInterfaceWrapper) DeleteDevfileIndexV2WithType(c *gin.Context) { + + var err error + + // ------------- Path parameter "indexType" ------------- + var indexType string + + err = runtime.BindStyledParameter("simple", false, "indexType", c.Param("indexType"), &indexType) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter indexType: %s", err), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.DeleteDevfileIndexV2WithType(c, indexType) +} + +// ServeDevfileIndexV2WithType operation middleware +func (siw *ServerInterfaceWrapper) ServeDevfileIndexV2WithType(c *gin.Context) { var err error @@ -1138,7 +1610,47 @@ func (siw *ServerInterfaceWrapper) PutDevfileIndexV2WithType(c *gin.Context) { } // Parameter object where we will unmarshal all parameters from the context - var params PutDevfileIndexV2WithTypeParams + var params ServeDevfileIndexV2WithTypeParams + + // ------------- Optional query parameter "name" ------------- + + err = runtime.BindQueryParameter("form", true, false, "name", c.Request.URL.Query(), ¶ms.Name) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter name: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "displayName" ------------- + + err = runtime.BindQueryParameter("form", true, false, "displayName", c.Request.URL.Query(), ¶ms.DisplayName) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter displayName: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "description" ------------- + + err = runtime.BindQueryParameter("form", true, false, "description", c.Request.URL.Query(), ¶ms.Description) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter description: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "attributeNames" ------------- + + err = runtime.BindQueryParameter("form", true, false, "attributeNames", c.Request.URL.Query(), ¶ms.AttributeNames) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter attributeNames: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "tags" ------------- + + err = runtime.BindQueryParameter("form", true, false, "tags", c.Request.URL.Query(), ¶ms.Tags) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter tags: %s", err), http.StatusBadRequest) + return + } // ------------- Optional query parameter "arch" ------------- @@ -1156,11 +1668,229 @@ func (siw *ServerInterfaceWrapper) PutDevfileIndexV2WithType(c *gin.Context) { return } + // ------------- Optional query parameter "iconUri" ------------- + + err = runtime.BindQueryParameter("form", true, false, "iconUri", c.Request.URL.Query(), ¶ms.IconUri) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter iconUri: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "projectType" ------------- + + err = runtime.BindQueryParameter("form", true, false, "projectType", c.Request.URL.Query(), ¶ms.ProjectType) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter projectType: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "language" ------------- + + err = runtime.BindQueryParameter("form", true, false, "language", c.Request.URL.Query(), ¶ms.Language) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter language: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "minVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "minVersion", c.Request.URL.Query(), ¶ms.MinVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter minVersion: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "maxVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "maxVersion", c.Request.URL.Query(), ¶ms.MaxVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter maxVersion: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "minSchemaVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "minSchemaVersion", c.Request.URL.Query(), ¶ms.MinSchemaVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter minSchemaVersion: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "maxSchemaVersion" ------------- + + err = runtime.BindQueryParameter("form", true, false, "maxSchemaVersion", c.Request.URL.Query(), ¶ms.MaxSchemaVersion) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter maxSchemaVersion: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "deprecated" ------------- + + err = runtime.BindQueryParameter("form", true, false, "deprecated", c.Request.URL.Query(), ¶ms.Deprecated) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter deprecated: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "default" ------------- + + err = runtime.BindQueryParameter("form", true, false, "default", c.Request.URL.Query(), ¶ms.Default) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter default: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "resources" ------------- + + err = runtime.BindQueryParameter("form", true, false, "resources", c.Request.URL.Query(), ¶ms.Resources) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter resources: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "starterProjects" ------------- + + err = runtime.BindQueryParameter("form", true, false, "starterProjects", c.Request.URL.Query(), ¶ms.StarterProjects) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter starterProjects: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "linkNames" ------------- + + err = runtime.BindQueryParameter("form", true, false, "linkNames", c.Request.URL.Query(), ¶ms.LinkNames) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter linkNames: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "links" ------------- + + err = runtime.BindQueryParameter("form", true, false, "links", c.Request.URL.Query(), ¶ms.Links) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter links: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "commandGroups" ------------- + + err = runtime.BindQueryParameter("form", true, false, "commandGroups", c.Request.URL.Query(), ¶ms.CommandGroups) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter commandGroups: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitRemoteNames" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitRemoteNames", c.Request.URL.Query(), ¶ms.GitRemoteNames) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRemoteNames: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitRemotes" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitRemotes", c.Request.URL.Query(), ¶ms.GitRemotes) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRemotes: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitUrl" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitUrl", c.Request.URL.Query(), ¶ms.GitUrl) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitUrl: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitRemoteName" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitRemoteName", c.Request.URL.Query(), ¶ms.GitRemoteName) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRemoteName: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitSubDir" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitSubDir", c.Request.URL.Query(), ¶ms.GitSubDir) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitSubDir: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "gitRevision" ------------- + + err = runtime.BindQueryParameter("form", true, false, "gitRevision", c.Request.URL.Query(), ¶ms.GitRevision) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter gitRevision: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "provider" ------------- + + err = runtime.BindQueryParameter("form", true, false, "provider", c.Request.URL.Query(), ¶ms.Provider) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter provider: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "supportUrl" ------------- + + err = runtime.BindQueryParameter("form", true, false, "supportUrl", c.Request.URL.Query(), ¶ms.SupportUrl) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter supportUrl: %s", err), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.ServeDevfileIndexV2WithType(c, indexType, params) +} + +// PostDevfileIndexV2WithType operation middleware +func (siw *ServerInterfaceWrapper) PostDevfileIndexV2WithType(c *gin.Context) { + + var err error + + // ------------- Path parameter "indexType" ------------- + var indexType string + + err = runtime.BindStyledParameter("simple", false, "indexType", c.Param("indexType"), &indexType) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter indexType: %s", err), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.PostDevfileIndexV2WithType(c, indexType) +} + +// PutDevfileIndexV2WithType operation middleware +func (siw *ServerInterfaceWrapper) PutDevfileIndexV2WithType(c *gin.Context) { + + var err error + + // ------------- Path parameter "indexType" ------------- + var indexType string + + err = runtime.BindStyledParameter("simple", false, "indexType", c.Param("indexType"), &indexType) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter indexType: %s", err), http.StatusBadRequest) + return + } + for _, middleware := range siw.HandlerMiddlewares { middleware(c) } - siw.Handler.PutDevfileIndexV2WithType(c, indexType, params) + siw.Handler.PutDevfileIndexV2WithType(c, indexType) } // GinServerOptions provides options for the Gin server. @@ -1278,41 +2008,63 @@ func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xcX3PbNhL/KhjczVwyQ4uO6nbm9Ja7Jlc9tOexfbmHOg8QuRLRkAALLKWoHn33zgIk", - "JZr6F9lS1JhPlkAQ2D+/3f1hTeqBRzrLtQKFlg8eeC6MyADBuG/CRMk1jdCXGGxkZI5SKz7gdwkwFGYC", - "yGiWRIiwMMDGMkUwPOCSZv1egJnzgCuRAR+49XjAbZRAJmjNvxsY8wH/W7iUIvRXbfh2ZVnLF4uAy0ir", - "LeLQZYbzfIcQNG1vIYY0eUGbG7C5Vhas33w6lim8M0abm/ICjUdaISh0tsvzVEaC5At/syTkw8qeudE5", - "GJR+OaB16ANJzwfcopFqwgP++WKiL0q53WZ8EXCLAgu7a/qtn0Wil9P06DeI0I2sCjcXWXpGwi2CR659", - "L2QKMUPNCGyYACut3+Nurvv8i8b3ulDxMzjjxOY9psHGUsWbLHaQpbaFyo9+3T0MsN8qLb1uiygCa8dF", - "ysiAbvXevbpXtyiiT5WOrFTG6ZqASDF5BlBkYK2YwC43/VxO8wnj90IaiPng1/r2j09Fy/Hk2N/cPzmj", - "Mg9cZ2apYvj87IAa0qq3furTQNVcaX9N3X0NQGWAiY5/0fg2TfUM4g5ah0DrZ2dFVliImbRMaWS2yHNt", - "EOJeWUUMgrk2mpbYw8h/yLyp21ibTCAf8JFUwpX/plJfAoP3lFZGcwRLSTXWM5Vq4QWd9ocHY99ZtZLK", - "jfZKjAbLaxcyI7t4boYJH/CJxKQY9SKdhWXKCw1MpEUzvyitGLqADCegSA1tykDwSm/HxFcRan9XfOiz", - "x0G5qLicQ3GTNbZ44n/dB5GyVFpkesxyo2knbRo01jJMRINsVAC1AYMsx7lfwBaTCVhcMz0Sio3AQ1wr", - "JtS8sQFxU4RsjYSrCjB/aeTkgSbRdj4KOKgio/ATWfzDFQ+4MJn7m+fRD1cpzbDf/fPy80pYVgFQDwhj", - "xJy+V7W3JdOPDTEsmsLLoMdMsCjVRXyhBMqpU3+mzSebiwiYUDENQKrzDBQyUFNptMqc34IG1KZvRJon", - "ot+rZPhStIlchtN+mH+a0Ecb1lLYsFrbQcUR+s2wqNxHh4SgcqJg/7sZMoIIM5B6RUkSJpUzSO7TFF9j", - "4tXKs/7Q4tDsEdaKs9MmBgtRYSTO3V4+eEbCyqhOECSPH6k1TRBzf7dUY70GOToqyN8uggkvlYErydjN", - "u9s79vZ66ML7biWCWjOoVLh4GmvDpEIwIkKpJmwmMWnd1mND8o60LF6VIXAeS7RFWs6CmdIKzovFKJVR", - "a52AzXXhkBAlQk2ASaQqMNeFYXqmyqXGbtZMKHexsAQKORXYVoeQjxIpzKqAW2MMHvApGOuN+KZ32XtD", - "aNI5KJFLPuDfuaHAud55KvS2TwFd/NZZcxi7fWj8Rmt8p+JcS0VYbRxory6/30Si6nnhRvLjADABbPv/", - "FtCyIvdGFypOwTj30XejNbJX4WsGpVCUKF2CATMFc6+GY5ZglpKjiGmARYjZK9mDHhsbnTHBZjBiI6Nn", - "Fsxr79mphBkYuiWWNk/FHOKAaUzAzKSFRor2oVeigIhH8MhstzS+zWoxjEWR4tkz3oAjfMaQjMkHD+1y", - "SzqySrOypBZZRrypvLh00XiJVu8nx4NybbGNu2tt8cioy4t1+xbH3XYR8CrF2vDB0vFzsTv+lqVttcv2", - "67qq4JZkjnGXTSyX4+selrvOVwk4mgJWm1o7mgnRJ/LamsGPJ0oMN4CFKcM9h0iOZVRq/egcv65obAjV", - "v46BXTL7l47nzdTRCkwStZzNRjqes6ygT+A5qIu8hrf6l5e7vfW4AbQI+NXl1d73tVpti4B//wX7Npum", - "zVzzH8Da2aP5ipdc1RQTcmfVwOIft+adlxptmxLiy7THukwdlt2Fi5K3+wsr/Yb9c/lt477ztm2wQRxS", - "oDrCOMGqY00jHW+Utqn/wWKvLrPYfvVUJeo9YJSAbdmorFcQk6GWxHRJixplzB1O7lXJ5v9h67pGp+Pc", - "6KmMwTKh/Ol+CuzVHzJ/7U+EVauJCX/y/unu7nqFp20rgh0yzwCZe5XjDb3OZVVu2uZaTMB1TMdUhHvP", - "WX03Ab6uxCVka3/wfSpwh8RvI0fuIBadm78FN6/lSw9l8dqfF/1fYvKh7F/99ZBQqksnz9r56wWb1joe", - "Jlplo8WG4W/gNN5B4Rmh0PUNTtU36GB7thlsBxHpPHemntvOLY7Wlenw8Dx46Ejzy+0fdTHUxdCL6nSR", - "2kH5uQRF8Bztry6QukA6q0ZdB8gOkMdvKfr3MHYTeP9qwb8TKEF02tZX68nDpPmmw1q+tEXkvYrio1dU", - "9i6Grf5HS9iq7+Ef09rR9jiu5TelpGPuSrhzj/jtfW50D819eMNPfD5wz0JWkFt5Hnil5do8Erh3HO+V", - "ewayQem3Mvqldo+y/DrFllPC5UuglOR2TF6+onkGDcvma0kHR5YtnVI+W9koNpVP7MGNxuOBbgcPONbG", - "ddyFD+7P3TyHxZfGINGSO/+Gw05O0giL5qu/zXpai3NwKR3WKyw2XzhuRJ1ZYqr+W/Qsuanz+gvMo1UG", - "bcCJ3PHUlNqh6TQ5ZL8603njFN6g4jvtH0J7+1+V9lYH+f6GOnOv1lDgg4pM/wUR4MdvJz8hdX/on4AF", - "978WC+7zIwbiE3hwv8ua58aDt2aqe7WREx+WrDr/v9TEeixa3EHqnGhx543T0GL38wZmWtm2MGn54wV2", - "ENa/ktCzKCbQq34sS+rQyb1hcmPax8WfAQAA//+ZpfowSk8AAA==", + "H4sIAAAAAAAC/+xda4/bttL+K4TeAm+Co7U3blqg+6VomyZdoM0J9pLzoc4BaGlss5FIhaS86y783w94", + "08WSbPq22Sb6kuxa5PDhcDjzcEbmPgQRSzNGgUoRXDwEGeY4BQlc/4Z5NH+nPlG/xCAiTjJJGA0ugl9Y", + "kkCkfkFsigSopkhITuhMIMnQlCQSOBISRx8FmiyRnAPhSDUjEiKZcxBBGBAl61MOfBmEAcUpBBd61CAM", + "RDSHFKuRv+EwDS6C/xuWWIfmqRj+VBO4WoUBlpKTSS7hLU5BHBE+UviEaj+mMUwJhRhNOcDZlPEUFcN2", + "TquGqzZBIiHVCpfLTDU1QIJV6D7AnOOlnl3E0hTT+A1neSaOuzYZBwFUIjsEGtOZHqVjPjUk3uv1S62X", + "mlEMU5wnsmMuPzOWAKZN2GSqYC8R5oCsCMQ4okx24LWNvJG+su0NxoxDhCXEh8F0UrYhde12AFt0MXgL", + "cB2Ar6tW0WnxlT5Iwn034FK0P+Kyj4ZMRJbgpdoch0AmHFlJZrt2IS5H80dc6aMQz4i8gpSZDX0g5hmR", + "iGthGnYH6tqI3rjf1Ho1kJ/KRapfy2kJnymJ/eYk6pM66oRury73m88ec6nMY0HEgXu3MCojahPcosUO", + "eG0fC/g6n7wi/EC4EvMZSCTySUw4RJLxJRpTph2onUvGBFGfd8/GINllLraHncktT46g9ZwnGwzklife", + "AFVbBY1EneZww2azBBCjCGjEYoUuYlSqUJ5hIXQUaUOiRHrjuIzsaqtet5wcqCQlBeWcbIB2q5/6o1Pt", + "FcAE01mOZ4d65IyzGcdpqlo5kR1oK4/94P7uOmi8hH48kR8udo8aAwmW86jTcRUw/GdR9HDTODIh1ajH", + "dDvu3TAbvCm+v9Yfvge+wdfezAGl+J6keYpiWExJAsgIQwvTsQPXunxviPVeFqo/SK3E7dh2RVXDQ6i3", + "6gjdQ3Vr8g9RHaH+IL1UVwjcS3X0cKq4gR/SXWhhwQYzzv6CSN4ssyP4TCUJ6VNrO8TKYN5I31X6WMAL", + "EsNBfMMuthPVjdY99oZqOiicHKzrOq5bHFMnWLXodIzF6N7gr4oeCr2QmEvgVvmnjE52JGc+XRNaA+Tv", + "Fdb66cnlWcb4UdjeAiiy4hTv6wJfDLgz9ZN4dmQLUhI7cNpHe+SmjMFnjAoQBql2+b9yzviVfaA+t9xU", + "5xezLCERVviHfwk1o4fKyBlnGXBJjDhQcpo4wuD+bMbOLHo9WGCMV+ZiW/Nr02pVToZNlI2YPGIF3BKn", + "yRMCV8/wBBfBa0wSiNWKqxOUydxo7Q8C3Vb//JbJ1yyn8REW45HVe0qFTQmNuzS2l6Y2J720XA8F+Elp", + "zOs6jyIQYponSClQSx+M6Zhe63DnaJidjJ7rHHAi50cwihSEUIeaLcv0h21mHMannHCIg4s/i+4fDrWW", + "0+HwV/dvWqnIGK5WM6Ex3B/doC6VVEN7DzSquiT/mep+NYNKQc5Z/JbJn5KE3UHcm9Y+pvWH1iLKBcSI", + "CESZdCwD4kHQ4GceSv6bZPW5TRlPsQwuggmhWJOAtRi/gxm8Vm5lspSgeUfM7mjCsAG6GF3ubftaqw6V", + "/nRgbTQsn52RVOnF1C/l3CTZ5vlkELF0aF3ekMOMCMmXZ1aLQ70hhzOgahqM241gJr3ZJj4LKP+leD9C", + "65ty5RidtuJ6zbRBKv+tf8AJSoiQildmnKmR2Fr5Fsk5rpENZ6AiRJBmcmkEiHw2AyFbmkeYogkYE2cU", + "YbqsDaAYqiOfdYTVCdga1UTjgZoAdw4Fmqdq++E0/v5lEAaYp/r/LIu+f5noc+i3P5zfV7ZlF8kNg3r9", + "soHsd6syV0M1FVTkysWEuslXJ+fwTXKSxEEY8JyqvQhCBmrRJ/lM/58lbLkdYxjklHzK4dJIlzyHVRi4", + "YmYD8OsEz9CU8aKG6hbHmSYCqv4tsyJ2tImpeAZaeFF83CS/KH62D9EhuiJsXXblodL5NrGlwhyB6pDo", + "bElInhtDYlOEUZSwPD6jWJKFtuE7xj+KDEeA1ErHsICEZSlQiYAuCGc01ZsvrPmLxQucZHM8Grwq7GA3", + "l4EzMlyMhtnHmfpRDAsUYuhk6/1erVY25nkrgCMOOMaTxBzHd1NgvaTYEP+mVqxZK2xuFrZhT806pYrq", + "btqhFOq1dSrluR2huSqQDzJ92G+6m2qtrfnSAcc0mofqOB8ixrXT0UCmwIFGXcq2Ba9myqNaeGtOSjLl", + "qREWmwfQhaLumOJsTBeAinqVddStwm458ZWXcxK6qILR7dXvSisYcUjMplW7Svlgtbltsql11AoVbk0Y", + "6yiNKgnttU3+WKQgDIpKUgPnu5b6FbIJN6e0wne0aaEs7zRkv3VVfifIFWE8k0XtG83UZjr3WOdYe2yr", + "drf1dmc/WM1MNy1lmYGlTyYr3sKUOoSaHHInQJeWXl/P7YDLFG+nnq0k207H7soY/ou8CoN6PaYl2rYV", + "hRDN04lOucM9TjMVpoPRYDQ4D5H679szHeQUfcNSAleC/vvn+dkPH/41Hg/MD8+qP5n2z398/uM3bRpZ", + "zw536mUtS90MPO1rtd6tmpbfZ5coi27Gc56gKYEk7vSkWxehne+1LcaLwcvBuaf+W5WuTiMQ5ZzIpbYQ", + "Y40TLEhUHLQ0D9SfFN3nUmbmeEbolLXMhEW5oly4kxBe/Xp9g356d6mPSTdtG8e1UEdufS5R5k+oBI4j", + "qVzpHZHzRrcBulRBhQgUVzGEeuPMmZBKnAC+0LFOBZ98kpCoISdES5brABbNMZ0BIlKF3SXLOWJ31Iqa", + "6lZ3mEoXkzNOFlg2p6PIpyRSr9qrTmUEYeDYvVrc88ELZTAsA4ozElwE3+qP9HrP9UoNje4TkNrrFafP", + "y1iPoz6/Ykz+SuOMEapCbK0w8PL8uy63XbQbdiaRtAHMQLaVbKRAeWaUjmmcAC+8F2dMomfD5wgsKHXg", + "1Bwf+AL4mF5O0VymiVooDp9yEOqg8owMYICmnKUIozuYoAlndwL4c7OyCwJ3wFUX+zohxCFicg78jgio", + "eWbDGKwVQKx0Xlfbtfp8k9bi8gT3pDOH6uB6L4dKmcHFQzNtoeaI3MxsaiJPU8yX7mG5RNPSWs066XxS", + "xoRs2t07JuSJrS7L28bNTzvsKgwcMxTDB13JW23ff+XpsvpG/59tZNYUwqtvFWhqWi27Rh+DaiJTRaJq", + "iXBLUSb6qFat5cMPj+QYrkDm3G73DCIyJZGd9Vo9pC1odGzVf4SCw3ZlloiH7a/y+HRsfX3KLKl2nz+z", + "eFl3Vg1XoJRjW6MJi5cozdVPYLKHeq/X7GN0fr7dPtZLd6sweHn+0rtfo0i6CoPvdhi3Xu6ue7c3UKbX", + "JsuKXeg4jWfKgFzpMfiw0dN9rfu7ywV/nfpoiw1De+A4c2/T6AeVI45/9KgfjZ68q2uF0ziyufxPLQB0", + "oq3Pf2/YVTGrzU8fKyi+BhnNQTR0ZCOkKVaUVLgkYrXAqY9DY2rPD/8vikiKaezSFAJhauoyC0DP/ibZ", + "c5NjcEVChE26/bebm3cVZrgp7PaW+Rks8zOxiV0JQEddvOQBa3lSPANdXZ+qsD84Zrzv2mJF7LebpLCA", + "wCfm97b/ZXjlLVSmX+YvYZlbGdqDDZf+TOw/RM7Lrx380yzB5ZLL17316C3AyrcM9oNWfNWi4+MvIOPQ", + "m8JxTKHPjXzhuZF+ozxZn7mF+vQr90RXbjObOVnmqbeHo4W8nqZ/pTmyfg/1e6jP5p00m6cUHdYvEQiP", + "keLrt26/dZ9UMrI3yN4gT582Nd9L3n5kMF+1/WUO1ogeN73XeIN0Xv/mbytD2wDZKyiufWXbOxg2Mi4N", + "sC7TYl6325JoOa3mu1zSKUdVdqdf1fQ+qeqXH9+/CB75RKLfaXUmV/k6SiWtXD+EqM2OxlS/y1o7RGw8", + "Q5SzW/PyWxhgec2RB11s3Pzp02f9glOPPm0XA3t0K69f8RmjuDHZo3F5p59n4+LePY/2jVudPPrUr87z", + "Wob6vbgeXdYuRfLo0XoRkc9s6hfrefbwb912i+ou3XbqUtxUtCuwXXpVr/H0Had6U6mfWVbu7vJZ/bWb", + "mp5AGaV+lcfe0VdYx23fo68RUue3xd7lj9MFpi1nhVMNXMTm4YP+T3m21a5xWh1d7N1zW88ttdBpbtJq", + "59wFnL3p9mUhYdX54MMTpBiutn0UlvEPXpuwZ0Q9I+oZUc+IekZ0AkbkuFAt5CiXfSg56tnA7ryu11md", + "ki5G+ySMRp81YeRS4KMOXjemLcmjvUjdqE8d9USpq7q8R1155y5HrmGfhADW/gLWV0UYW/6aWU8ze5rZ", + "Ff/WbxI9gGi+Hz1C9m30ubJvo+CEVOeA/NuoZ48nYGz6Dym05+L2I219Jq4nmD3B7AlmTzB7gtkTzOMS", + "zFMlM3tqtQdN7nVWYfj6Wk6+cBrIeWIv3RQXw+JS4oGQeAYD98dyCBvqrd7RuNbsw+p/AQAA//9nXWi2", + "bn4AAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/index/server/pkg/server/endpoint.go b/index/server/pkg/server/endpoint.go index 3aa9a7464..a495dc258 100644 --- a/index/server/pkg/server/endpoint.go +++ b/index/server/pkg/server/endpoint.go @@ -68,7 +68,7 @@ func (*Server) DeleteRootEndpoint(c *gin.Context) { } func (*Server) ServeDevfileIndexV1(c *gin.Context, params ServeDevfileIndexV1Params) { - ServeDevfileIndex(c, true, IndexParams(params)) + ServeDevfileIndex(c, true, params.toIndexParams()) } func (*Server) PostDevfileIndexV1(c *gin.Context) { @@ -84,7 +84,7 @@ func (*Server) DeleteDevfileIndexV1(c *gin.Context) { } func (*Server) ServeDevfileIndexV2(c *gin.Context, params ServeDevfileIndexV2Params) { - ServeDevfileIndex(c, false, IndexParams(params)) + ServeDevfileIndex(c, false, params.toIndexParams()) } func (*Server) PostDevfileIndexV2(c *gin.Context) { @@ -117,36 +117,36 @@ func ServeDevfileIndex(c *gin.Context, wantV1Index bool, params IndexParams) { func (*Server) ServeDevfileIndexV1WithType(c *gin.Context, indexType string, params ServeDevfileIndexV1WithTypeParams) { // Serve the index with type - buildIndexAPIResponse(c, indexType, true, IndexParams(params)) + buildIndexAPIResponse(c, indexType, true, params.toIndexParams()) } -func (*Server) PostDevfileIndexV1WithType(c *gin.Context, indexType string, params PostDevfileIndexV1WithTypeParams) { +func (*Server) PostDevfileIndexV1WithType(c *gin.Context, indexType string) { SetMethodNotAllowedJSONResponse(c) } -func (*Server) PutDevfileIndexV1WithType(c *gin.Context, indexType string, params PutDevfileIndexV1WithTypeParams) { +func (*Server) PutDevfileIndexV1WithType(c *gin.Context, indexType string) { SetMethodNotAllowedJSONResponse(c) } -func (*Server) DeleteDevfileIndexV1WithType(c *gin.Context, indexType string, params DeleteDevfileIndexV1WithTypeParams) { +func (*Server) DeleteDevfileIndexV1WithType(c *gin.Context, indexType string) { SetMethodNotAllowedJSONResponse(c) } func (*Server) ServeDevfileIndexV2WithType(c *gin.Context, indexType string, params ServeDevfileIndexV2WithTypeParams) { // Serve the index with type - buildIndexAPIResponse(c, indexType, false, IndexParams(params)) + buildIndexAPIResponse(c, indexType, false, params.toIndexParams()) } -func (*Server) PostDevfileIndexV2WithType(c *gin.Context, indexType string, params PostDevfileIndexV2WithTypeParams) { +func (*Server) PostDevfileIndexV2WithType(c *gin.Context, indexType string) { SetMethodNotAllowedJSONResponse(c) } -func (*Server) PutDevfileIndexV2WithType(c *gin.Context, indexType string, params PutDevfileIndexV2WithTypeParams) { +func (*Server) PutDevfileIndexV2WithType(c *gin.Context, indexType string) { SetMethodNotAllowedJSONResponse(c) } -func (*Server) DeleteDevfileIndexV2WithType(c *gin.Context, indexType string, params DeleteDevfileIndexV2WithTypeParams) { +func (*Server) DeleteDevfileIndexV2WithType(c *gin.Context, indexType string) { SetMethodNotAllowedJSONResponse(c) } @@ -172,8 +172,8 @@ func (*Server) DeleteHealthCheck(c *gin.Context) { SetMethodNotAllowedJSONResponse(c) } -func (*Server) ServeDevfileWithVersion(c *gin.Context, name string, version string) { - bytes, devfileIndex := fetchDevfile(c, name, version) +func (*Server) ServeDevfileWithVersion(c *gin.Context, name string, version string, params ServeDevfileWithVersionParams) { + bytes, devfileIndex := fetchDevfile(c, name, version, params) if len(bytes) != 0 { // Track event for telemetry. Ignore events from the registry-viewer and DevConsole since those are tracked on the client side. Ignore indirect calls from clients. @@ -213,9 +213,9 @@ func (*Server) DeleteDevfileWithVersion(c *gin.Context, name string, version str } // ServeDevfile returns the devfile content -func (s *Server) ServeDevfile(c *gin.Context, name string) { +func (s *Server) ServeDevfile(c *gin.Context, name string, params ServeDevfileParams) { // append the stack version, for endpoint /devfiles/name without version - s.ServeDevfileWithVersion(c, name, "default") + s.ServeDevfileWithVersion(c, name, "default", ServeDevfileWithVersionParams(params)) } func (s *Server) PostDevfile(c *gin.Context, name string) { @@ -231,8 +231,8 @@ func (s *Server) DeleteDevfile(c *gin.Context, name string) { } // ServeDevfileStarterProject returns the starter project content for the devfile using default version -func (s *Server) ServeDevfileStarterProject(c *gin.Context, name string, starterProject string) { - s.ServeDevfileStarterProjectWithVersion(c, name, "default", starterProject) +func (s *Server) ServeDevfileStarterProject(c *gin.Context, name string, starterProject string, params ServeDevfileStarterProjectParams) { + s.ServeDevfileStarterProjectWithVersion(c, name, "default", starterProject, ServeDevfileStarterProjectWithVersionParams(params)) } func (s *Server) PostDevfileStarterProject(c *gin.Context, name string, starterProject string) { @@ -248,10 +248,10 @@ func (s *Server) DeleteDevfileStarterProject(c *gin.Context, name string, starte } // ServeDevfileStarterProject returns the starter project content for the devfile using specified version -func (*Server) ServeDevfileStarterProjectWithVersion(c *gin.Context, name string, version string, starterProject string) { +func (*Server) ServeDevfileStarterProjectWithVersion(c *gin.Context, name string, version string, starterProject string, params ServeDevfileStarterProjectWithVersionParams) { downloadTmpLoc := path.Join("/tmp", starterProject) stackLoc := path.Join(stacksPath, name) - devfileBytes, devfileIndex := fetchDevfile(c, name, version) + devfileBytes, devfileIndex := fetchDevfile(c, name, version, ServeDevfileWithVersionParams(params)) if len(devfileIndex.Versions) > 1 { versionMap, err := util.MakeVersionMap(devfileIndex) @@ -481,19 +481,14 @@ func ServeUI(c *gin.Context) { func buildIndexAPIResponse(c *gin.Context, indexType string, wantV1Index bool, params IndexParams) { iconType := "" - archs := []string{} + + var bytes []byte + var responseIndexPath, responseBase64IndexPath string if params.Icon != nil { iconType = *params.Icon } - if params.Arch != nil { - archs = append(archs, *params.Arch...) - } - - var bytes []byte - var responseIndexPath, responseBase64IndexPath string - // Sets Access-Control-Allow-Origin response header to allow cross origin requests c.Header("Access-Control-Allow-Origin", "*") @@ -543,28 +538,37 @@ func buildIndexAPIResponse(c *gin.Context, indexType string, wantV1Index bool, p }) return } + + // Filter based on deprecation if deprecated parameter is set + if params.Deprecated != nil { + util.FilterDevfileDeprecated(&index, *params.Deprecated, wantV1Index) + } + if wantV1Index { index = util.ConvertToOldIndexFormat(index) } else { - minSchemaVersion := c.Query("minSchemaVersion") - maxSchemaVersion := c.Query("maxSchemaVersion") - if maxSchemaVersion != "" || minSchemaVersion != "" { + minSchemaVersion := params.MinSchemaVersion + maxSchemaVersion := params.MaxSchemaVersion + minVersion := params.MinVersion + maxVersion := params.MaxVersion + + if util.StrPtrIsSet(maxSchemaVersion) || util.StrPtrIsSet(minSchemaVersion) { // check if schema version filters are in valid format. // should include major and minor versions as well as an optional bugfix version. e.g. 2.1 or 2.1.0 - if minSchemaVersion != "" { - matched, err := regexp.MatchString(`^([2-9])\.([0-9]+)(\.[0-9]+)?$`, minSchemaVersion) + if util.StrPtrIsSet(minSchemaVersion) { + matched, err := regexp.MatchString(`^([2-9])\.([0-9]+)(\.[0-9]+)?$`, *minSchemaVersion) if !matched || err != nil { c.JSON(http.StatusBadRequest, gin.H{ - "status": fmt.Sprintf("minSchemaVersion %s is not valid, version format should be '+2.x' or '+2.x.x'. %v", minSchemaVersion, err), + "status": fmt.Sprintf("minSchemaVersion %s is not valid, version format should be '+2.x' or '+2.x.x'. %v", *minSchemaVersion, err), }) return } } - if maxSchemaVersion != "" { - matched, err := regexp.MatchString(`^([2-9])\.([0-9]+)(\.[0-9]+)?$`, maxSchemaVersion) + if util.StrPtrIsSet(maxSchemaVersion) { + matched, err := regexp.MatchString(`^([2-9])\.([0-9]+)(\.[0-9]+)?$`, *maxSchemaVersion) if !matched || err != nil { c.JSON(http.StatusBadRequest, gin.H{ - "status": fmt.Sprintf("maxSchemaVersion %s is not valid, version format should be '+2.x' or '+2.x.x'. %v", maxSchemaVersion, err), + "status": fmt.Sprintf("maxSchemaVersion %s is not valid, version format should be '+2.x' or '+2.x.x'. %v", *maxSchemaVersion, err), }) return } @@ -578,11 +582,47 @@ func buildIndexAPIResponse(c *gin.Context, indexType string, wantV1Index bool, p return } } + + if util.StrPtrIsSet(minVersion) || util.StrPtrIsSet(maxVersion) { + // check if version filters are in valid format. + // should include major and minor versions as well as an optional bugfix version. e.g. 2.1 or 2.1.0 + if util.StrPtrIsSet(minVersion) { + matched, err := regexp.MatchString(`^([0-9])\.([0-9]+)(\.[0-9]+)?$`, *minVersion) + if !matched || err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "status": fmt.Sprintf("minVersion %s is not valid, version format should be 'x.x' or 'x.x.x'. %v", *minVersion, err), + }) + return + } + } + if util.StrPtrIsSet(maxVersion) { + matched, err := regexp.MatchString(`^([0-9]+)\.([0-9]+)(\.[0-9]+)?$`, *maxVersion) + if !matched || err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "status": fmt.Sprintf("maxVersion %s is not valid, version format should be 'x.x' or 'x.x.x'. %v", *maxVersion, err), + }) + return + } + } + + index, err = util.FilterDevfileVersion(index, minVersion, maxVersion) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "status": fmt.Sprintf("failed to apply version filter: %v", err), + }) + return + } + } } - // Filter the index if archs has been requested - if len(archs) > 0 { - index = util.FilterDevfileArchitectures(index, archs, wantV1Index) + + // Filter the fields of the index + index, err = filterFieldsByParams(index, wantV1Index, params) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "status": fmt.Sprintf("failed to perform field filtering: %v", err), + }) } + bytes, err = json.MarshalIndent(&index, "", " ") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ @@ -634,7 +674,7 @@ func buildProxyErrorResponse(w http.ResponseWriter, r *http.Request, err error, // fetchDevfile retrieves a specified devfile by fetching stacks from the OCI // registry and samples from the `samplesPath` given by server. Also retrieves index // schema from `indexPath` given by server. -func fetchDevfile(c *gin.Context, name string, version string) ([]byte, indexSchema.Schema) { +func fetchDevfile(c *gin.Context, name string, version string, params ServeDevfileWithVersionParams) ([]byte, indexSchema.Schema) { var index []indexSchema.Schema bytes, err := os.ReadFile(indexPath) if err != nil { @@ -657,24 +697,24 @@ func fetchDevfile(c *gin.Context, name string, version string) ([]byte, indexSch // minSchemaVersion and maxSchemaVersion will only be applied if looking for latest stack version if version == "latest" { - minSchemaVersion := c.Query("minSchemaVersion") - maxSchemaVersion := c.Query("maxSchemaVersion") + minSchemaVersion := params.MinSchemaVersion + maxSchemaVersion := params.MaxSchemaVersion // check if schema version filters are in valid format. // should include major and minor versions as well as an optional bugfix version. e.g. 2.1 or 2.1.0 - if minSchemaVersion != "" { - matched, err := regexp.MatchString(`^([2-9])\.([0-9]+)(\.[0-9]+)?$`, minSchemaVersion) + if util.StrPtrIsSet(minSchemaVersion) { + matched, err := regexp.MatchString(`^([2-9])\.([0-9]+)(\.[0-9]+)?$`, *minSchemaVersion) if !matched || err != nil { c.JSON(http.StatusBadRequest, gin.H{ - "status": fmt.Sprintf("minSchemaVersion %s is not valid, version format should be '+2.x' or '+2.x.x'. %v", minSchemaVersion, err), + "status": fmt.Sprintf("minSchemaVersion %s is not valid, version format should be '+2.x' or '+2.x.x'. %v", *minSchemaVersion, err), }) return []byte{}, indexSchema.Schema{} } } - if maxSchemaVersion != "" { - matched, err := regexp.MatchString(`^([2-9])\.([0-9]+)(\.[0-9]+)?$`, maxSchemaVersion) + if util.StrPtrIsSet(maxSchemaVersion) { + matched, err := regexp.MatchString(`^([2-9])\.([0-9]+)(\.[0-9]+)?$`, *maxSchemaVersion) if !matched || err != nil { c.JSON(http.StatusBadRequest, gin.H{ - "status": fmt.Sprintf("maxSchemaVersion %s is not valid, version format should be '+2.x' or '+2.x.x'. %v", maxSchemaVersion, err), + "status": fmt.Sprintf("maxSchemaVersion %s is not valid, version format should be '+2.x' or '+2.x.x'. %v", *maxSchemaVersion, err), }) return []byte{}, indexSchema.Schema{} } diff --git a/index/server/pkg/server/endpoint_test.go b/index/server/pkg/server/endpoint_test.go index ab083a09c..2466717a4 100644 --- a/index/server/pkg/server/endpoint_test.go +++ b/index/server/pkg/server/endpoint_test.go @@ -210,6 +210,11 @@ func notFoundManifest(c *gin.Context, tag string) { c.JSON(http.StatusNotFound, data) } +// testErrorHandler error handler for handling API errors during testing +func testErrorHandler(c *gin.Context, err error, statusCode int) { + c.JSON(statusCode, gin.H{"msg": err.Error()}) +} + // digestEntity generates sha256 digest of any entity type func digestEntity(e interface{}) (string, error) { bytes, err := json.Marshal(e) @@ -367,7 +372,10 @@ func TestServeHealthCheck(t *testing.T) { setupVars() - server := &Server{} + server := &ServerInterfaceWrapper{ + Handler: &Server{}, + ErrorHandler: testErrorHandler, + } w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) @@ -416,11 +424,16 @@ func TestServeDevfileIndexV1(t *testing.T) { gin.SetMode(gin.TestMode) - server := &Server{} + server := &ServerInterfaceWrapper{ + Handler: &Server{}, + ErrorHandler: testErrorHandler, + } w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - server.ServeDevfileIndexV1(c, ServeDevfileIndexV1Params{}) + c.Request = httptest.NewRequest(http.MethodGet, "/index", nil) + + server.ServeDevfileIndexV1(c) if gotStatusCode := w.Code; !reflect.DeepEqual(gotStatusCode, wantStatusCode) { t.Errorf("Did not get expected status code, Got: %v, Expected: %v", gotStatusCode, wantStatusCode) @@ -428,7 +441,7 @@ func TestServeDevfileIndexV1(t *testing.T) { } } -// TestServeDevfileIndexV1WithType tests '/index/:type' endpoint +// TestServeDevfileIndexV1WithType tests '/index/:indexType' endpoint func TestServeDevfileIndexV1WithType(t *testing.T) { setupVars() tests := []struct { @@ -439,33 +452,36 @@ func TestServeDevfileIndexV1WithType(t *testing.T) { { name: "GET /index/stack - Successful Response Test", params: gin.Params{ - gin.Param{Key: "type", Value: "stack"}, + gin.Param{Key: "indexType", Value: "stack"}, }, wantCode: http.StatusOK, }, { name: "GET /index/sample - Successful Response Test", params: gin.Params{ - gin.Param{Key: "type", Value: "sample"}, + gin.Param{Key: "indexType", Value: "sample"}, }, wantCode: http.StatusOK, }, { name: "GET /index/all - Successful Response Test", params: gin.Params{ - gin.Param{Key: "type", Value: "all"}, + gin.Param{Key: "indexType", Value: "all"}, }, wantCode: http.StatusOK, }, { name: "GET /index/notatype - Type Not Found Response Test", params: gin.Params{ - gin.Param{Key: "type", Value: "notatype"}, + gin.Param{Key: "indexType", Value: "notatype"}, }, wantCode: http.StatusNotFound, }, } - server := &Server{} + server := &ServerInterfaceWrapper{ + Handler: &Server{}, + ErrorHandler: testErrorHandler, + } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { @@ -474,11 +490,10 @@ func TestServeDevfileIndexV1WithType(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/index/%s", test.params.ByName("indexType")), nil) c.Params = append(c.Params, test.params...) - indexType, _ := c.Params.Get("type") - - server.ServeDevfileIndexV1WithType(c, indexType, ServeDevfileIndexV1WithTypeParams{}) + server.ServeDevfileIndexV1WithType(c) if gotStatusCode := w.Code; !reflect.DeepEqual(gotStatusCode, test.wantCode) { t.Errorf("Did not get expected status code, Got: %v, Expected: %v", gotStatusCode, test.wantCode) @@ -496,11 +511,16 @@ func TestServeDevfileIndexV2(t *testing.T) { gin.SetMode(gin.TestMode) - server := &Server{} + server := &ServerInterfaceWrapper{ + Handler: &Server{}, + ErrorHandler: testErrorHandler, + } w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - server.ServeDevfileIndexV2(c, ServeDevfileIndexV2Params{}) + c.Request = httptest.NewRequest(http.MethodGet, "/v2index", nil) + + server.ServeDevfileIndexV2(c) if gotStatusCode := w.Code; !reflect.DeepEqual(gotStatusCode, wantStatusCode) { t.Errorf("Did not get expected status code, Got: %v, Expected: %v", gotStatusCode, wantStatusCode) @@ -508,7 +528,7 @@ func TestServeDevfileIndexV2(t *testing.T) { } } -// TestServeDevfileIndexV2 tests '/v2index/:type' endpoint +// TestServeDevfileIndexV2 tests '/v2index/:indexType' endpoint func TestServeDevfileIndexV2WithType(t *testing.T) { setupVars() tests := []struct { @@ -520,28 +540,28 @@ func TestServeDevfileIndexV2WithType(t *testing.T) { { name: "GET /v2index/stack - Successful Response Test", params: gin.Params{ - gin.Param{Key: "type", Value: "stack"}, + gin.Param{Key: "indexType", Value: "stack"}, }, wantCode: http.StatusOK, }, { name: "GET /v2index/sample - Successful Response Test", params: gin.Params{ - gin.Param{Key: "type", Value: "sample"}, + gin.Param{Key: "indexType", Value: "sample"}, }, wantCode: http.StatusOK, }, { name: "GET /v2index/all - Successful Response Test", params: gin.Params{ - gin.Param{Key: "type", Value: "all"}, + gin.Param{Key: "indexType", Value: "all"}, }, wantCode: http.StatusOK, }, { - name: "GET /v2index/all?minSchemaVersion=2.1.0&maxSChemaVersion=2.2 - Successful Response Test", + name: "GET /v2index/all?minSchemaVersion=2.1.0&maxSchemaVersion=2.2 - Successful Response Test", params: gin.Params{ - gin.Param{Key: "type", Value: "all"}, + gin.Param{Key: "indexType", Value: "all"}, }, query: url.Values{ "minSchemaVersion": []string{"2.1.0"}, @@ -550,9 +570,20 @@ func TestServeDevfileIndexV2WithType(t *testing.T) { wantCode: http.StatusOK, }, { - name: "GET /v2index/all?minSchemaVersion=1.0&maxSChemaVersion=2.2 - Bad Request Response Test", + name: "GET /v2index/all?minVersion=2.1.0&maxVersion=2.2 - Successful Response Test", + params: gin.Params{ + gin.Param{Key: "indexType", Value: "all"}, + }, + query: url.Values{ + "minVersion": []string{"1.1.0"}, + "maxVersion": []string{"1.2"}, + }, + wantCode: http.StatusOK, + }, + { + name: "GET /v2index/all?minSchemaVersion=1.0&maxSchemaVersion=2.2 - Bad Request Response Test", params: gin.Params{ - gin.Param{Key: "type", Value: "all"}, + gin.Param{Key: "indexType", Value: "all"}, }, query: url.Values{ "minSchemaVersion": []string{"1.0"}, @@ -561,9 +592,9 @@ func TestServeDevfileIndexV2WithType(t *testing.T) { wantCode: http.StatusBadRequest, }, { - name: "GET /v2index/all?minSchemaVersion=2.0.0.0&maxSChemaVersion=2.2 - Bad Request Response Test", + name: "GET /v2index/all?minSchemaVersion=2.0.0.0&maxSchemaVersion=2.2 - Bad Request Response Test", params: gin.Params{ - gin.Param{Key: "type", Value: "all"}, + gin.Param{Key: "indexType", Value: "all"}, }, query: url.Values{ "minSchemaVersion": []string{"2.0.0.0"}, @@ -572,9 +603,9 @@ func TestServeDevfileIndexV2WithType(t *testing.T) { wantCode: http.StatusBadRequest, }, { - name: "GET /v2index/all?minSchemaVersion=2.0.0&maxSChemaVersion=test - Bad Request Response Test", + name: "GET /v2index/all?minSchemaVersion=2.0.0&maxSchemaVersion=test - Bad Request Response Test", params: gin.Params{ - gin.Param{Key: "type", Value: "all"}, + gin.Param{Key: "indexType", Value: "all"}, }, query: url.Values{ "minSchemaVersion": []string{"2.0.0"}, @@ -582,15 +613,40 @@ func TestServeDevfileIndexV2WithType(t *testing.T) { }, wantCode: http.StatusBadRequest, }, + { + name: "GET /v2index/all?minVersion=1.0.0.0&maxVersion=1.1 - Bad Request Response Test", + params: gin.Params{ + gin.Param{Key: "indexType", Value: "all"}, + }, + query: url.Values{ + "minVersion": []string{"1.0.0.0"}, + "maxVersion": []string{"1.1"}, + }, + wantCode: http.StatusBadRequest, + }, + { + name: "GET /v2index/all?minVersion=1.2.0&maxVersion=test - Bad Request Response Test", + params: gin.Params{ + gin.Param{Key: "indexType", Value: "all"}, + }, + query: url.Values{ + "minSchemaVersion": []string{"1.2.0"}, + "maxSchemaVersion": []string{"test"}, + }, + wantCode: http.StatusBadRequest, + }, { name: "GET /v2index/notatype - Type Not Found Response Test", params: gin.Params{ - gin.Param{Key: "type", Value: "notatype"}, + gin.Param{Key: "indexType", Value: "notatype"}, }, wantCode: http.StatusNotFound, }, } - server := &Server{} + server := &ServerInterfaceWrapper{ + Handler: &Server{}, + ErrorHandler: testErrorHandler, + } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { @@ -599,13 +655,11 @@ func TestServeDevfileIndexV2WithType(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest(http.MethodGet, "/v2index", nil) + c.Request = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/v2index/%s", test.params.ByName("indexType")), nil) c.Params = append(c.Params, test.params...) c.Request.URL.RawQuery = test.query.Encode() - indexType, _ := c.Params.Get("type") - - server.ServeDevfileIndexV2WithType(c, indexType, ServeDevfileIndexV2WithTypeParams{}) + server.ServeDevfileIndexV2WithType(c) if gotStatusCode := w.Code; !reflect.DeepEqual(gotStatusCode, test.wantCode) { t.Errorf("Did not get expected status code, Got: %v, Expected: %v", gotStatusCode, test.wantCode) @@ -615,7 +669,7 @@ func TestServeDevfileIndexV2WithType(t *testing.T) { } } -// TestServeDevfile tests '/devfiles/:name' endpoint +// TestServeDevfile tests '/devfiles/:stack' endpoint func TestServeDevfile(t *testing.T) { tests := []struct { name string @@ -627,7 +681,7 @@ func TestServeDevfile(t *testing.T) { { name: "GET /devfiles/java-maven - Fetch Java Maven Devfile", params: gin.Params{ - gin.Param{Key: "name", Value: "java-maven"}, + gin.Param{Key: "stack", Value: "java-maven"}, }, wantCode: http.StatusOK, wantSchemaVersion: "2.2.0", @@ -635,7 +689,7 @@ func TestServeDevfile(t *testing.T) { { name: "GET /devfiles/go - Fetch Go Devfile", params: gin.Params{ - gin.Param{Key: "name", Value: "go"}, + gin.Param{Key: "stack", Value: "go"}, }, wantCode: http.StatusOK, wantSchemaVersion: "2.0.0", @@ -643,7 +697,7 @@ func TestServeDevfile(t *testing.T) { { name: "GET /devfiles/not-exist - Fetch Non-Existent Devfile", params: gin.Params{ - gin.Param{Key: "name", Value: "not-exist"}, + gin.Param{Key: "stack", Value: "not-exist"}, }, wantCode: http.StatusNotFound, wantError: true, @@ -657,7 +711,10 @@ func TestServeDevfile(t *testing.T) { } defer closeServer() setupVars() - server := &Server{} + server := &ServerInterfaceWrapper{ + Handler: &Server{}, + ErrorHandler: testErrorHandler, + } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { @@ -666,10 +723,10 @@ func TestServeDevfile(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/devfile/%s", test.params.ByName("stack")), nil) c.Params = append(c.Params, test.params...) - name, _ := c.Params.Get("name") - server.ServeDevfile(c, name) + server.ServeDevfile(c) if gotStatusCode := w.Code; !reflect.DeepEqual(gotStatusCode, test.wantCode) { t.Errorf("Did not get expected status code, Got: %v, Expected: %v", gotStatusCode, test.wantCode) @@ -701,7 +758,7 @@ func TestServeDevfileWithVersion(t *testing.T) { { name: "GET /devfiles/go/default - Fetch Go Devfile With Default Stack Version", params: gin.Params{ - gin.Param{Key: "name", Value: "go"}, + gin.Param{Key: "stack", Value: "go"}, gin.Param{Key: "version", Value: "default"}, }, wantCode: http.StatusOK, @@ -710,7 +767,7 @@ func TestServeDevfileWithVersion(t *testing.T) { { name: "GET /devfiles/go/latest - Fetch Go Devfile With Latest Stack Version", params: gin.Params{ - gin.Param{Key: "name", Value: "go"}, + gin.Param{Key: "stack", Value: "go"}, gin.Param{Key: "version", Value: "latest"}, }, wantCode: http.StatusOK, @@ -719,7 +776,7 @@ func TestServeDevfileWithVersion(t *testing.T) { { name: "GET /devfiles/go/latest?minSchemaVersion=2.1 - Fetch Go Devfile With Latest Devfile 2.1.0 Stack Version", params: gin.Params{ - gin.Param{Key: "name", Value: "go"}, + gin.Param{Key: "stack", Value: "go"}, gin.Param{Key: "version", Value: "latest"}, }, query: url.Values{ @@ -731,7 +788,7 @@ func TestServeDevfileWithVersion(t *testing.T) { { name: "GET /devfiles/go/latest?maxSchemaVersion=2.0.0 - Fetch Go Devfile With Latest Devfile 2.0.0 Stack Version", params: gin.Params{ - gin.Param{Key: "name", Value: "go"}, + gin.Param{Key: "stack", Value: "go"}, gin.Param{Key: "version", Value: "latest"}, }, query: url.Values{ @@ -743,7 +800,7 @@ func TestServeDevfileWithVersion(t *testing.T) { { name: "GET /devfiles/go/latest?maxSchemaVersion=1.0 - Invalid Schema Version Fetch Go Devfile With Latest Stack Version", params: gin.Params{ - gin.Param{Key: "name", Value: "go"}, + gin.Param{Key: "stack", Value: "go"}, gin.Param{Key: "version", Value: "latest"}, }, query: url.Values{ @@ -755,7 +812,7 @@ func TestServeDevfileWithVersion(t *testing.T) { { name: "GET /devfiles/go/latest?minSchemaVersion=test - Invalid Schema Version Fetch Go Devfile With Latest Stack Version", params: gin.Params{ - gin.Param{Key: "name", Value: "go"}, + gin.Param{Key: "stack", Value: "go"}, gin.Param{Key: "version", Value: "latest"}, }, query: url.Values{ @@ -767,7 +824,7 @@ func TestServeDevfileWithVersion(t *testing.T) { { name: "GET /devfiles/go/1.2.0 - Fetch Go Devfile With Specific Version", params: gin.Params{ - gin.Param{Key: "name", Value: "go"}, + gin.Param{Key: "stack", Value: "go"}, gin.Param{Key: "version", Value: "1.2.0"}, }, wantCode: http.StatusOK, @@ -776,7 +833,7 @@ func TestServeDevfileWithVersion(t *testing.T) { { name: "GET /devfiles/not-exist/latest - Fetch Non-Existent Devfile With Latest Stack Version", params: gin.Params{ - gin.Param{Key: "name", Value: "not-exist"}, + gin.Param{Key: "stack", Value: "not-exist"}, gin.Param{Key: "version", Value: "latest"}, }, wantCode: http.StatusNotFound, @@ -785,7 +842,7 @@ func TestServeDevfileWithVersion(t *testing.T) { { name: "GET /devfiles/java-maven/not-exist - Fetch Java Maven Devfile With Non-Existent Stack Version", params: gin.Params{ - gin.Param{Key: "name", Value: "java-maven"}, + gin.Param{Key: "stack", Value: "java-maven"}, gin.Param{Key: "version", Value: "non-exist"}, }, wantCode: http.StatusNotFound, @@ -800,7 +857,10 @@ func TestServeDevfileWithVersion(t *testing.T) { } defer closeServer() setupVars() - server := &Server{} + server := &ServerInterfaceWrapper{ + Handler: &Server{}, + ErrorHandler: testErrorHandler, + } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { @@ -809,13 +869,15 @@ func TestServeDevfileWithVersion(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest(http.MethodGet, "/devfiles", nil) + c.Request = httptest.NewRequest( + http.MethodGet, + fmt.Sprintf("/devfile/%s/%s", test.params.ByName("stack"), test.params.ByName("version")), + nil, + ) c.Params = append(c.Params, test.params...) c.Request.URL.RawQuery = test.query.Encode() - name, _ := c.Params.Get("name") - version, _ := c.Params.Get("version") - server.ServeDevfileWithVersion(c, name, version) + server.ServeDevfileWithVersion(c) if gotStatusCode := w.Code; !reflect.DeepEqual(gotStatusCode, test.wantCode) { t.Errorf("Did not get expected status code, Got: %v, Expected: %v", gotStatusCode, test.wantCode) @@ -834,7 +896,7 @@ func TestServeDevfileWithVersion(t *testing.T) { } } -// TestServeDevfileStarterProject tests '/devfiles/:name/starter-projects/:starterProjectName' endpoint +// TestServeDevfileStarterProject tests '/devfiles/:name/starter-projects/:starterProject' endpoint func TestServeDevfileStarterProject(t *testing.T) { const wantContentType = starterProjectMediaType tests := []struct { @@ -846,48 +908,48 @@ func TestServeDevfileStarterProject(t *testing.T) { { name: "GET /devfiles/java-maven/starter-projects/springbootproject - Fetch Java Maven 'springbootproject' Starter Project", params: gin.Params{ - gin.Param{Key: "name", Value: "java-maven"}, - gin.Param{Key: "starterProjectName", Value: "springbootproject"}, + gin.Param{Key: "stack", Value: "java-maven"}, + gin.Param{Key: "starterProject", Value: "springbootproject"}, }, wantCode: http.StatusAccepted, }, { name: "GET /devfiles/go/starter-projects/go-starter - Fetch Go 'go-starter' Starter Project", params: gin.Params{ - gin.Param{Key: "name", Value: "go"}, - gin.Param{Key: "starterProjectName", Value: "go-starter"}, + gin.Param{Key: "stack", Value: "go"}, + gin.Param{Key: "starterProject", Value: "go-starter"}, }, wantCode: http.StatusAccepted, }, { name: "GET /devfiles/java-quarkus/starter-projects/community - Fetch Java Quarkus 'community' Starter Project", params: gin.Params{ - gin.Param{Key: "name", Value: "java-quarkus"}, - gin.Param{Key: "starterProjectName", Value: "community"}, + gin.Param{Key: "stack", Value: "java-quarkus"}, + gin.Param{Key: "starterProject", Value: "community"}, }, wantCode: http.StatusAccepted, }, { name: "GET /devfiles/java-wildfly/starter-projects/microprofile-config - Fetch Java Wildfly 'microprofile-config' Starter Project", params: gin.Params{ - gin.Param{Key: "name", Value: "java-wildfly"}, - gin.Param{Key: "starterProjectName", Value: "microprofile-config"}, + gin.Param{Key: "stack", Value: "java-wildfly"}, + gin.Param{Key: "starterProject", Value: "microprofile-config"}, }, wantCode: http.StatusAccepted, }, { name: "GET /devfiles/java-wildfly/starter-projects/microprofile-jwt - Fetch Java Wildfly 'microprofile-jwt' Starter Project", params: gin.Params{ - gin.Param{Key: "name", Value: "java-wildfly"}, - gin.Param{Key: "starterProjectName", Value: "microprofile-jwt"}, + gin.Param{Key: "stack", Value: "java-wildfly"}, + gin.Param{Key: "starterProject", Value: "microprofile-jwt"}, }, wantCode: http.StatusAccepted, }, { name: "GET /devfiles/not-exist/starter-projects/some - Fetch 'some' starter project from Non-Existent stack", params: gin.Params{ - gin.Param{Key: "name", Value: "not-exist"}, - gin.Param{Key: "starterProjectName", Value: "some"}, + gin.Param{Key: "stack", Value: "not-exist"}, + gin.Param{Key: "starterProject", Value: "some"}, }, wantCode: http.StatusNotFound, wantError: true, @@ -895,8 +957,8 @@ func TestServeDevfileStarterProject(t *testing.T) { { name: "GET /devfiles/java-maven/starter-projects/not-exist - Fetch Non-Existent starter project from Java Maven stack", params: gin.Params{ - gin.Param{Key: "name", Value: "java-maven"}, - gin.Param{Key: "starterProjectName", Value: "not-exist"}, + gin.Param{Key: "stack", Value: "java-maven"}, + gin.Param{Key: "starterProject", Value: "not-exist"}, }, wantCode: http.StatusNotFound, wantError: true, @@ -910,7 +972,10 @@ func TestServeDevfileStarterProject(t *testing.T) { } defer closeServer() setupVars() - server := &Server{} + server := &ServerInterfaceWrapper{ + Handler: &Server{}, + ErrorHandler: testErrorHandler, + } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { @@ -919,12 +984,14 @@ func TestServeDevfileStarterProject(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) + c.Request = httptest.NewRequest( + http.MethodGet, + fmt.Sprintf("/devfile/%s/starter-projects/%s", test.params.ByName("stack"), test.params.ByName("starterProject")), + nil, + ) c.Params = append(c.Params, test.params...) - name, _ := c.Params.Get("name") - starterProject, _ := c.Params.Get("starterProjectName") - - server.ServeDevfileStarterProject(c, name, starterProject) + server.ServeDevfileStarterProject(c) if gotStatusCode := w.Code; !reflect.DeepEqual(gotStatusCode, test.wantCode) { t.Errorf("Did not get expected status code, Got: %v, Expected: %v", gotStatusCode, test.wantCode) @@ -938,7 +1005,7 @@ func TestServeDevfileStarterProject(t *testing.T) { } } -// TestServeDevfileStarterProjectWithVersion tests '/devfiles/:name/:version/starter-projects/:starterProjectName' endpoint +// TestServeDevfileStarterProjectWithVersion tests '/devfiles/:name/:version/starter-projects/:starterProject' endpoint func TestServeDevfileStarterProjectWithVersion(t *testing.T) { const wantContentType = starterProjectMediaType tests := []struct { @@ -950,27 +1017,27 @@ func TestServeDevfileStarterProjectWithVersion(t *testing.T) { { name: "GET /devfiles/go/default/starter-projects/go-starter - Fetch Go 'go-starter' Starter Project With Default Version", params: gin.Params{ - gin.Param{Key: "name", Value: "go"}, + gin.Param{Key: "stack", Value: "go"}, gin.Param{Key: "version", Value: "default"}, - gin.Param{Key: "starterProjectName", Value: "go-starter"}, + gin.Param{Key: "starterProject", Value: "go-starter"}, }, wantCode: http.StatusAccepted, }, { name: "GET /devfiles/go/latest/starter-projects/go-starter - Fetch Go 'go-starter' Starter Project With Latest Version", params: gin.Params{ - gin.Param{Key: "name", Value: "go"}, + gin.Param{Key: "stack", Value: "go"}, gin.Param{Key: "version", Value: "latest"}, - gin.Param{Key: "starterProjectName", Value: "go-starter"}, + gin.Param{Key: "starterProject", Value: "go-starter"}, }, wantCode: http.StatusAccepted, }, { name: "GET /devfiles/go/1.2.0/starter-projects/go-starter - Fetch Go 'go-starter' Starter Project With Specific Version", params: gin.Params{ - gin.Param{Key: "name", Value: "go"}, + gin.Param{Key: "stack", Value: "go"}, gin.Param{Key: "version", Value: "1.2.0"}, - gin.Param{Key: "starterProjectName", Value: "go-starter"}, + gin.Param{Key: "starterProject", Value: "go-starter"}, }, wantCode: http.StatusAccepted, }, @@ -978,9 +1045,9 @@ func TestServeDevfileStarterProjectWithVersion(t *testing.T) { name: "GET /devfiles/not-exist/latest/starter-projects/some - " + "Fetch 'some' starter project from Non-Existent stack With Latest Version", params: gin.Params{ - gin.Param{Key: "name", Value: "not-exist"}, + gin.Param{Key: "stack", Value: "not-exist"}, gin.Param{Key: "version", Value: "latest"}, - gin.Param{Key: "starterProjectName", Value: "some"}, + gin.Param{Key: "starterProject", Value: "some"}, }, wantCode: http.StatusNotFound, wantError: true, @@ -989,9 +1056,9 @@ func TestServeDevfileStarterProjectWithVersion(t *testing.T) { name: "GET /devfiles/java-maven/latest/starter-projects/not-exist - " + "Fetch Non-Existent starter project from Java Maven stack With Latest Version", params: gin.Params{ - gin.Param{Key: "name", Value: "java-maven"}, + gin.Param{Key: "stack", Value: "java-maven"}, gin.Param{Key: "version", Value: "latest"}, - gin.Param{Key: "starterProjectName", Value: "not-exist"}, + gin.Param{Key: "starterProject", Value: "not-exist"}, }, wantCode: http.StatusNotFound, wantError: true, @@ -1000,9 +1067,9 @@ func TestServeDevfileStarterProjectWithVersion(t *testing.T) { name: "GET /devfiles/java-maven/not-exist/starter-projects/springbootproject - " + "Fetch Java Maven 'springbootproject' Starter Project With Non-Existent Version", params: gin.Params{ - gin.Param{Key: "name", Value: "java-maven"}, + gin.Param{Key: "stack", Value: "java-maven"}, gin.Param{Key: "version", Value: "non-exist"}, - gin.Param{Key: "starterProjectName", Value: "springbootproject"}, + gin.Param{Key: "starterProject", Value: "springbootproject"}, }, wantCode: http.StatusNotFound, wantError: true, @@ -1016,7 +1083,10 @@ func TestServeDevfileStarterProjectWithVersion(t *testing.T) { } defer closeServer() setupVars() - server := &Server{} + server := &ServerInterfaceWrapper{ + Handler: &Server{}, + ErrorHandler: testErrorHandler, + } for _, test := range tests { t.Run(test.name, func(tt *testing.T) { @@ -1025,13 +1095,18 @@ func TestServeDevfileStarterProjectWithVersion(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) + c.Request = httptest.NewRequest( + http.MethodGet, + fmt.Sprintf("/devfile/%s/%s/starter-projects/%s", + test.params.ByName("stack"), + test.params.ByName("version"), + test.params.ByName("starterProject"), + ), + nil, + ) c.Params = append(c.Params, test.params...) - name, _ := c.Params.Get("name") - version, _ := c.Params.Get("version") - starterProject, _ := c.Params.Get("starterProjectName") - - server.ServeDevfileStarterProjectWithVersion(c, name, version, starterProject) + server.ServeDevfileStarterProjectWithVersion(c) if gotStatusCode := w.Code; !reflect.DeepEqual(gotStatusCode, test.wantCode) { t.Errorf("Did not get expected status code, Got: %v, Expected: %v", gotStatusCode, test.wantCode) @@ -1321,19 +1396,19 @@ func TestDevfileIndexV1MethodNotAllowed(t *testing.T) { { name: "POST /index/{indexType} - Successful Response Test", handler: func(c *gin.Context) { - server.PostDevfileIndexV1WithType(c, "stack", PostDevfileIndexV1WithTypeParams{}) + server.PostDevfileIndexV1WithType(c, "stack") }, wantCode: http.StatusMethodNotAllowed, }, { name: "PUT /index/{indexType} - Successful Response Test", - handler: func(c *gin.Context) { server.PutDevfileIndexV1WithType(c, "stack", PutDevfileIndexV1WithTypeParams{}) }, + handler: func(c *gin.Context) { server.PutDevfileIndexV1WithType(c, "stack") }, wantCode: http.StatusMethodNotAllowed, }, { name: "DELETE /index/{indexType} - Successful Response Test", handler: func(c *gin.Context) { - server.DeleteDevfileIndexV1WithType(c, "stack", DeleteDevfileIndexV1WithTypeParams{}) + server.DeleteDevfileIndexV1WithType(c, "stack") }, wantCode: http.StatusMethodNotAllowed, }, @@ -1384,19 +1459,19 @@ func TestDevfileIndexV2MethodNotAllowed(t *testing.T) { { name: "POST /v2index/{indexType} - Successful Response Test", handler: func(c *gin.Context) { - server.PostDevfileIndexV2WithType(c, "stack", PostDevfileIndexV2WithTypeParams{}) + server.PostDevfileIndexV2WithType(c, "stack") }, wantCode: http.StatusMethodNotAllowed, }, { name: "PUT /v2index/{indexType} - Successful Response Test", - handler: func(c *gin.Context) { server.PutDevfileIndexV2WithType(c, "stack", PutDevfileIndexV2WithTypeParams{}) }, + handler: func(c *gin.Context) { server.PutDevfileIndexV2WithType(c, "stack") }, wantCode: http.StatusMethodNotAllowed, }, { name: "DELETE /v2index/{indexType} - Successful Response Test", handler: func(c *gin.Context) { - server.DeleteDevfileIndexV2WithType(c, "stack", DeleteDevfileIndexV2WithTypeParams{}) + server.DeleteDevfileIndexV2WithType(c, "stack") }, wantCode: http.StatusMethodNotAllowed, }, diff --git a/index/server/pkg/server/filter.go b/index/server/pkg/server/filter.go new file mode 100644 index 000000000..b25f23d39 --- /dev/null +++ b/index/server/pkg/server/filter.go @@ -0,0 +1,61 @@ +// +// Copyright Red Hat +// +// 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 server + +import ( + "fmt" + + indexSchema "github.com/devfile/registry-support/index/generator/schema" + "github.com/devfile/registry-support/index/server/pkg/util" +) + +func filterFieldbyParam(index []indexSchema.Schema, wantV1Index bool, paramName string, paramValue any) util.FilterResult { + switch typedValue := paramValue.(type) { + case string: + return util.FilterDevfileStrField(index, paramName, typedValue, wantV1Index) + default: + return util.FilterDevfileStrField(index, paramName, fmt.Sprintf("%v", typedValue), wantV1Index) + } +} + +func filterFieldsByParams(index []indexSchema.Schema, wantV1Index bool, params IndexParams) ([]indexSchema.Schema, error) { + paramsMap := util.StructToMap(params) + results := []*util.FilterResult{} + var andResult util.FilterResult + + if len(paramsMap) == 0 { + return index, nil + } + + for paramName, paramValue := range paramsMap { + if util.IsFieldParameter(paramName) { + result := filterFieldbyParam(index, wantV1Index, paramName, paramValue) + results = append(results, &result) + } else if util.IsArrayParameter(paramName) { + typedValues := paramValue.([]string) + result := util.FilterDevfileStrArrayField(index, paramName, typedValues, wantV1Index) + results = append(results, &result) + } + } + + if len(results) == 0 { + return index, nil + } + + andResult = util.AndFilter(results...) + + return andResult.Index, andResult.Error +} diff --git a/index/server/pkg/server/filter_test.go b/index/server/pkg/server/filter_test.go new file mode 100644 index 000000000..8bc3ee37c --- /dev/null +++ b/index/server/pkg/server/filter_test.go @@ -0,0 +1,859 @@ +// +// Copyright Red Hat +// +// 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 server + +import ( + "reflect" + "sort" + "strings" + "testing" + + indexSchema "github.com/devfile/registry-support/index/generator/schema" + "github.com/devfile/registry-support/index/server/pkg/util" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" +) + +var testIndexSchema = []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + Description: "A python stack.", + Attributes: map[string]apiext.JSON{ + "attributeA": {}, + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + }, + Architectures: []string{"amd64", "arm64"}, + Tags: []string{"Python", "Django", "Flask"}, + Versions: []indexSchema.Version{ + { + Version: "v1.0.0", + Description: "A python stack.", + Architectures: []string{"amd64"}, + Tags: []string{"Python"}, + Resources: []string{"devfile.yaml"}, + StarterProjects: []string{"starterA"}, + }, + { + Version: "v1.1.0", + Description: "A python stack.", + Architectures: []string{"amd64", "arm64"}, + Tags: []string{"Python", "Django"}, + Resources: []string{"devfile.yaml"}, + StarterProjects: []string{"starterA", "starterB"}, + Links: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Version: "v2.0.0", + Description: "A python stack.", + Icon: "devfileA.png", + Default: true, + Tags: []string{"Python", "Flask"}, + Resources: []string{"devfile.yaml", "archive.tar"}, + StarterProjects: []string{"starterA", "starterB"}, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + { + Name: "devfileB", + DisplayName: "Python", + Description: "A python sample.", + Icon: "devfileB.ico", + Attributes: map[string]apiext.JSON{ + "attributeA": {}, + "attributeC": {}, + "attributeD": {}, + "attributeE": {}, + }, + Architectures: []string{"amd64"}, + Tags: []string{"Python"}, + Resources: []string{"devfile.yaml"}, + StarterProjects: []string{"starterB"}, + Links: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Name: "devfileC", + DisplayName: "Flask", + Icon: "devfileC.jpg", + Attributes: map[string]apiext.JSON{ + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + "attributeE": {}, + }, + Resources: []string{"devfile.yaml", "archive.tar"}, + Links: map[string]string{ + "linkA": "git.test.com", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + Versions: []indexSchema.Version{ + { + Version: "v1.0.0", + Icon: "devfileC.png", + Resources: []string{"devfile.yaml", "archive.tar"}, + StarterProjects: []string{"starterA", "starterC"}, + Default: true, + Links: map[string]string{ + "linkA": "git.test.com", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Version: "v2.0.0", + Icon: "devfileC.ico", + StarterProjects: []string{"starterA", "starterB"}, + Links: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + { + Name: "devfileD", + }, +} + +func TestFilterFieldbyParam(t *testing.T) { + tests := []struct { + name string + index []indexSchema.Schema + v1Index bool + paramName string + paramValue any + wantIndex []indexSchema.Schema + }{ + { + name: "Case 1: string parameter", + index: testIndexSchema, + v1Index: true, + paramName: util.PARAM_ICON, + paramValue: ".jpg", + wantIndex: []indexSchema.Schema{ + { + Name: "devfileC", + DisplayName: "Flask", + Icon: "devfileC.jpg", + Attributes: map[string]apiext.JSON{ + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + "attributeE": {}, + }, + Resources: []string{"devfile.yaml", "archive.tar"}, + Links: map[string]string{ + "linkA": "git.test.com", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + Versions: []indexSchema.Version{ + { + Version: "v1.0.0", + Icon: "devfileC.png", + Resources: []string{"devfile.yaml", "archive.tar"}, + StarterProjects: []string{"starterA", "starterC"}, + Default: true, + Links: map[string]string{ + "linkA": "git.test.com", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Version: "v2.0.0", + Icon: "devfileC.ico", + StarterProjects: []string{"starterA", "starterB"}, + Links: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + }, + }, + { + name: "Case 2: string parameter v2", + index: testIndexSchema, + paramName: util.PARAM_ICON, + paramValue: ".png", + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + Description: "A python stack.", + Attributes: map[string]apiext.JSON{ + "attributeA": {}, + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + }, + Architectures: []string{"amd64", "arm64"}, + Tags: []string{"Python", "Django", "Flask"}, + Versions: []indexSchema.Version{ + { + Version: "v2.0.0", + Description: "A python stack.", + Icon: "devfileA.png", + Default: true, + Tags: []string{"Python", "Flask"}, + Resources: []string{"devfile.yaml", "archive.tar"}, + StarterProjects: []string{"starterA", "starterB"}, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + { + Name: "devfileC", + DisplayName: "Flask", + Icon: "devfileC.jpg", + Attributes: map[string]apiext.JSON{ + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + "attributeE": {}, + }, + Resources: []string{"devfile.yaml", "archive.tar"}, + Links: map[string]string{ + "linkA": "git.test.com", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + Versions: []indexSchema.Version{ + { + Version: "v1.0.0", + Icon: "devfileC.png", + Resources: []string{"devfile.yaml", "archive.tar"}, + StarterProjects: []string{"starterA", "starterC"}, + Default: true, + Links: map[string]string{ + "linkA": "git.test.com", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + }, + }, + }, + }, + }, + { + name: "Case 3: Non-string parameter", + index: testIndexSchema, + paramName: util.PARAM_DEFAULT, + paramValue: true, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + Description: "A python stack.", + Attributes: map[string]apiext.JSON{ + "attributeA": {}, + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + }, + Architectures: []string{"amd64", "arm64"}, + Tags: []string{"Python", "Django", "Flask"}, + Versions: []indexSchema.Version{ + { + Version: "v2.0.0", + Description: "A python stack.", + Icon: "devfileA.png", + Default: true, + Tags: []string{"Python", "Flask"}, + Resources: []string{"devfile.yaml", "archive.tar"}, + StarterProjects: []string{"starterA", "starterB"}, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + { + Name: "devfileC", + DisplayName: "Flask", + Icon: "devfileC.jpg", + Attributes: map[string]apiext.JSON{ + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + "attributeE": {}, + }, + Resources: []string{"devfile.yaml", "archive.tar"}, + Links: map[string]string{ + "linkA": "git.test.com", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + Versions: []indexSchema.Version{ + { + Version: "v1.0.0", + Icon: "devfileC.png", + Resources: []string{"devfile.yaml", "archive.tar"}, + StarterProjects: []string{"starterA", "starterC"}, + Default: true, + Links: map[string]string{ + "linkA": "git.test.com", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + gotResult := filterFieldbyParam(test.index, test.v1Index, test.paramName, test.paramValue) + + if gotResult.Error != nil { + t.Errorf("unexpected error: %v", gotResult.Error) + return + } + + sort.Slice(gotResult.Index, func(i, j int) bool { + return gotResult.Index[i].Name < gotResult.Index[j].Name + }) + sort.Slice(test.wantIndex, func(i, j int) bool { + return test.wantIndex[i].Name < test.wantIndex[j].Name + }) + + if !reflect.DeepEqual(gotResult.Index, test.wantIndex) { + t.Errorf("expected: %v, got: %v", test.wantIndex, gotResult.Index) + } + }) + } +} + +func TestFilterFieldsByParams(t *testing.T) { + tests := []struct { + name string + index []indexSchema.Schema + v1Index bool + params IndexParams + wantIndex []indexSchema.Schema + wantErr bool + wantErrStr string + }{ + { + name: "Case 1: Single filter", + index: testIndexSchema, + params: IndexParams{ + Arch: &[]string{"arm64"}, + }, + v1Index: true, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + Description: "A python stack.", + Attributes: map[string]apiext.JSON{ + "attributeA": {}, + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + }, + Architectures: []string{"amd64", "arm64"}, + Tags: []string{"Python", "Django", "Flask"}, + Versions: []indexSchema.Version{ + { + Version: "v1.0.0", + Description: "A python stack.", + Architectures: []string{"amd64"}, + Tags: []string{"Python"}, + Resources: []string{"devfile.yaml"}, + StarterProjects: []string{"starterA"}, + }, + { + Version: "v1.1.0", + Description: "A python stack.", + Architectures: []string{"amd64", "arm64"}, + Tags: []string{"Python", "Django"}, + Resources: []string{"devfile.yaml"}, + StarterProjects: []string{"starterA", "starterB"}, + Links: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Version: "v2.0.0", + Description: "A python stack.", + Icon: "devfileA.png", + Default: true, + Tags: []string{"Python", "Flask"}, + Resources: []string{"devfile.yaml", "archive.tar"}, + StarterProjects: []string{"starterA", "starterB"}, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + { + Name: "devfileC", + DisplayName: "Flask", + Icon: "devfileC.jpg", + Attributes: map[string]apiext.JSON{ + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + "attributeE": {}, + }, + Resources: []string{"devfile.yaml", "archive.tar"}, + Links: map[string]string{ + "linkA": "git.test.com", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + Versions: []indexSchema.Version{ + { + Version: "v1.0.0", + Icon: "devfileC.png", + Resources: []string{"devfile.yaml", "archive.tar"}, + StarterProjects: []string{"starterA", "starterC"}, + Default: true, + Links: map[string]string{ + "linkA": "git.test.com", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Version: "v2.0.0", + Icon: "devfileC.ico", + StarterProjects: []string{"starterA", "starterB"}, + Links: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + { + Name: "devfileD", + }, + }, + }, + { + name: "Case 2: Single filter v2", + index: testIndexSchema, + params: IndexParams{ + Arch: &[]string{"arm64"}, + }, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + Description: "A python stack.", + Attributes: map[string]apiext.JSON{ + "attributeA": {}, + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + }, + Architectures: []string{"amd64", "arm64"}, + Tags: []string{"Python", "Django", "Flask"}, + Versions: []indexSchema.Version{ + { + Version: "v1.1.0", + Description: "A python stack.", + Architectures: []string{"amd64", "arm64"}, + Tags: []string{"Python", "Django"}, + Resources: []string{"devfile.yaml"}, + StarterProjects: []string{"starterA", "starterB"}, + Links: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Version: "v2.0.0", + Description: "A python stack.", + Icon: "devfileA.png", + Default: true, + Tags: []string{"Python", "Flask"}, + Resources: []string{"devfile.yaml", "archive.tar"}, + StarterProjects: []string{"starterA", "starterB"}, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + { + Name: "devfileC", + DisplayName: "Flask", + Icon: "devfileC.jpg", + Attributes: map[string]apiext.JSON{ + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + "attributeE": {}, + }, + Resources: []string{"devfile.yaml", "archive.tar"}, + Links: map[string]string{ + "linkA": "git.test.com", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + Versions: []indexSchema.Version{ + { + Version: "v1.0.0", + Icon: "devfileC.png", + Resources: []string{"devfile.yaml", "archive.tar"}, + StarterProjects: []string{"starterA", "starterC"}, + Default: true, + Links: map[string]string{ + "linkA": "git.test.com", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Version: "v2.0.0", + Icon: "devfileC.ico", + StarterProjects: []string{"starterA", "starterB"}, + Links: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + { + Name: "devfileD", + }, + }, + }, + { + name: "Case 3: Multi filter", + index: testIndexSchema, + params: IndexParams{ + Arch: &[]string{"arm64"}, + CommandGroups: &[]string{ + string(indexSchema.RunCommandGroupKind), + }, + }, + v1Index: true, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileC", + DisplayName: "Flask", + Icon: "devfileC.jpg", + Attributes: map[string]apiext.JSON{ + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + "attributeE": {}, + }, + Resources: []string{"devfile.yaml", "archive.tar"}, + Links: map[string]string{ + "linkA": "git.test.com", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + Versions: []indexSchema.Version{ + { + Version: "v1.0.0", + Icon: "devfileC.png", + Resources: []string{"devfile.yaml", "archive.tar"}, + StarterProjects: []string{"starterA", "starterC"}, + Default: true, + Links: map[string]string{ + "linkA": "git.test.com", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Version: "v2.0.0", + Icon: "devfileC.ico", + StarterProjects: []string{"starterA", "starterB"}, + Links: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + }, + }, + { + name: "Case 4: Multi filter v2", + index: testIndexSchema, + params: IndexParams{ + Arch: &[]string{"arm64"}, + CommandGroups: &[]string{ + string(indexSchema.BuildCommandGroupKind), + string(indexSchema.RunCommandGroupKind), + }, + }, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + Description: "A python stack.", + Attributes: map[string]apiext.JSON{ + "attributeA": {}, + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + }, + Architectures: []string{"amd64", "arm64"}, + Tags: []string{"Python", "Django", "Flask"}, + Versions: []indexSchema.Version{ + { + Version: "v1.1.0", + Description: "A python stack.", + Architectures: []string{"amd64", "arm64"}, + Tags: []string{"Python", "Django"}, + Resources: []string{"devfile.yaml"}, + StarterProjects: []string{"starterA", "starterB"}, + Links: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Version: "v2.0.0", + Description: "A python stack.", + Icon: "devfileA.png", + Default: true, + Tags: []string{"Python", "Flask"}, + Resources: []string{"devfile.yaml", "archive.tar"}, + StarterProjects: []string{"starterA", "starterB"}, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + { + Name: "devfileC", + DisplayName: "Flask", + Icon: "devfileC.jpg", + Attributes: map[string]apiext.JSON{ + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + "attributeE": {}, + }, + Resources: []string{"devfile.yaml", "archive.tar"}, + Links: map[string]string{ + "linkA": "git.test.com", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + Versions: []indexSchema.Version{ + { + Version: "v2.0.0", + Icon: "devfileC.ico", + StarterProjects: []string{"starterA", "starterB"}, + Links: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + }, + }, + { + name: "Case 5: Blank result", + index: testIndexSchema, + params: IndexParams{ + Arch: &[]string{"arm64"}, + AttributeNames: &[]string{"attributeE"}, + CommandGroups: &[]string{ + string(indexSchema.DeployCommandGroupKind), + string(indexSchema.RunCommandGroupKind), + }, + }, + wantIndex: []indexSchema.Schema{}, + }, + { + name: "Case 6: Blank filter", + index: testIndexSchema, + params: IndexParams{}, + wantIndex: testIndexSchema, + }, + { + name: "Case 7: Error", + index: testIndexSchema, + params: IndexParams{}, + wantErr: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + gotIndex, gotErr := filterFieldsByParams(test.index, test.v1Index, test.params) + + // sorting is not consistent in output + sort.Slice(gotIndex, func(i, j int) bool { + return gotIndex[i].Name < gotIndex[j].Name + }) + sort.Slice(test.wantIndex, func(i, j int) bool { + return test.wantIndex[i].Name < test.wantIndex[j].Name + }) + + if test.wantErr && gotErr != nil && !strings.Contains(gotErr.Error(), test.wantErrStr) { + t.Errorf("unexpected error %v", gotErr) + } else if !test.wantErr && gotErr != nil { + t.Errorf("unexpected error %v", gotErr) + } else if !test.wantErr && !reflect.DeepEqual(gotIndex, test.wantIndex) { + t.Errorf("expected: %v, got: %v", test.wantIndex, gotIndex) + } + }) + } +} diff --git a/index/server/pkg/server/types.gen.go b/index/server/pkg/server/types.gen.go index a3753602d..945257b9b 100644 --- a/index/server/pkg/server/types.gen.go +++ b/index/server/pkg/server/types.gen.go @@ -23,30 +23,264 @@ import ( // Architectures Optional list of processor architectures that the devfile supports, empty list suggests that the devfile can be used on any architecture type Architectures = []string +// AttributeNames List of the YAML free-form attribute names +type AttributeNames = []string + +// CommandGroups List of command groups defined in devfile +type CommandGroups = []string + +// Default Flag for default devfile registry entry version +type Default = bool + +// Deprecated Flag for deprecated devfile registry entry +type Deprecated = bool + +// Description Description of devfile registry entry +type Description = string + // Devfile Describes the structure of a cloud-native devworkspace and development environment. type Devfile = v1alpha2.Devfile -// Icon Optional devfile icon, can be a URI or a relative path in the project +// DisplayName User readable name of devfile registry entry +type DisplayName = string + +// GitRemoteName Git repository remote name +type GitRemoteName = string + +// GitRemoteNames List of git repository remote names +type GitRemoteNames = []GitRemoteName + +// GitRemotes List of git repository remote urls +type GitRemotes = []Url + +// GitRevision Branch, tag, or commit reference +type GitRevision = string + +// GitSubDir Subdirectory of git repository to use as reference +type GitSubDir = string + +// Icon Optional devfile icon encoding type type Icon = string +// IconUri Optional devfile icon uri, can be a URL or a relative path in the project +type IconUri = string + // IndexParams IndexParams defines parameters for index endpoints. type IndexParams struct { // Arch Optional list of processor architectures that the devfile supports, empty list suggests that the devfile can be used on any architecture Arch *Architectures `json:"arch,omitempty"` - // Icon Optional devfile icon, can be a URI or a relative path in the project + // AttributeNames List of the YAML free-form attribute names + AttributeNames *AttributeNames `json:"attributeNames,omitempty"` + + // CommandGroups List of command groups defined in devfile + CommandGroups *CommandGroups `json:"commandGroups,omitempty"` + + // Default Flag for default devfile registry entry version + Default *Default `json:"default,omitempty"` + + // Deprecated Flag for deprecated devfile registry entry + Deprecated *Deprecated `json:"deprecated,omitempty"` + + // Description Description of devfile registry entry + Description *Description `json:"description,omitempty"` + + // DisplayName User readable name of devfile registry entry + DisplayName *DisplayName `json:"displayName,omitempty"` + + // GitRemoteName Git repository remote name + GitRemoteName *GitRemoteName `json:"gitRemoteName,omitempty"` + + // GitRemoteNames List of git repository remote names + GitRemoteNames *GitRemoteNames `json:"gitRemoteNames,omitempty"` + + // GitRemotes List of git repository remote urls + GitRemotes *GitRemotes `json:"gitRemotes,omitempty"` + + // GitRevision Branch, tag, or commit reference + GitRevision *GitRevision `json:"gitRevision,omitempty"` + + // GitSubDir Subdirectory of git repository to use as reference + GitSubDir *GitSubDir `json:"gitSubDir,omitempty"` + + // GitUrl Url field type + GitUrl *Url `json:"gitUrl,omitempty"` + + // Icon Optional devfile icon encoding type Icon *Icon `json:"icon,omitempty"` + + // IconUri Optional devfile icon uri, can be a URL or a relative path in the project + IconUri *IconUri `json:"iconUri,omitempty"` + + // Language Programming language of the devfile workspace + Language *Language `json:"language,omitempty"` + + // LinkNames Names of devfile links + LinkNames *LinkNames `json:"linkNames,omitempty"` + + // Links List of devfile links + Links *Links `json:"links,omitempty"` + + // MaxSchemaVersion Devfile schema version number + MaxSchemaVersion *SchemaVersion `json:"maxSchemaVersion,omitempty"` + + // MaxVersion Devfile registry entry version number + MaxVersion *Version `json:"maxVersion,omitempty"` + + // MinSchemaVersion Devfile schema version number + MinSchemaVersion *SchemaVersion `json:"minSchemaVersion,omitempty"` + + // MinVersion Devfile registry entry version number + MinVersion *Version `json:"minVersion,omitempty"` + + // Name Name of devfile registry entry + Name *Name `json:"name,omitempty"` + + // ProjectType Type of project the devfile supports + ProjectType *ProjectType `json:"projectType,omitempty"` + + // Provider Name of provider of the devfile registry entry + Provider *Provider `json:"provider,omitempty"` + + // Resources List of file resources for the devfile + Resources *Resources `json:"resources,omitempty"` + + // StarterProjects List of starter project names + StarterProjects *StarterProjects `json:"starterProjects,omitempty"` + + // SupportUrl Url field type + SupportUrl *Url `json:"supportUrl,omitempty"` + + // Tags List of devfile subject tags + Tags *Tags `json:"tags,omitempty"` } // IndexSchema The index file schema type IndexSchema = schema.Schema +// Language Programming language of the devfile workspace +type Language = string + +// LinkNames Names of devfile links +type LinkNames = []string + +// Links List of devfile links +type Links = []Url + +// Name Name of devfile registry entry +type Name = string + +// ProjectType Type of project the devfile supports +type ProjectType = string + +// Provider Name of provider of the devfile registry entry +type Provider = string + +// Resources List of file resources for the devfile +type Resources = []string + +// SchemaVersion Devfile schema version number +type SchemaVersion = string + +// StarterProjects List of starter project names +type StarterProjects = []string + +// Tags List of devfile subject tags +type Tags = []string + +// Url Url field type +type Url = string + +// Version Devfile registry entry version number +type Version = string + // ArchParam Optional list of processor architectures that the devfile supports, empty list suggests that the devfile can be used on any architecture type ArchParam = Architectures -// IconParam Optional devfile icon, can be a URI or a relative path in the project +// AttributeNamesParam defines model for attributeNamesParam. +type AttributeNamesParam = []string + +// CommandGroupsParam List of command groups defined in devfile +type CommandGroupsParam = CommandGroups + +// DefaultParam Flag for default devfile registry entry version +type DefaultParam = Default + +// DeprecatedParam Flag for deprecated devfile registry entry +type DeprecatedParam = Deprecated + +// DescriptionParam Description of devfile registry entry +type DescriptionParam = Description + +// DisplayNameParam User readable name of devfile registry entry +type DisplayNameParam = DisplayName + +// GitRemoteNameParam Git repository remote name +type GitRemoteNameParam = GitRemoteName + +// GitRemoteNamesParam List of git repository remote names +type GitRemoteNamesParam = GitRemoteNames + +// GitRemotesParam List of git repository remote urls +type GitRemotesParam = GitRemotes + +// GitRevisionParam Branch, tag, or commit reference +type GitRevisionParam = GitRevision + +// GitSubDirParam Subdirectory of git repository to use as reference +type GitSubDirParam = GitSubDir + +// GitUrlParam Url field type +type GitUrlParam = Url + +// IconParam Optional devfile icon encoding type type IconParam = Icon +// IconUriParam Optional devfile icon uri, can be a URL or a relative path in the project +type IconUriParam = IconUri + +// LanguageParam Programming language of the devfile workspace +type LanguageParam = Language + +// LinkNamesParam Names of devfile links +type LinkNamesParam = LinkNames + +// LinksParam List of devfile links +type LinksParam = Links + +// MaxSchemaVersionParam Devfile schema version number +type MaxSchemaVersionParam = SchemaVersion + +// MaxVersionParam Devfile registry entry version number +type MaxVersionParam = Version + +// MinSchemaVersionParam Devfile schema version number +type MinSchemaVersionParam = SchemaVersion + +// MinVersionParam Devfile registry entry version number +type MinVersionParam = Version + +// NameParam Name of devfile registry entry +type NameParam = Name + +// ProjectTypeParam Type of project the devfile supports +type ProjectTypeParam = ProjectType + +// ProviderParam Name of provider of the devfile registry entry +type ProviderParam = Provider + +// ResourcesParam List of file resources for the devfile +type ResourcesParam = Resources + +// StarterProjectsParam List of starter project names +type StarterProjectsParam = StarterProjects + +// SupportUrlParam Url field type +type SupportUrlParam = Url + +// TagsParam defines model for tagsParam. +type TagsParam = []string + // DevfileErrorResponse defines model for devfileErrorResponse. type DevfileErrorResponse struct { Error *string `json:"error,omitempty"` @@ -77,92 +311,396 @@ type MethodNotAllowedResponse struct { // V2IndexResponse defines model for v2IndexResponse. type V2IndexResponse = schema.Schema +// ServeDevfileParams defines parameters for ServeDevfile. +type ServeDevfileParams struct { + // MinSchemaVersion The minimum devfile schema version + MinSchemaVersion *MinSchemaVersionParam `form:"minSchemaVersion,omitempty" json:"minSchemaVersion,omitempty"` + + // MaxSchemaVersion The maximum devfile schema version + MaxSchemaVersion *MaxSchemaVersionParam `form:"maxSchemaVersion,omitempty" json:"maxSchemaVersion,omitempty"` +} + +// ServeDevfileStarterProjectParams defines parameters for ServeDevfileStarterProject. +type ServeDevfileStarterProjectParams struct { + // MinSchemaVersion The minimum devfile schema version + MinSchemaVersion *MinSchemaVersionParam `form:"minSchemaVersion,omitempty" json:"minSchemaVersion,omitempty"` + + // MaxSchemaVersion The maximum devfile schema version + MaxSchemaVersion *MaxSchemaVersionParam `form:"maxSchemaVersion,omitempty" json:"maxSchemaVersion,omitempty"` +} + +// ServeDevfileWithVersionParams defines parameters for ServeDevfileWithVersion. +type ServeDevfileWithVersionParams struct { + // MinSchemaVersion The minimum devfile schema version + MinSchemaVersion *MinSchemaVersionParam `form:"minSchemaVersion,omitempty" json:"minSchemaVersion,omitempty"` + + // MaxSchemaVersion The maximum devfile schema version + MaxSchemaVersion *MaxSchemaVersionParam `form:"maxSchemaVersion,omitempty" json:"maxSchemaVersion,omitempty"` +} + +// ServeDevfileStarterProjectWithVersionParams defines parameters for ServeDevfileStarterProjectWithVersion. +type ServeDevfileStarterProjectWithVersionParams struct { + // MinSchemaVersion The minimum devfile schema version + MinSchemaVersion *MinSchemaVersionParam `form:"minSchemaVersion,omitempty" json:"minSchemaVersion,omitempty"` + + // MaxSchemaVersion The maximum devfile schema version + MaxSchemaVersion *MaxSchemaVersionParam `form:"maxSchemaVersion,omitempty" json:"maxSchemaVersion,omitempty"` +} + // ServeDevfileIndexV1Params defines parameters for ServeDevfileIndexV1. type ServeDevfileIndexV1Params struct { - // Arch The target architecture filter - Arch *ArchParam `form:"arch,omitempty" json:"arch,omitempty"` + // Name Search string to filter stacks by their name + Name *NameParam `form:"name,omitempty" json:"name,omitempty"` - // Icon The icon type filter - Icon *IconParam `form:"icon,omitempty" json:"icon,omitempty"` -} + // DisplayName Search string to filter stacks by their display names + DisplayName *DisplayNameParam `form:"displayName,omitempty" json:"displayName,omitempty"` + + // Description Search string to filter stacks by the description text + Description *DescriptionParam `form:"description,omitempty" json:"description,omitempty"` + + // AttributeNames Collection of search strings to filter stacks by the names of + // defined free-form attributes + AttributeNames *AttributeNamesParam `form:"attributeNames,omitempty" json:"attributeNames,omitempty"` + + // Tags Collection of search strings to filter stacks by their tags + Tags *TagsParam `form:"tags,omitempty" json:"tags,omitempty"` -// DeleteDevfileIndexV1WithTypeParams defines parameters for DeleteDevfileIndexV1WithType. -type DeleteDevfileIndexV1WithTypeParams struct { - // Arch The target architecture filter + // Arch Collection of search strings to filter stacks by their architectures Arch *ArchParam `form:"arch,omitempty" json:"arch,omitempty"` - // Icon The icon type filter + // Icon Toggle on encoding content passed Icon *IconParam `form:"icon,omitempty" json:"icon,omitempty"` + + // IconUri Search string to filter stacks by their icon uri + IconUri *IconUriParam `form:"iconUri,omitempty" json:"iconUri,omitempty"` + + // ProjectType Search string to filter stacks by their project type + ProjectType *ProjectTypeParam `form:"projectType,omitempty" json:"projectType,omitempty"` + + // Language Search string to filter stacks by their programming language + Language *LanguageParam `form:"language,omitempty" json:"language,omitempty"` + + // Deprecated Boolean to filter stacks if they are deprecated or not + Deprecated *DeprecatedParam `form:"deprecated,omitempty" json:"deprecated,omitempty"` + + // Resources Collection of search strings to filter stacks by their + // resource files + Resources *ResourcesParam `form:"resources,omitempty" json:"resources,omitempty"` + + // StarterProjects Collection of search strings to filter stacks by the names + // of the starter projects + StarterProjects *StarterProjectsParam `form:"starterProjects,omitempty" json:"starterProjects,omitempty"` + + // LinkNames Collection of search strings to filter stacks by the names + // of the link sources + LinkNames *LinkNamesParam `form:"linkNames,omitempty" json:"linkNames,omitempty"` + + // Links Collection of search strings to filter stacks by their link + // sources + Links *LinksParam `form:"links,omitempty" json:"links,omitempty"` + + // GitRemoteNames Collection of search strings to filter stacks by the names of + // the git remotes + GitRemoteNames *GitRemoteNamesParam `form:"gitRemoteNames,omitempty" json:"gitRemoteNames,omitempty"` + + // GitRemotes Collection of search strings to filter stacks by the URIs of + // the git remotes + GitRemotes *GitRemotesParam `form:"gitRemotes,omitempty" json:"gitRemotes,omitempty"` + + // GitUrl Search string to filter stacks by their git urls + GitUrl *GitUrlParam `form:"gitUrl,omitempty" json:"gitUrl,omitempty"` + + // GitRemoteName Search string to filter stacks by their git remote name + GitRemoteName *GitRemoteNameParam `form:"gitRemoteName,omitempty" json:"gitRemoteName,omitempty"` + + // GitSubDir Search string to filter stacks by their target subdirectory + // of the git repository + GitSubDir *GitSubDirParam `form:"gitSubDir,omitempty" json:"gitSubDir,omitempty"` + + // GitRevision Search string to filter stacks by their git revision + GitRevision *GitRevisionParam `form:"gitRevision,omitempty" json:"gitRevision,omitempty"` + + // Provider Search string to filter stacks by the stack provider + Provider *ProviderParam `form:"provider,omitempty" json:"provider,omitempty"` + + // SupportUrl Search string to filter stacks by their given support url + SupportUrl *SupportUrlParam `form:"supportUrl,omitempty" json:"supportUrl,omitempty"` } // ServeDevfileIndexV1WithTypeParams defines parameters for ServeDevfileIndexV1WithType. type ServeDevfileIndexV1WithTypeParams struct { - // Arch The target architecture filter - Arch *ArchParam `form:"arch,omitempty" json:"arch,omitempty"` + // Name Search string to filter stacks by their name + Name *NameParam `form:"name,omitempty" json:"name,omitempty"` - // Icon The icon type filter - Icon *IconParam `form:"icon,omitempty" json:"icon,omitempty"` -} + // DisplayName Search string to filter stacks by their display names + DisplayName *DisplayNameParam `form:"displayName,omitempty" json:"displayName,omitempty"` -// PostDevfileIndexV1WithTypeParams defines parameters for PostDevfileIndexV1WithType. -type PostDevfileIndexV1WithTypeParams struct { - // Arch The target architecture filter - Arch *ArchParam `form:"arch,omitempty" json:"arch,omitempty"` + // Description Search string to filter stacks by the description text + Description *DescriptionParam `form:"description,omitempty" json:"description,omitempty"` - // Icon The icon type filter - Icon *IconParam `form:"icon,omitempty" json:"icon,omitempty"` -} + // AttributeNames Collection of search strings to filter stacks by the names of + // defined free-form attributes + AttributeNames *AttributeNamesParam `form:"attributeNames,omitempty" json:"attributeNames,omitempty"` + + // Tags Collection of search strings to filter stacks by their tags + Tags *TagsParam `form:"tags,omitempty" json:"tags,omitempty"` -// PutDevfileIndexV1WithTypeParams defines parameters for PutDevfileIndexV1WithType. -type PutDevfileIndexV1WithTypeParams struct { - // Arch The target architecture filter + // Arch Collection of search strings to filter stacks by their architectures Arch *ArchParam `form:"arch,omitempty" json:"arch,omitempty"` - // Icon The icon type filter + // Icon Toggle on encoding content passed Icon *IconParam `form:"icon,omitempty" json:"icon,omitempty"` + + // IconUri Search string to filter stacks by their icon uri + IconUri *IconUriParam `form:"iconUri,omitempty" json:"iconUri,omitempty"` + + // ProjectType Search string to filter stacks by their project type + ProjectType *ProjectTypeParam `form:"projectType,omitempty" json:"projectType,omitempty"` + + // Language Search string to filter stacks by their programming language + Language *LanguageParam `form:"language,omitempty" json:"language,omitempty"` + + // Deprecated Boolean to filter stacks if they are deprecated or not + Deprecated *DeprecatedParam `form:"deprecated,omitempty" json:"deprecated,omitempty"` + + // Resources Collection of search strings to filter stacks by their + // resource files + Resources *ResourcesParam `form:"resources,omitempty" json:"resources,omitempty"` + + // StarterProjects Collection of search strings to filter stacks by the names + // of the starter projects + StarterProjects *StarterProjectsParam `form:"starterProjects,omitempty" json:"starterProjects,omitempty"` + + // LinkNames Collection of search strings to filter stacks by the names + // of the link sources + LinkNames *LinkNamesParam `form:"linkNames,omitempty" json:"linkNames,omitempty"` + + // Links Collection of search strings to filter stacks by their link + // sources + Links *LinksParam `form:"links,omitempty" json:"links,omitempty"` + + // GitRemoteNames Collection of search strings to filter stacks by the names of + // the git remotes + GitRemoteNames *GitRemoteNamesParam `form:"gitRemoteNames,omitempty" json:"gitRemoteNames,omitempty"` + + // GitRemotes Collection of search strings to filter stacks by the URIs of + // the git remotes + GitRemotes *GitRemotesParam `form:"gitRemotes,omitempty" json:"gitRemotes,omitempty"` + + // GitUrl Search string to filter stacks by their git urls + GitUrl *GitUrlParam `form:"gitUrl,omitempty" json:"gitUrl,omitempty"` + + // GitRemoteName Search string to filter stacks by their git remote name + GitRemoteName *GitRemoteNameParam `form:"gitRemoteName,omitempty" json:"gitRemoteName,omitempty"` + + // GitSubDir Search string to filter stacks by their target subdirectory + // of the git repository + GitSubDir *GitSubDirParam `form:"gitSubDir,omitempty" json:"gitSubDir,omitempty"` + + // GitRevision Search string to filter stacks by their git revision + GitRevision *GitRevisionParam `form:"gitRevision,omitempty" json:"gitRevision,omitempty"` + + // Provider Search string to filter stacks by the stack provider + Provider *ProviderParam `form:"provider,omitempty" json:"provider,omitempty"` + + // SupportUrl Search string to filter stacks by their given support url + SupportUrl *SupportUrlParam `form:"supportUrl,omitempty" json:"supportUrl,omitempty"` } // ServeDevfileIndexV2Params defines parameters for ServeDevfileIndexV2. type ServeDevfileIndexV2Params struct { - // Arch The target architecture filter - Arch *ArchParam `form:"arch,omitempty" json:"arch,omitempty"` + // Name Search string to filter stacks by their name + Name *NameParam `form:"name,omitempty" json:"name,omitempty"` - // Icon The icon type filter - Icon *IconParam `form:"icon,omitempty" json:"icon,omitempty"` -} + // DisplayName Search string to filter stacks by their display names + DisplayName *DisplayNameParam `form:"displayName,omitempty" json:"displayName,omitempty"` -// DeleteDevfileIndexV2WithTypeParams defines parameters for DeleteDevfileIndexV2WithType. -type DeleteDevfileIndexV2WithTypeParams struct { - // Arch The target architecture filter + // Description Search string to filter stacks by the description text + Description *DescriptionParam `form:"description,omitempty" json:"description,omitempty"` + + // AttributeNames Collection of search strings to filter stacks by the names of + // defined free-form attributes + AttributeNames *AttributeNamesParam `form:"attributeNames,omitempty" json:"attributeNames,omitempty"` + + // Tags Collection of search strings to filter stacks by their tags + Tags *TagsParam `form:"tags,omitempty" json:"tags,omitempty"` + + // Arch Collection of search strings to filter stacks by their architectures Arch *ArchParam `form:"arch,omitempty" json:"arch,omitempty"` - // Icon The icon type filter + // Icon Toggle on encoding content passed Icon *IconParam `form:"icon,omitempty" json:"icon,omitempty"` + + // IconUri Search string to filter stacks by their icon uri + IconUri *IconUriParam `form:"iconUri,omitempty" json:"iconUri,omitempty"` + + // ProjectType Search string to filter stacks by their project type + ProjectType *ProjectTypeParam `form:"projectType,omitempty" json:"projectType,omitempty"` + + // Language Search string to filter stacks by their programming language + Language *LanguageParam `form:"language,omitempty" json:"language,omitempty"` + + // MinVersion The minimum stack version + MinVersion *MinVersionParam `form:"minVersion,omitempty" json:"minVersion,omitempty"` + + // MaxVersion The maximum stack version + MaxVersion *MaxVersionParam `form:"maxVersion,omitempty" json:"maxVersion,omitempty"` + + // MinSchemaVersion The minimum devfile schema version + MinSchemaVersion *MinSchemaVersionParam `form:"minSchemaVersion,omitempty" json:"minSchemaVersion,omitempty"` + + // MaxSchemaVersion The maximum devfile schema version + MaxSchemaVersion *MaxSchemaVersionParam `form:"maxSchemaVersion,omitempty" json:"maxSchemaVersion,omitempty"` + + // Deprecated Boolean to filter stacks if they are deprecated or not + Deprecated *DeprecatedParam `form:"deprecated,omitempty" json:"deprecated,omitempty"` + + // Default Boolean to filter stacks if they are default or not + Default *DefaultParam `form:"default,omitempty" json:"default,omitempty"` + + // Resources Collection of search strings to filter stacks by their + // resource files + Resources *ResourcesParam `form:"resources,omitempty" json:"resources,omitempty"` + + // StarterProjects Collection of search strings to filter stacks by the names + // of the starter projects + StarterProjects *StarterProjectsParam `form:"starterProjects,omitempty" json:"starterProjects,omitempty"` + + // LinkNames Collection of search strings to filter stacks by the names + // of the link sources + LinkNames *LinkNamesParam `form:"linkNames,omitempty" json:"linkNames,omitempty"` + + // Links Collection of search strings to filter stacks by their link + // sources + Links *LinksParam `form:"links,omitempty" json:"links,omitempty"` + + // CommandGroups Collection of search strings to filter stacks by their present command + // groups + CommandGroups *CommandGroupsParam `form:"commandGroups,omitempty" json:"commandGroups,omitempty"` + + // GitRemoteNames Collection of search strings to filter stacks by the names of + // the git remotes + GitRemoteNames *GitRemoteNamesParam `form:"gitRemoteNames,omitempty" json:"gitRemoteNames,omitempty"` + + // GitRemotes Collection of search strings to filter stacks by the URIs of + // the git remotes + GitRemotes *GitRemotesParam `form:"gitRemotes,omitempty" json:"gitRemotes,omitempty"` + + // GitUrl Search string to filter stacks by their git urls + GitUrl *GitUrlParam `form:"gitUrl,omitempty" json:"gitUrl,omitempty"` + + // GitRemoteName Search string to filter stacks by their git remote name + GitRemoteName *GitRemoteNameParam `form:"gitRemoteName,omitempty" json:"gitRemoteName,omitempty"` + + // GitSubDir Search string to filter stacks by their target subdirectory + // of the git repository + GitSubDir *GitSubDirParam `form:"gitSubDir,omitempty" json:"gitSubDir,omitempty"` + + // GitRevision Search string to filter stacks by their git revision + GitRevision *GitRevisionParam `form:"gitRevision,omitempty" json:"gitRevision,omitempty"` + + // Provider Search string to filter stacks by the stack provider + Provider *ProviderParam `form:"provider,omitempty" json:"provider,omitempty"` + + // SupportUrl Search string to filter stacks by their given support url + SupportUrl *SupportUrlParam `form:"supportUrl,omitempty" json:"supportUrl,omitempty"` } // ServeDevfileIndexV2WithTypeParams defines parameters for ServeDevfileIndexV2WithType. type ServeDevfileIndexV2WithTypeParams struct { - // Arch The target architecture filter - Arch *ArchParam `form:"arch,omitempty" json:"arch,omitempty"` + // Name Search string to filter stacks by their name + Name *NameParam `form:"name,omitempty" json:"name,omitempty"` - // Icon The icon type filter - Icon *IconParam `form:"icon,omitempty" json:"icon,omitempty"` -} + // DisplayName Search string to filter stacks by their display names + DisplayName *DisplayNameParam `form:"displayName,omitempty" json:"displayName,omitempty"` -// PostDevfileIndexV2WithTypeParams defines parameters for PostDevfileIndexV2WithType. -type PostDevfileIndexV2WithTypeParams struct { - // Arch The target architecture filter - Arch *ArchParam `form:"arch,omitempty" json:"arch,omitempty"` + // Description Search string to filter stacks by the description text + Description *DescriptionParam `form:"description,omitempty" json:"description,omitempty"` - // Icon The icon type filter - Icon *IconParam `form:"icon,omitempty" json:"icon,omitempty"` -} + // AttributeNames Collection of search strings to filter stacks by the names of + // defined free-form attributes + AttributeNames *AttributeNamesParam `form:"attributeNames,omitempty" json:"attributeNames,omitempty"` + + // Tags Collection of search strings to filter stacks by their tags + Tags *TagsParam `form:"tags,omitempty" json:"tags,omitempty"` -// PutDevfileIndexV2WithTypeParams defines parameters for PutDevfileIndexV2WithType. -type PutDevfileIndexV2WithTypeParams struct { - // Arch The target architecture filter + // Arch Collection of search strings to filter stacks by their architectures Arch *ArchParam `form:"arch,omitempty" json:"arch,omitempty"` - // Icon The icon type filter + // Icon Toggle on encoding content passed Icon *IconParam `form:"icon,omitempty" json:"icon,omitempty"` + + // IconUri Search string to filter stacks by their icon uri + IconUri *IconUriParam `form:"iconUri,omitempty" json:"iconUri,omitempty"` + + // ProjectType Search string to filter stacks by their project type + ProjectType *ProjectTypeParam `form:"projectType,omitempty" json:"projectType,omitempty"` + + // Language Search string to filter stacks by their programming language + Language *LanguageParam `form:"language,omitempty" json:"language,omitempty"` + + // MinVersion The minimum stack version + MinVersion *MinVersionParam `form:"minVersion,omitempty" json:"minVersion,omitempty"` + + // MaxVersion The maximum stack version + MaxVersion *MaxVersionParam `form:"maxVersion,omitempty" json:"maxVersion,omitempty"` + + // MinSchemaVersion The minimum devfile schema version + MinSchemaVersion *MinSchemaVersionParam `form:"minSchemaVersion,omitempty" json:"minSchemaVersion,omitempty"` + + // MaxSchemaVersion The maximum devfile schema version + MaxSchemaVersion *MaxSchemaVersionParam `form:"maxSchemaVersion,omitempty" json:"maxSchemaVersion,omitempty"` + + // Deprecated Boolean to filter stacks if they are deprecated or not + Deprecated *DeprecatedParam `form:"deprecated,omitempty" json:"deprecated,omitempty"` + + // Default Boolean to filter stacks if they are default or not + Default *DefaultParam `form:"default,omitempty" json:"default,omitempty"` + + // Resources Collection of search strings to filter stacks by their + // resource files + Resources *ResourcesParam `form:"resources,omitempty" json:"resources,omitempty"` + + // StarterProjects Collection of search strings to filter stacks by the names + // of the starter projects + StarterProjects *StarterProjectsParam `form:"starterProjects,omitempty" json:"starterProjects,omitempty"` + + // LinkNames Collection of search strings to filter stacks by the names + // of the link sources + LinkNames *LinkNamesParam `form:"linkNames,omitempty" json:"linkNames,omitempty"` + + // Links Collection of search strings to filter stacks by their link + // sources + Links *LinksParam `form:"links,omitempty" json:"links,omitempty"` + + // CommandGroups Collection of search strings to filter stacks by their present command + // groups + CommandGroups *CommandGroupsParam `form:"commandGroups,omitempty" json:"commandGroups,omitempty"` + + // GitRemoteNames Collection of search strings to filter stacks by the names of + // the git remotes + GitRemoteNames *GitRemoteNamesParam `form:"gitRemoteNames,omitempty" json:"gitRemoteNames,omitempty"` + + // GitRemotes Collection of search strings to filter stacks by the URIs of + // the git remotes + GitRemotes *GitRemotesParam `form:"gitRemotes,omitempty" json:"gitRemotes,omitempty"` + + // GitUrl Search string to filter stacks by their git urls + GitUrl *GitUrlParam `form:"gitUrl,omitempty" json:"gitUrl,omitempty"` + + // GitRemoteName Search string to filter stacks by their git remote name + GitRemoteName *GitRemoteNameParam `form:"gitRemoteName,omitempty" json:"gitRemoteName,omitempty"` + + // GitSubDir Search string to filter stacks by their target subdirectory + // of the git repository + GitSubDir *GitSubDirParam `form:"gitSubDir,omitempty" json:"gitSubDir,omitempty"` + + // GitRevision Search string to filter stacks by their git revision + GitRevision *GitRevisionParam `form:"gitRevision,omitempty" json:"gitRevision,omitempty"` + + // Provider Search string to filter stacks by the stack provider + Provider *ProviderParam `form:"provider,omitempty" json:"provider,omitempty"` + + // SupportUrl Search string to filter stacks by their given support url + SupportUrl *SupportUrlParam `form:"supportUrl,omitempty" json:"supportUrl,omitempty"` } diff --git a/index/server/pkg/server/types.go b/index/server/pkg/server/types.go new file mode 100644 index 000000000..67383f85a --- /dev/null +++ b/index/server/pkg/server/types.go @@ -0,0 +1,138 @@ +// Copyright Red Hat +// +// 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 server + +func (params *ServeDevfileIndexV2Params) toIndexParams() IndexParams { + return IndexParams{ + Name: params.Name, + DisplayName: params.DisplayName, + Description: params.Description, + AttributeNames: params.AttributeNames, + Tags: params.Tags, + Icon: params.Icon, + IconUri: params.IconUri, + Arch: params.Arch, + ProjectType: params.ProjectType, + Language: params.Language, + MinVersion: params.MinVersion, + MaxVersion: params.MaxVersion, + MinSchemaVersion: params.MinSchemaVersion, + MaxSchemaVersion: params.MaxSchemaVersion, + Deprecated: params.Deprecated, + Default: params.Default, + Resources: params.Resources, + StarterProjects: params.StarterProjects, + LinkNames: params.LinkNames, + Links: params.Links, + CommandGroups: params.CommandGroups, + GitRemoteNames: params.GitRemoteNames, + GitRemotes: params.GitRemotes, + GitUrl: params.GitUrl, + GitRemoteName: params.GitRemoteName, + GitSubDir: params.GitSubDir, + GitRevision: params.GitRevision, + Provider: params.Provider, + SupportUrl: params.SupportUrl, + } +} + +func (params *ServeDevfileIndexV2WithTypeParams) toIndexParams() IndexParams { + return IndexParams{ + Name: params.Name, + DisplayName: params.DisplayName, + Description: params.Description, + AttributeNames: params.AttributeNames, + Tags: params.Tags, + Icon: params.Icon, + IconUri: params.IconUri, + Arch: params.Arch, + ProjectType: params.ProjectType, + Language: params.Language, + MinVersion: params.MinVersion, + MaxVersion: params.MaxVersion, + MinSchemaVersion: params.MinSchemaVersion, + MaxSchemaVersion: params.MaxSchemaVersion, + Deprecated: params.Deprecated, + Default: params.Default, + Resources: params.Resources, + StarterProjects: params.StarterProjects, + LinkNames: params.LinkNames, + Links: params.Links, + CommandGroups: params.CommandGroups, + GitRemoteNames: params.GitRemoteNames, + GitRemotes: params.GitRemotes, + GitUrl: params.GitUrl, + GitRemoteName: params.GitRemoteName, + GitSubDir: params.GitSubDir, + GitRevision: params.GitRevision, + Provider: params.Provider, + SupportUrl: params.SupportUrl, + } +} + +func (params *ServeDevfileIndexV1Params) toIndexParams() IndexParams { + return IndexParams{ + Name: params.Name, + DisplayName: params.DisplayName, + Description: params.Description, + AttributeNames: params.AttributeNames, + Tags: params.Tags, + Icon: params.Icon, + IconUri: params.IconUri, + Arch: params.Arch, + ProjectType: params.ProjectType, + Language: params.Language, + Deprecated: params.Deprecated, + Resources: params.Resources, + StarterProjects: params.StarterProjects, + LinkNames: params.LinkNames, + Links: params.Links, + GitRemoteNames: params.GitRemoteNames, + GitRemotes: params.GitRemotes, + GitUrl: params.GitUrl, + GitRemoteName: params.GitRemoteName, + GitSubDir: params.GitSubDir, + GitRevision: params.GitRevision, + Provider: params.Provider, + SupportUrl: params.SupportUrl, + } +} + +func (params *ServeDevfileIndexV1WithTypeParams) toIndexParams() IndexParams { + return IndexParams{ + Name: params.Name, + DisplayName: params.DisplayName, + Description: params.Description, + AttributeNames: params.AttributeNames, + Tags: params.Tags, + Icon: params.Icon, + IconUri: params.IconUri, + Arch: params.Arch, + ProjectType: params.ProjectType, + Language: params.Language, + Deprecated: params.Deprecated, + Resources: params.Resources, + StarterProjects: params.StarterProjects, + LinkNames: params.LinkNames, + Links: params.Links, + GitRemoteNames: params.GitRemoteNames, + GitRemotes: params.GitRemotes, + GitUrl: params.GitUrl, + GitRemoteName: params.GitRemoteName, + GitSubDir: params.GitSubDir, + GitRevision: params.GitRevision, + Provider: params.Provider, + SupportUrl: params.SupportUrl, + } +} diff --git a/index/server/pkg/util/filter.go b/index/server/pkg/util/filter.go index aa4ff7dbe..f3008b42f 100644 --- a/index/server/pkg/util/filter.go +++ b/index/server/pkg/util/filter.go @@ -17,111 +17,351 @@ package util import ( "fmt" + "regexp" "strings" indexSchema "github.com/devfile/registry-support/index/generator/schema" + sets "github.com/hashicorp/go-set" versionpkg "github.com/hashicorp/go-version" + "github.com/mohae/deepcopy" ) -// FilterDevfileArchitectures filters devfiles based on architectures -func FilterDevfileArchitectures(index []indexSchema.Schema, archs []string, v1Index bool) []indexSchema.Schema { - for i := 0; i < len(index); i++ { - if len(index[i].Architectures) == 0 { - // If a stack has no architectures mentioned, then it supports all architectures - continue +const ( + /* Array empty strategies */ + + // Filters out entries with empty field + ARRAY_FILTER_IF_EMPTY = iota + // Omits entries from filtering with empty field (leaves entries in result) + ARRAY_SKIP_IF_EMPTY = iota + + /* Parameter Names */ + + // Parameter 'name' + PARAM_NAME = "name" + // Parameter 'displayName' + PARAM_DISPLAY_NAME = "displayName" + // Parameter 'description' + PARAM_DESCRIPTION = "description" + // Parameter 'icon' + PARAM_ICON = "iconUri" + // Parameter 'projectType' + PARAM_PROJECT_TYPE = "projectType" + // Parameter 'language' + PARAM_LANGUAGE = "language" + // Parameter 'version' + PARAM_VERSION = "version" + // Parameter 'schemaVersion' + PARAM_SCHEMA_VERSION = "schemaVersion" + // Parameter 'default' + PARAM_DEFAULT = "default" + // Parameter 'git.url' + PARAM_GIT_URL = "gitUrl" + // Parameter 'git.remoteName' + PARAM_GIT_REMOTE_NAME = "gitRemoteName" + // Parameter 'git.subDir' + PARAM_GIT_SUBDIR = "gitSubDir" + // Parameter 'git.revision' + PARAM_GIT_REVISION = "gitRevision" + // Parameter 'provider' + PARAM_PROVIDER = "provider" + // Parameter 'supportUrl' + PARAM_SUPPORT_URL = "supportUrl" + + /* Array Parameter Names */ + + // Parameter 'attributeNames' + ARRAY_PARAM_ATTRIBUTE_NAMES = "attributeNames" + // Parameter 'tags' + ARRAY_PARAM_TAGS = "tags" + // Parameter 'architectures' + ARRAY_PARAM_ARCHITECTURES = "arch" + // Parameter 'resources' + ARRAY_PARAM_RESOURCES = "resources" + // Parameter 'starterProjects' + ARRAY_PARAM_STARTER_PROJECTS = "starterProjects" + // Parameter 'links' + ARRAY_PARAM_LINKS = "links" + // Parameter 'commandGroups' + ARRAY_PARAM_COMMAND_GROUPS = "commandGroups" + // Parameter 'gitRemoteNames' + ARRAY_PARAM_GIT_REMOTE_NAMES = "gitRemoteNames" + // Parameter 'gitRemotes' + ARRAY_PARAM_GIT_REMOTES = "gitRemotes" +) + +// FilterResult result entity of filtering the index schema +type FilterResult struct { + // Name of filter + Name string + // Index schema result + Index []indexSchema.Schema + // First error returned in result + Error error +} + +// FilterOptions provides filtering options to filters operations +type FilterOptions[T any] struct { + GetFromIndexField func(*indexSchema.Schema) T + GetFromVersionField func(*indexSchema.Version) T + FilterOutEmpty bool + V1Index bool +} + +// indexFieldEmptyHandler handles what to do with empty index array fields +func indexFieldEmptyHandler(fieldValues []string, requestedValues []string, options FilterOptions[[]string]) bool { + // If filtering out empty, assume that if a stack has no field values mentioned and field value are request + // Else assume that if a stack has no array field values mentioned, then assume all possible are valid + if options.FilterOutEmpty { + return len(requestedValues) != 0 && len(fieldValues) == 0 + } else { + return len(fieldValues) == 0 + } +} + +// versionFieldEmptyHandler handles what to do with empty version array fields +func versionFieldEmptyHandler(fieldValues []string, requestedValues []string, options FilterOptions[[]string]) bool { + // If filtering out empty, assume that if a stack has no field values mentioned and field value are request + // Else assume that if a stack has no array field values mentioned, then assume all possible are valid + if options.FilterOutEmpty { + return len(requestedValues) != 0 && len(fieldValues) == 0 + } else { + return len(fieldValues) == 0 + } +} + +// filterOut filters out element at i in a given referenced array, +// if element does not exist the given referenced array does not +// change. +func filterOut[T any](arr *[]T, i *int) { + if len(*arr) <= *i { + return + } + + // if the requested value is not present, filter it out + *arr = append((*arr)[:*i], (*arr)[*i+1:]...) + + // decrement counter, since we shifted the array + *i-- +} + +// trimExtraSpace Trims extra whitespace from string +func trimExtraSpace(s string) string { + re := regexp.MustCompile(`\s+`) + splitStr := re.Split(strings.TrimSpace(s), -1) + for i := 0; i < len(splitStr); i++ { + if splitStr[i] == "" { + filterOut(&splitStr, &i) } + } + return strings.Join(splitStr, " ") +} - filterIn := true +// preProcessString pre-process give string to perform fuzzy matching +func preProcessString(s string) string { + sLower := strings.ToLower(s) + return trimExtraSpace(sLower) +} - for _, requestedArch := range archs { - isArchPresent := false - for _, devfileArch := range index[i].Architectures { - if requestedArch == devfileArch { - isArchPresent = true - break +// fuzzyMatch fuzzy compare function +func fuzzyMatch(a, b string) bool { + return strings.Contains(preProcessString(a), preProcessString(b)) +} + +// filterDevfileFieldFuzzy filters devfiles based on fuzzy filtering of string fields +func filterDevfileFieldFuzzy(index []indexSchema.Schema, requestedValue string, options FilterOptions[string]) []indexSchema.Schema { + filteredIndex := deepcopy.Copy(index).([]indexSchema.Schema) + + if options.GetFromIndexField != nil || options.GetFromVersionField != nil { + for i := 0; i < len(filteredIndex); i++ { + toFilterOutIndex := false + + if options.GetFromIndexField != nil { + indexValue := options.GetFromIndexField(&filteredIndex[i]) + if !fuzzyMatch(indexValue, requestedValue) { + toFilterOutIndex = true } + } else { + toFilterOutIndex = true } - if !isArchPresent { - // if one of the arch requested is not present, no need to search for the others - filterIn = false - break + if !options.V1Index && options.GetFromVersionField != nil { + filteredVersions := deepcopy.Copy(filteredIndex[i].Versions).([]indexSchema.Version) + for versionIndex := 0; versionIndex < len(filteredVersions); versionIndex++ { + versionValue := options.GetFromVersionField(&filteredVersions[versionIndex]) + if !fuzzyMatch(versionValue, requestedValue) { + filterOut(&filteredVersions, &versionIndex) + } + } + + if len(filteredVersions) != 0 { + filteredIndex[i].Versions = filteredVersions + toFilterOutIndex = false + } + } + + if toFilterOutIndex { + filterOut(&filteredIndex, &i) } } + } - if !filterIn { - // if an arch requested is not present in a devfile, filter it out - index = append(index[:i], index[i+1:]...) + return filteredIndex +} - // decrement counter, since we shifted the array - i-- - continue - } +// filterDevfileFieldFuzzy filters devfiles based on fuzzy filtering of string array fields +func filterDevfileArrayFuzzy(index []indexSchema.Schema, requestedValues []string, options FilterOptions[[]string]) []indexSchema.Schema { + filteredIndex := deepcopy.Copy(index).([]indexSchema.Schema) - // go through each version's architecture if multi-version stack is supported - if !v1Index { - for versionIndex := 0; versionIndex < len(index[i].Versions); versionIndex++ { - versionArchs := index[i].Versions[versionIndex].Architectures - if len(versionArchs) == 0 { - // If a devfile has no architectures mentioned, then it supports all architectures - continue - } - archInVersion := true - for _, requestedArch := range archs { - archPresentInVersion := false - for _, versionArch := range versionArchs { - if requestedArch == versionArch { - archPresentInVersion = true + if options.GetFromIndexField != nil || options.GetFromVersionField != nil { + for i := 0; i < len(filteredIndex); i++ { + toFilterOutIndex := false + + if options.GetFromIndexField != nil { + fieldValues := options.GetFromIndexField(&filteredIndex[i]) + + // If index schema field is not empty perform fuzzy filtering + // else if filtering out based on empty fields is set, set index schema to be filtered out + // (after version filtering if applicable) + if !indexFieldEmptyHandler(fieldValues, requestedValues, options) { + matchAll := true + for _, requestedValue := range requestedValues { + matchFound := false + + for _, fieldValue := range fieldValues { + if fuzzyMatch(fieldValue, requestedValue) { + matchFound = true + break + } + } + + if !matchFound { + matchAll = false break } } - if !archPresentInVersion { - // if one of the arch requested is not present, no need to search for the others - archInVersion = false - break + if !matchAll { + toFilterOutIndex = true } + } else if options.FilterOutEmpty { + toFilterOutIndex = true } + } + + // go through each version's tags if multi-version stack is supported + if !options.V1Index && options.GetFromVersionField != nil { + filteredVersions := deepcopy.Copy(filteredIndex[i].Versions).([]indexSchema.Version) + for versionIndex := 0; versionIndex < len(filteredVersions); versionIndex++ { + fieldValues := options.GetFromVersionField(&filteredVersions[versionIndex]) - if !archInVersion { - // if an arch requested is not present in a devfile, filter it out - index[i].Versions = append(index[i].Versions[:versionIndex], index[i].Versions[versionIndex+1:]...) + // If version schema field is not empty perform fuzzy filtering + // else if filtering out based on empty fields is set, filter out version schema + if !versionFieldEmptyHandler(fieldValues, requestedValues, options) { + matchAll := true + for _, requestedValue := range requestedValues { + matchFound := false + + for _, fieldValue := range fieldValues { + if fuzzyMatch(fieldValue, requestedValue) { + matchFound = true + break + } + } + + if !matchFound { + matchAll = false + break + } + } + if !matchAll { + filterOut(&filteredVersions, &versionIndex) + } + } else if options.FilterOutEmpty { + filterOut(&filteredVersions, &versionIndex) + } + } - // decrement counter, since we shifted the array - versionIndex-- + // If the filtered versions is not empty, set to the filtered index result + // Else if empty and index field filtering was not performed ensure index entry is filtered out + if len(filteredVersions) != 0 { + filteredIndex[i].Versions = filteredVersions + toFilterOutIndex = false + } else if options.GetFromIndexField == nil { + toFilterOutIndex = true } + } + if toFilterOutIndex { + filterOut(&filteredIndex, &i) } } } - return index + return filteredIndex +} + +func IsFieldParameter(name string) bool { + parameterNames := sets.From([]string{ + PARAM_NAME, + PARAM_DISPLAY_NAME, + PARAM_DESCRIPTION, + PARAM_ICON, + PARAM_PROJECT_TYPE, + PARAM_LANGUAGE, + PARAM_VERSION, + PARAM_SCHEMA_VERSION, + PARAM_DEFAULT, + PARAM_GIT_URL, + PARAM_GIT_REMOTE_NAME, + PARAM_GIT_SUBDIR, + PARAM_GIT_REVISION, + PARAM_PROVIDER, + PARAM_SUPPORT_URL, + }) + + return parameterNames.Contains(name) +} + +func IsArrayParameter(name string) bool { + parameterNames := sets.From([]string{ + ARRAY_PARAM_ATTRIBUTE_NAMES, + ARRAY_PARAM_ARCHITECTURES, + ARRAY_PARAM_TAGS, + ARRAY_PARAM_RESOURCES, + ARRAY_PARAM_STARTER_PROJECTS, + ARRAY_PARAM_LINKS, + ARRAY_PARAM_COMMAND_GROUPS, + ARRAY_PARAM_GIT_REMOTE_NAMES, + ARRAY_PARAM_GIT_REMOTES, + }) + + return parameterNames.Contains(name) } // FilterDevfileSchemaVersion filters devfiles based on schema version -func FilterDevfileSchemaVersion(index []indexSchema.Schema, minSchemaVersion string, maxSchemaVersion string) ([]indexSchema.Schema, error) { - for i := 0; i < len(index); i++ { - for versionIndex := 0; versionIndex < len(index[i].Versions); versionIndex++ { - currectSchemaVersion := index[i].Versions[versionIndex].SchemaVersion - schemaVersionWithoutServiceVersion := currectSchemaVersion[:strings.LastIndex(currectSchemaVersion, ".")] - curVersion, err := versionpkg.NewVersion(schemaVersionWithoutServiceVersion) +func FilterDevfileSchemaVersion(index []indexSchema.Schema, minSchemaVersion, maxSchemaVersion *string) ([]indexSchema.Schema, error) { + filteredIndex := deepcopy.Copy(index).([]indexSchema.Schema) + for i := 0; i < len(filteredIndex); i++ { + for versionIndex := 0; versionIndex < len(filteredIndex[i].Versions); versionIndex++ { + currentSchemaVersion := filteredIndex[i].Versions[versionIndex].SchemaVersion + curVersion, err := versionpkg.NewVersion(currentSchemaVersion) if err != nil { - return nil, fmt.Errorf("failed to parse schemaVersion %s for stack: %s, version %s. Error: %v", currectSchemaVersion, index[i].Name, index[i].Versions[versionIndex].Version, err) + return nil, fmt.Errorf("failed to parse schemaVersion %s for stack: %s, version %s. Error: %v", currentSchemaVersion, filteredIndex[i].Name, index[i].Versions[versionIndex].Version, err) } versionInRange := true - if minSchemaVersion != "" { - minVersion, err := versionpkg.NewVersion(minSchemaVersion) + if StrPtrIsSet(minSchemaVersion) { + minVersion, err := versionpkg.NewVersion(*minSchemaVersion) if err != nil { - return nil, fmt.Errorf("failed to parse minSchemaVersion %s. Error: %v", minSchemaVersion, err) + return nil, fmt.Errorf("failed to parse minSchemaVersion %s. Error: %v", *minSchemaVersion, err) } if minVersion.GreaterThan(curVersion) { versionInRange = false } } - if versionInRange && maxSchemaVersion != "" { - maxVersion, err := versionpkg.NewVersion(maxSchemaVersion) + if versionInRange && StrPtrIsSet(maxSchemaVersion) { + maxVersion, err := versionpkg.NewVersion(*maxSchemaVersion) if err != nil { - return nil, fmt.Errorf("failed to parse maxSchemaVersion %s. Error: %v", maxSchemaVersion, err) + return nil, fmt.Errorf("failed to parse maxSchemaVersion %s. Error: %v", *maxSchemaVersion, err) } if maxVersion.LessThan(curVersion) { versionInRange = false @@ -129,20 +369,402 @@ func FilterDevfileSchemaVersion(index []indexSchema.Schema, minSchemaVersion str } if !versionInRange { // if schemaVersion is not in requested range, filter it out - index[i].Versions = append(index[i].Versions[:versionIndex], index[i].Versions[versionIndex+1:]...) + filterOut(&filteredIndex[i].Versions, &versionIndex) + } + } + if len(filteredIndex[i].Versions) == 0 { + // if versions list is empty after filter, remove this index + filterOut(&filteredIndex, &i) + } + } + + return filteredIndex, nil +} + +// FilterDevfileVersion filters devfiles based on stack version +func FilterDevfileVersion(index []indexSchema.Schema, minVersion, maxVersion *string) ([]indexSchema.Schema, error) { + filteredIndex := deepcopy.Copy(index).([]indexSchema.Schema) + for i := 0; i < len(filteredIndex); i++ { + for versionIndex := 0; versionIndex < len(filteredIndex[i].Versions); versionIndex++ { + currentVersion := filteredIndex[i].Versions[versionIndex].Version + curVersion, err := versionpkg.NewVersion(currentVersion) + if err != nil { + return nil, fmt.Errorf("failed to parse version %s for stack: %s. Error: %v", currentVersion, filteredIndex[i].Name, err) + } - // decrement counter, since we shifted the array - versionIndex-- + versionInRange := true + if StrPtrIsSet(minVersion) { + minVersion, err := versionpkg.NewVersion(*minVersion) + if err != nil { + return nil, fmt.Errorf("failed to parse minVersion %s. Error: %v", minVersion, err) + } + if minVersion.GreaterThan(curVersion) { + versionInRange = false + } + } + if versionInRange && StrPtrIsSet(maxVersion) { + maxVersion, err := versionpkg.NewVersion(*maxVersion) + if err != nil { + return nil, fmt.Errorf("failed to parse maxVersion %s. Error: %v", maxVersion, err) + } + if maxVersion.LessThan(curVersion) { + versionInRange = false + } + } + if !versionInRange { + // if version is not in requested range, filter it out + filterOut(&filteredIndex[i].Versions, &versionIndex) } } - if len(index[i].Versions) == 0 { + if len(filteredIndex[i].Versions) == 0 { // if versions list is empty after filter, remove this index - index = append(index[:i], index[i+1:]...) + filterOut(&filteredIndex, &i) + } + } + + return filteredIndex, nil +} + +// FilterDevfileDeprecated inplace filters devfiles based on stack deprecation +func FilterDevfileDeprecated(index *[]indexSchema.Schema, deprecated, v1Index bool) { + for i := 0; i < len(*index); i++ { + toFilterOutIndex := !deprecated + foundDeprecated := false + + for _, tag := range (*index)[i].Tags { + if tag == "Deprecated" { + foundDeprecated = true + break + } + } + + if !foundDeprecated { + toFilterOutIndex = deprecated + + if !v1Index { + for versionIndex := 0; versionIndex < len((*index)[i].Versions); versionIndex++ { + if (*index)[i].Versions[versionIndex].Default { + for _, tag := range (*index)[i].Versions[versionIndex].Tags { + if tag == "Deprecated" { + toFilterOutIndex = !deprecated + break + } + } + break + } + } + } + } - // decrement counter, since we shifted the array - i-- + if toFilterOutIndex { + filterOut(index, &i) } } +} - return index, nil +// FilterDevfileStrField filters by given string field, returns unchanged index if given parameter name is unrecognized +func FilterDevfileStrField(index []indexSchema.Schema, paramName, requestedValue string, v1Index bool) FilterResult { + filterName := fmt.Sprintf("Fuzzy_Field_Filter_On_%s", paramName) + options := FilterOptions[string]{ + V1Index: v1Index, + } + switch paramName { + case PARAM_NAME: + options.GetFromIndexField = func(s *indexSchema.Schema) string { + return s.Name + } + case PARAM_DISPLAY_NAME: + options.GetFromIndexField = func(s *indexSchema.Schema) string { + return s.DisplayName + } + case PARAM_DESCRIPTION: + options.GetFromIndexField = func(s *indexSchema.Schema) string { + return s.Description + } + options.GetFromVersionField = func(v *indexSchema.Version) string { + return v.Description + } + case PARAM_ICON: + options.GetFromIndexField = func(s *indexSchema.Schema) string { + return s.Icon + } + options.GetFromVersionField = func(v *indexSchema.Version) string { + return v.Icon + } + case PARAM_PROJECT_TYPE: + options.GetFromIndexField = func(s *indexSchema.Schema) string { + return s.ProjectType + } + case PARAM_LANGUAGE: + options.GetFromIndexField = func(s *indexSchema.Schema) string { + return s.Language + } + case PARAM_VERSION: + options.GetFromIndexField = func(s *indexSchema.Schema) string { + return s.Version + } + options.GetFromVersionField = func(v *indexSchema.Version) string { + return v.Version + } + case PARAM_SCHEMA_VERSION: + options.GetFromVersionField = func(v *indexSchema.Version) string { + return v.SchemaVersion + } + case PARAM_DEFAULT: + options.GetFromIndexField = nil + options.GetFromVersionField = func(v *indexSchema.Version) string { + return fmt.Sprintf("%v", v.Default) + } + case PARAM_GIT_URL: + options.GetFromIndexField = func(s *indexSchema.Schema) string { + return s.Git.Url + } + options.GetFromVersionField = func(v *indexSchema.Version) string { + return v.Git.Url + } + case PARAM_GIT_REMOTE_NAME: + options.GetFromIndexField = func(s *indexSchema.Schema) string { + return s.Git.RemoteName + } + options.GetFromVersionField = func(v *indexSchema.Version) string { + return v.Git.RemoteName + } + case PARAM_GIT_SUBDIR: + options.GetFromIndexField = func(s *indexSchema.Schema) string { + return s.Git.SubDir + } + options.GetFromVersionField = func(v *indexSchema.Version) string { + return v.Git.SubDir + } + case PARAM_GIT_REVISION: + options.GetFromIndexField = func(s *indexSchema.Schema) string { + return s.Git.Revision + } + options.GetFromVersionField = func(v *indexSchema.Version) string { + return v.Git.Revision + } + case PARAM_PROVIDER: + options.GetFromIndexField = func(s *indexSchema.Schema) string { + return s.Provider + } + case PARAM_SUPPORT_URL: + options.GetFromIndexField = func(s *indexSchema.Schema) string { + return s.SupportUrl + } + default: + return FilterResult{ + Name: filterName, + Index: index, + } + } + + return FilterResult{ + Name: filterName, + Index: filterDevfileFieldFuzzy(index, requestedValue, options), + } +} + +// AndFilter filters results of given filters to only overlapping results +func AndFilter(results ...*FilterResult) FilterResult { + schemaCounts := map[string]*struct { + count int + schema indexSchema.Schema + }{} + resultNames := func() []string { + names := []string{} + for _, result := range results { + names = append(names, result.Name) + } + return names + }() + andResult := FilterResult{ + Name: fmt.Sprintf("And(%s)", strings.Join(resultNames, ", ")), + Index: []indexSchema.Schema{}, + } + + for _, result := range results { + // If a filter returns an error, return as overall result + if result.Error != nil { + andResult.Error = fmt.Errorf("filter failed on '%s': %v", result.Name, result.Error) + return andResult + } + + for _, schema := range result.Index { + schemaCount, found := schemaCounts[schema.Name] + // if not found, initize is a seen counter of one and the current seen schema + // else increment seen counter and re-assign current seen schema if versions have been filtered + if !found { + schemaCounts[schema.Name] = &struct { + count int + schema indexSchema.Schema + }{ + count: 1, + schema: schema, + } + } else { + schemaCounts[schema.Name].count += 1 + if len(schema.Versions) < len(schemaCount.schema.Versions) { + schemaCounts[schema.Name].schema = schema + } + } + } + } + + // build results of filters into new index schema + for _, v := range schemaCounts { + // if result is in every filter result then add to array + if v.count == len(results) { + andResult.Index = append(andResult.Index, v.schema) + } + } + + return andResult +} + +// FilterDevfileStrArrayField filters devfiles based on an array field +func FilterDevfileStrArrayField(index []indexSchema.Schema, paramName string, requestedValues []string, v1Index bool) FilterResult { + filterName := fmt.Sprintf("Fuzzy_Array_Filter_On_%s", paramName) + options := FilterOptions[[]string]{ + FilterOutEmpty: true, + V1Index: v1Index, + } + switch paramName { + case ARRAY_PARAM_ATTRIBUTE_NAMES: + options.GetFromIndexField = func(s *indexSchema.Schema) []string { + names := []string{} + + for name := range s.Attributes { + names = append(names, name) + } + + return names + } + case ARRAY_PARAM_ARCHITECTURES: + options.GetFromIndexField = func(s *indexSchema.Schema) []string { + return s.Architectures + } + options.GetFromVersionField = func(v *indexSchema.Version) []string { + return v.Architectures + } + options.FilterOutEmpty = false + case ARRAY_PARAM_TAGS: + options.GetFromIndexField = func(s *indexSchema.Schema) []string { + return s.Tags + } + options.GetFromVersionField = func(v *indexSchema.Version) []string { + return v.Tags + } + case ARRAY_PARAM_RESOURCES: + options.GetFromIndexField = func(s *indexSchema.Schema) []string { + return s.Resources + } + options.GetFromVersionField = func(v *indexSchema.Version) []string { + return v.Resources + } + case ARRAY_PARAM_STARTER_PROJECTS: + options.GetFromIndexField = func(s *indexSchema.Schema) []string { + return s.StarterProjects + } + options.GetFromVersionField = func(v *indexSchema.Version) []string { + return v.StarterProjects + } + case ARRAY_PARAM_LINKS: + options.GetFromIndexField = func(s *indexSchema.Schema) []string { + links := []string{} + + for linkName := range s.Links { + links = append(links, linkName) + } + + return links + } + options.GetFromVersionField = func(v *indexSchema.Version) []string { + links := []string{} + + for linkName := range v.Links { + links = append(links, linkName) + } + + return links + } + case ARRAY_PARAM_COMMAND_GROUPS: + options.GetFromIndexField = func(s *indexSchema.Schema) []string { + commandGroups := []string{} + + for commandGroup, isSet := range s.CommandGroups { + if isSet { + commandGroups = append(commandGroups, string(commandGroup)) + } + } + + return commandGroups + } + options.GetFromVersionField = func(v *indexSchema.Version) []string { + commandGroups := []string{} + + for commandGroup, isSet := range v.CommandGroups { + if isSet { + commandGroups = append(commandGroups, string(commandGroup)) + } + } + + return commandGroups + } + case ARRAY_PARAM_GIT_REMOTE_NAMES: + options.GetFromIndexField = func(s *indexSchema.Schema) []string { + gitRemoteNames := []string{} + + if s.Git != nil { + for remoteName := range s.Git.Remotes { + gitRemoteNames = append(gitRemoteNames, remoteName) + } + } + + return gitRemoteNames + } + options.GetFromVersionField = func(v *indexSchema.Version) []string { + gitRemoteNames := []string{} + + if v.Git != nil { + for remoteName := range v.Git.Remotes { + gitRemoteNames = append(gitRemoteNames, remoteName) + } + } + + return gitRemoteNames + } + case ARRAY_PARAM_GIT_REMOTES: + options.GetFromIndexField = func(s *indexSchema.Schema) []string { + gitRemotes := []string{} + + if s.Git != nil { + for _, remoteUrl := range s.Git.Remotes { + gitRemotes = append(gitRemotes, remoteUrl) + } + } + + return gitRemotes + } + options.GetFromVersionField = func(v *indexSchema.Version) []string { + gitRemotes := []string{} + + if v.Git != nil { + for _, remoteUrl := range v.Git.Remotes { + gitRemotes = append(gitRemotes, remoteUrl) + } + } + + return gitRemotes + } + default: + return FilterResult{ + Name: filterName, + Index: index, + } + } + + return FilterResult{ + Name: filterName, + Index: filterDevfileArrayFuzzy(index, requestedValues, options), + } } diff --git a/index/server/pkg/util/filter_test.go b/index/server/pkg/util/filter_test.go index a44a570b8..d381d9be1 100644 --- a/index/server/pkg/util/filter_test.go +++ b/index/server/pkg/util/filter_test.go @@ -16,102 +16,181 @@ package util import ( + "fmt" "reflect" + "sort" + "strings" "testing" indexSchema "github.com/devfile/registry-support/index/generator/schema" + "github.com/mohae/deepcopy" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ) -func TestFilterDevfileArchitectures(t *testing.T) { +// filterDevfileStrArrayFieldTestCase type of test case to be used with FilterDevfileStrArrayField +type filterDevfileStrArrayFieldTestCase struct { + Name string + Index []indexSchema.Schema + FieldName string + Values []string + V1Index bool + WantIndex []indexSchema.Schema +} - tests := []struct { - name string - index []indexSchema.Schema - archs []string - v1Index bool - wantIndex []indexSchema.Schema - }{ +// filterDevfileStrFieldTestCase type of test case to be used with FilterDevfileStrField +type filterDevfileStrFieldTestCase struct { + Name string + Index []indexSchema.Schema + FieldName string + Value string + V1Index bool + WantIndex []indexSchema.Schema + WantErr bool + WantErrStr string +} + +var ( + // ============================================ + // Filter Devfile String Array Field Test Cases + // ============================================ + filterAttributeNamesTestCases = []filterDevfileStrArrayFieldTestCase{ { - name: "one arch filter", - index: []indexSchema.Schema{ + Name: "two attribute filters", + FieldName: ARRAY_PARAM_ATTRIBUTE_NAMES, + Index: []indexSchema.Schema{ { - Name: "devfileA", - Architectures: []string{"amd64", "arm64"}, + Name: "devfileA", + Attributes: map[string]apiext.JSON{ + "attributeA": {}, + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + }, }, { - Name: "devfileB", - Architectures: []string{"amd64"}, + Name: "devfileB", + Attributes: map[string]apiext.JSON{ + "attributeA": {}, + "attributeC": {}, + "attributeD": {}, + "attributeE": {}, + }, }, { Name: "devfileC", + Attributes: map[string]apiext.JSON{ + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + "attributeE": {}, + }, }, - }, - v1Index: true, - archs: []string{"amd64"}, - wantIndex: []indexSchema.Schema{ { - Name: "devfileA", - Architectures: []string{"amd64", "arm64"}, + Name: "devfileD", }, + }, + Values: []string{"attributeB", "attributeD"}, + V1Index: true, + WantIndex: []indexSchema.Schema{ { - Name: "devfileB", - Architectures: []string{"amd64"}, + Name: "devfileA", + Attributes: map[string]apiext.JSON{ + "attributeA": {}, + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + }, }, { Name: "devfileC", + Attributes: map[string]apiext.JSON{ + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + "attributeE": {}, + }, }, }, }, { - name: "one arch filter with v2 index", - index: []indexSchema.Schema{ + Name: "two attribute filters v2", + FieldName: ARRAY_PARAM_ATTRIBUTE_NAMES, + Index: []indexSchema.Schema{ { - Name: "devfileA", - Architectures: []string{"amd64", "arm64"}, + Name: "devfileA", + Attributes: map[string]apiext.JSON{ + "attributeA": {}, + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + }, Versions: []indexSchema.Version{ { - Version: "1.0.0", - Architectures: []string{"amd64"}, + Version: "v1.0.0", }, { - Version: "1.1.0", - Architectures: []string{"arm64"}, + Version: "v1.1.0", }, }, }, { - Name: "devfileB", - Architectures: []string{"amd64"}, + Name: "devfileB", + Attributes: map[string]apiext.JSON{ + "attributeA": {}, + "attributeC": {}, + "attributeD": {}, + "attributeE": {}, + }, }, { Name: "devfileC", + Attributes: map[string]apiext.JSON{ + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + "attributeE": {}, + }, + }, + { + Name: "devfileD", }, }, - v1Index: false, - archs: []string{"amd64"}, - wantIndex: []indexSchema.Schema{ + Values: []string{"attributeB", "attributeD"}, + WantIndex: []indexSchema.Schema{ { - Name: "devfileA", - Architectures: []string{"amd64", "arm64"}, + Name: "devfileA", + Attributes: map[string]apiext.JSON{ + "attributeA": {}, + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + }, Versions: []indexSchema.Version{ { - Version: "1.0.0", - Architectures: []string{"amd64"}, + Version: "v1.0.0", + }, + { + Version: "v1.1.0", }, }, }, - { - Name: "devfileB", - Architectures: []string{"amd64"}, - }, { Name: "devfileC", + Attributes: map[string]apiext.JSON{ + "attributeB": {}, + "attributeC": {}, + "attributeD": {}, + "attributeE": {}, + }, }, }, }, + } + filterArchitecturesTestCases = []filterDevfileStrArrayFieldTestCase{ { - name: "two arch filters", - index: []indexSchema.Schema{ + Name: "two arch filters", + FieldName: ARRAY_PARAM_ARCHITECTURES, + Index: []indexSchema.Schema{ { Name: "devfileA", Architectures: []string{"amd64", "arm64"}, @@ -124,9 +203,9 @@ func TestFilterDevfileArchitectures(t *testing.T) { Name: "devfileC", }, }, - v1Index: true, - archs: []string{"amd64", "arm64"}, - wantIndex: []indexSchema.Schema{ + V1Index: true, + Values: []string{"amd64", "arm64"}, + WantIndex: []indexSchema.Schema{ { Name: "devfileA", Architectures: []string{"amd64", "arm64"}, @@ -137,8 +216,9 @@ func TestFilterDevfileArchitectures(t *testing.T) { }, }, { - name: "two arch filters with v2 index", - index: []indexSchema.Schema{ + Name: "two arch filters with v2 index", + FieldName: ARRAY_PARAM_ARCHITECTURES, + Index: []indexSchema.Schema{ { Name: "devfileA", Architectures: []string{"amd64", "arm64"}, @@ -161,9 +241,9 @@ func TestFilterDevfileArchitectures(t *testing.T) { Name: "devfileC", }, }, - v1Index: false, - archs: []string{"amd64", "arm64"}, - wantIndex: []indexSchema.Schema{ + V1Index: false, + Values: []string{"amd64", "arm64"}, + WantIndex: []indexSchema.Schema{ { Name: "devfileA", Architectures: []string{"amd64", "arm64"}, @@ -179,208 +259,2015 @@ func TestFilterDevfileArchitectures(t *testing.T) { }, }, }, + } + filterTagsTestCases = []filterDevfileStrArrayFieldTestCase{ { - name: "empty filters", - index: []indexSchema.Schema{ + Name: "two tag filters", + FieldName: ARRAY_PARAM_TAGS, + Index: []indexSchema.Schema{ { - Name: "devfileA", - Architectures: []string{"amd64", "arm64"}, + Name: "devfileA", + Tags: []string{"Python", "Django"}, }, { - Name: "devfileB", - Architectures: []string{"amd64"}, + Name: "devfileB", + Tags: []string{"Python"}, }, { Name: "devfileC", }, }, - v1Index: true, - archs: []string{}, - wantIndex: []indexSchema.Schema{ + V1Index: true, + Values: []string{"Python", "Django"}, + WantIndex: []indexSchema.Schema{ { - Name: "devfileA", - Architectures: []string{"amd64", "arm64"}, + Name: "devfileA", + Tags: []string{"Python", "Django"}, }, + }, + }, + { + Name: "two tag filters with v2 index", + FieldName: ARRAY_PARAM_TAGS, + Index: []indexSchema.Schema{ { - Name: "devfileB", - Architectures: []string{"amd64"}, + Name: "devfileA", + Tags: []string{"Python", "Django", "Flask"}, + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + Tags: []string{"Python"}, + }, + { + Version: "1.1.0", + Tags: []string{"Python", "Django"}, + }, + { + Version: "2.0.0", + Tags: []string{"Python", "Flask"}, + }, + }, + }, + { + Name: "devfileB", + Tags: []string{"Python"}, }, { Name: "devfileC", }, }, + V1Index: false, + Values: []string{"Python", "Django"}, + WantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Tags: []string{"Python", "Django", "Flask"}, + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + Tags: []string{"Python", "Django"}, + }, + }, + }, + }, }, } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - gotIndex := FilterDevfileArchitectures(test.index, test.archs, test.v1Index) - if !reflect.DeepEqual(gotIndex, test.wantIndex) { - t.Errorf("Got: %v, Expected: %v", gotIndex, test.wantIndex) - } - }) - } -} - -func TestFilterDevfileSchemaVersion(t *testing.T) { - - tests := []struct { - name string - index []indexSchema.Schema - minSchemaVersion string - maxSchemaVersion string - wantIndex []indexSchema.Schema - }{ + filterResourcesTestCases = []filterDevfileStrArrayFieldTestCase{ { - name: "only minSchemaVersion", - index: []indexSchema.Schema{ + Name: "two resource filters", + FieldName: ARRAY_PARAM_RESOURCES, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + Resources: []string{"devfile.yaml", "archive.tar"}, + }, + { + Name: "devfileB", + Resources: []string{"devfile.yaml"}, + }, + { + Name: "devfileC", + Resources: []string{"devfile.yaml", "archive.tar"}, + }, + { + Name: "devfileD", + }, + }, + V1Index: true, + Values: []string{"devfile.yaml", "archive.tar"}, + WantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Resources: []string{"devfile.yaml", "archive.tar"}, + }, + { + Name: "devfileC", + Resources: []string{"devfile.yaml", "archive.tar"}, + }, + }, + }, + { + Name: "two resource filters with v2 index", + FieldName: ARRAY_PARAM_RESOURCES, + Index: []indexSchema.Schema{ { Name: "devfileA", Versions: []indexSchema.Version{ { - Version: "1.0.0", - SchemaVersion: "2.0.0", + Version: "1.0.0", + Resources: []string{"devfile.yaml"}, }, { - Version: "1.1.0", - SchemaVersion: "2.1.0", + Version: "1.1.0", + Resources: []string{"devfile.yaml"}, }, { - Version: "1.2.0", - SchemaVersion: "2.2.0", + Version: "2.0.0", + Resources: []string{"devfile.yaml", "archive.tar"}, }, }, }, { - Name: "devfileB", + Name: "devfileB", + Resources: []string{"devfile.yaml"}, + }, + { + Name: "devfileC", + Resources: []string{"devfile.yaml", "archive.tar"}, Versions: []indexSchema.Version{ { - Version: "1.0.0", - SchemaVersion: "2.0.0", + Version: "1.0.0", + Resources: []string{"devfile.yaml", "archive.tar"}, }, }, }, + { + Name: "devfileD", + }, }, - minSchemaVersion: "2.1", - wantIndex: []indexSchema.Schema{ + V1Index: false, + Values: []string{"devfile.yaml", "archive.tar"}, + WantIndex: []indexSchema.Schema{ { Name: "devfileA", Versions: []indexSchema.Version{ { - Version: "1.1.0", - SchemaVersion: "2.1.0", + Version: "2.0.0", + Resources: []string{"devfile.yaml", "archive.tar"}, }, + }, + }, + { + Name: "devfileC", + Resources: []string{"devfile.yaml", "archive.tar"}, + Versions: []indexSchema.Version{ { - Version: "1.2.0", - SchemaVersion: "2.2.0", + Version: "1.0.0", + Resources: []string{"devfile.yaml", "archive.tar"}, }, }, }, }, }, + } + filterStarterProjectsTestCases = []filterDevfileStrArrayFieldTestCase{ + { + Name: "two starter project filters", + FieldName: ARRAY_PARAM_STARTER_PROJECTS, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + StarterProjects: []string{"starterA", "starterB"}, + }, + { + Name: "devfileB", + StarterProjects: []string{"starterB"}, + }, + { + Name: "devfileC", + StarterProjects: []string{"starterA", "starterC"}, + }, + { + Name: "devfileD", + }, + }, + V1Index: true, + Values: []string{"starterA", "starterB"}, + WantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + StarterProjects: []string{"starterA", "starterB"}, + }, + }, + }, { - name: "only maxSchemaVersion", - index: []indexSchema.Schema{ + Name: "two starter project filters with v2 index", + FieldName: ARRAY_PARAM_STARTER_PROJECTS, + Index: []indexSchema.Schema{ { Name: "devfileA", Versions: []indexSchema.Version{ { - Version: "1.0.0", - SchemaVersion: "2.0.0", + Version: "1.0.0", + StarterProjects: []string{"starterA"}, }, { - Version: "1.1.0", - SchemaVersion: "2.1.0", + Version: "1.1.0", + StarterProjects: []string{"starterA", "starterB"}, }, { - Version: "1.2.0", - SchemaVersion: "2.2.0", + Version: "2.0.0", + StarterProjects: []string{"starterA", "starterB"}, }, }, }, { - Name: "devfileB", + Name: "devfileB", + StarterProjects: []string{"starterB"}, + }, + { + Name: "devfileC", + StarterProjects: []string{"starterA", "starterC"}, Versions: []indexSchema.Version{ { - Version: "1.1.0", - SchemaVersion: "2.1.0", + Version: "1.0.0", + StarterProjects: []string{"starterA", "starterC"}, + }, + { + Version: "2.0.0", + StarterProjects: []string{"starterA", "starterB"}, }, }, }, + { + Name: "devfileD", + }, }, - maxSchemaVersion: "2.1", - wantIndex: []indexSchema.Schema{ + V1Index: false, + Values: []string{"starterA", "starterB"}, + WantIndex: []indexSchema.Schema{ { Name: "devfileA", Versions: []indexSchema.Version{ { - Version: "1.0.0", - SchemaVersion: "2.0.0", + Version: "1.1.0", + StarterProjects: []string{"starterA", "starterB"}, }, { - Version: "1.1.0", - SchemaVersion: "2.1.0", + Version: "2.0.0", + StarterProjects: []string{"starterA", "starterB"}, }, }, }, { - Name: "devfileB", + Name: "devfileC", + StarterProjects: []string{"starterA", "starterC"}, Versions: []indexSchema.Version{ { - Version: "1.1.0", - SchemaVersion: "2.1.0", + Version: "2.0.0", + StarterProjects: []string{"starterA", "starterB"}, }, }, }, }, }, + } + filterLinksTestCases = []filterDevfileStrArrayFieldTestCase{ { - name: "both minSchemaVersion and maxSchemaVersion", - index: []indexSchema.Schema{ + Name: "two link filters", + FieldName: ARRAY_PARAM_LINKS, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + Links: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + { + Name: "devfileB", + Links: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + { + Name: "devfileC", + Links: map[string]string{ + "linkA": "git.test.com", + }, + }, + { + Name: "devfileD", + }, + }, + V1Index: true, + Values: []string{"linkA", "linkC"}, + WantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Links: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + { + Name: "devfileB", + Links: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + }, + { + Name: "two link filters with v2 index", + FieldName: ARRAY_PARAM_LINKS, + Index: []indexSchema.Schema{ { Name: "devfileA", Versions: []indexSchema.Version{ { - Version: "1.0.0", - SchemaVersion: "2.0.0", - }, - { - Version: "1.1.0", - SchemaVersion: "2.1.0", + Version: "1.0.0", }, { - Version: "1.2.0", - SchemaVersion: "2.2.0", + Version: "1.1.0", + Links: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, }, }, }, { Name: "devfileB", + Links: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + { + Name: "devfileC", + Links: map[string]string{ + "linkA": "git.test.com", + }, Versions: []indexSchema.Version{ { - Version: "1.1.0", - SchemaVersion: "2.1.0", + Version: "1.0.0", + Links: map[string]string{ + "linkA": "git.test.com", + }, + }, + { + Version: "1.1.0", + Links: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, }, }, }, + { + Name: "devfileD", + }, }, - minSchemaVersion: "2.1", - maxSchemaVersion: "2.1", - wantIndex: []indexSchema.Schema{ + V1Index: false, + Values: []string{"linkA", "linkC"}, + WantIndex: []indexSchema.Schema{ { Name: "devfileA", Versions: []indexSchema.Version{ { - Version: "1.1.0", - SchemaVersion: "2.1.0", + Version: "1.1.0", + Links: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, }, }, }, { Name: "devfileB", - Versions: []indexSchema.Version{ - { - Version: "1.1.0", + Links: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + { + Name: "devfileC", + Links: map[string]string{ + "linkA": "git.test.com", + }, + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + Links: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + }, + }, + }, + } + filterCommandGroupsTestCases = []filterDevfileStrArrayFieldTestCase{ + { + Name: "two command group filters", + FieldName: ARRAY_PARAM_COMMAND_GROUPS, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + { + Name: "devfileB", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Name: "devfileC", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Name: "devfileD", + }, + }, + V1Index: true, + Values: []string{string(indexSchema.BuildCommandGroupKind), string(indexSchema.RunCommandGroupKind)}, + WantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + { + Name: "devfileB", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + }, + }, + }, + }, + { + Name: "two command group filters with v2 index", + FieldName: ARRAY_PARAM_COMMAND_GROUPS, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + }, + { + Version: "1.1.0", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Version: "2.0.0", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + { + Name: "devfileB", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Name: "devfileC", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Version: "2.0.0", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + { + Name: "devfileD", + }, + }, + V1Index: false, + Values: []string{string(indexSchema.BuildCommandGroupKind), string(indexSchema.RunCommandGroupKind)}, + WantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Version: "2.0.0", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + { + Name: "devfileB", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + }, + }, + { + Name: "devfileC", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DeployCommandGroupKind: false, + indexSchema.RunCommandGroupKind: true, + }, + Versions: []indexSchema.Version{ + { + Version: "2.0.0", + CommandGroups: map[indexSchema.CommandGroupKind]bool{ + indexSchema.DebugCommandGroupKind: false, + indexSchema.DeployCommandGroupKind: false, + indexSchema.BuildCommandGroupKind: true, + indexSchema.RunCommandGroupKind: true, + indexSchema.TestCommandGroupKind: false, + }, + }, + }, + }, + }, + }, + } + filterGitRemoteNamesTestCases = []filterDevfileStrArrayFieldTestCase{ + { + Name: "two git remote name filters", + FieldName: ARRAY_PARAM_GIT_REMOTE_NAMES, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + { + Name: "devfileB", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + { + Name: "devfileC", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + }, + }, + }, + { + Name: "devfileD", + Git: &indexSchema.Git{ + RemoteName: "remoteA", + }, + }, + { + Name: "devfileE", + }, + }, + V1Index: true, + Values: []string{"linkA", "linkC"}, + WantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + { + Name: "devfileB", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + }, + }, + { + Name: "two git remote name filters with v2 index", + FieldName: ARRAY_PARAM_GIT_REMOTE_NAMES, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + }, + { + Version: "1.1.0", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + }, + }, + { + Name: "devfileB", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + { + Name: "devfileC", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + }, + }, + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + }, + }, + }, + { + Version: "1.1.0", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + }, + }, + { + Name: "devfileD", + Git: &indexSchema.Git{ + RemoteName: "remoteA", + }, + }, + { + Name: "devfileE", + }, + }, + V1Index: false, + Values: []string{"linkA", "linkC"}, + WantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + }, + }, + { + Name: "devfileB", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + { + Name: "devfileC", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + }, + }, + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + }, + }, + }, + }, + } + filterGitRemotesTestCases = []filterDevfileStrArrayFieldTestCase{ + { + Name: "two git remote filters", + FieldName: ARRAY_PARAM_GIT_REMOTES, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + { + Name: "devfileB", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + { + Name: "devfileC", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + }, + }, + }, + { + Name: "devfileD", + Git: &indexSchema.Git{ + RemoteName: "remoteA", + }, + }, + { + Name: "devfileE", + }, + }, + V1Index: true, + Values: []string{"git", ".com"}, + WantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + { + Name: "devfileB", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + { + Name: "devfileC", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + }, + }, + }, + }, + }, + { + Name: "two git remote filters with v2 index", + FieldName: ARRAY_PARAM_GIT_REMOTES, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + }, + { + Version: "1.1.0", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + }, + }, + { + Name: "devfileB", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + { + Name: "devfileC", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + }, + }, + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + }, + }, + }, + { + Version: "1.1.0", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + }, + }, + { + Name: "devfileD", + Git: &indexSchema.Git{ + RemoteName: "remoteA", + }, + }, + { + Name: "devfileE", + }, + }, + V1Index: false, + Values: []string{"git", ".com"}, + WantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkB": "https://http.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + }, + }, + { + Name: "devfileB", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + { + Name: "devfileC", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + }, + }, + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + }, + }, + }, + { + Version: "1.1.0", + Git: &indexSchema.Git{ + Remotes: map[string]string{ + "linkA": "git.test.com", + "linkC": "https://another.testlink.ca", + }, + }, + }, + }, + }, + }, + }, + } + // ====================================== + // Filter Devfile String Field Test Cases + // ====================================== + filterNameFieldTestCases = []filterDevfileStrFieldTestCase{ + { + Name: "name filter", + FieldName: PARAM_NAME, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + }, + { + Name: "devfileB", + }, + { + Name: "devfileC", + }, + { + Name: "devfileAA", + }, + }, + V1Index: true, + Value: "A", + WantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + }, + { + Name: "devfileAA", + }, + }, + WantErr: false, + }, + { + Name: "name filter v2", + FieldName: PARAM_NAME, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + }, + { + Name: "devfileB", + }, + { + Name: "devfileC", + }, + { + Name: "devfileAA", + }, + }, + Value: "A", + WantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + }, + { + Name: "devfileAA", + }, + }, + WantErr: false, + }, + } + filterDisplayNameFieldTestCases = []filterDevfileStrFieldTestCase{ + { + Name: "display name filter", + FieldName: PARAM_DISPLAY_NAME, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + }, + { + Name: "devfileB", + DisplayName: "Python", + }, + { + Name: "devfileC", + DisplayName: "Flask", + }, + { + Name: "devfileAA", + DisplayName: "Python - Flask", + }, + }, + V1Index: true, + Value: "Flask", + WantIndex: []indexSchema.Schema{ + { + Name: "devfileC", + DisplayName: "Flask", + }, + { + Name: "devfileAA", + DisplayName: "Python - Flask", + }, + }, + WantErr: false, + }, + { + Name: "display name filter v2", + FieldName: PARAM_DISPLAY_NAME, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + }, + { + Name: "devfileB", + DisplayName: "Python", + }, + { + Name: "devfileC", + DisplayName: "Flask", + }, + { + Name: "devfileAA", + DisplayName: "Python - Flask", + }, + }, + Value: "Flask", + WantIndex: []indexSchema.Schema{ + { + Name: "devfileC", + DisplayName: "Flask", + }, + { + Name: "devfileAA", + DisplayName: "Python - Flask", + }, + }, + WantErr: false, + }, + } + filterDescriptionFieldTestCases = []filterDevfileStrFieldTestCase{ + { + Name: "description filter", + FieldName: PARAM_DESCRIPTION, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + Description: "A python stack.", + }, + { + Name: "devfileB", + DisplayName: "Python", + Description: "A python sample.", + }, + { + Name: "devfileC", + DisplayName: "Flask", + Description: "A python flask stack.", + }, + { + Name: "devfileAA", + DisplayName: "Python - Flask", + Description: "A python flask sample.", + }, + }, + V1Index: true, + Value: "stack", + WantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + Description: "A python stack.", + }, + { + Name: "devfileC", + DisplayName: "Flask", + Description: "A python flask stack.", + }, + }, + WantErr: false, + }, + { + Name: "description filter v2", + FieldName: PARAM_DESCRIPTION, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + Description: "A python stack.", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + Description: "A python stack.", + Default: true, + }, + }, + }, + { + Name: "devfileB", + DisplayName: "Python", + Description: "A python sample.", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + Description: "A python sample.", + }, + { + Version: "2.0.0", + Description: "A python stack.", + Default: true, + }, + }, + }, + { + Name: "devfileC", + DisplayName: "Flask", + Description: "A python flask stack.", + }, + { + Name: "devfileAA", + DisplayName: "Python - Flask", + Description: "A python flask sample.", + }, + }, + Value: "stack", + WantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + Description: "A python stack.", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + Description: "A python stack.", + Default: true, + }, + }, + }, + { + Name: "devfileB", + DisplayName: "Python", + Description: "A python sample.", + Versions: []indexSchema.Version{ + { + Version: "2.0.0", + Description: "A python stack.", + Default: true, + }, + }, + }, + { + Name: "devfileC", + DisplayName: "Flask", + Description: "A python flask stack.", + }, + }, + WantErr: false, + }, + } + filterIconFieldTestCases = []filterDevfileStrFieldTestCase{ + { + Name: "icon filter", + FieldName: PARAM_ICON, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + Icon: "devfileA.png", + }, + { + Name: "devfileB", + DisplayName: "Python", + Icon: "devfileB.png", + }, + { + Name: "devfileC", + DisplayName: "Flask", + Icon: "devfileC.jpg", + }, + { + Name: "devfileAA", + DisplayName: "Python - Flask", + Icon: "devfileAA.ico", + }, + }, + V1Index: true, + Value: "png", + WantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + Icon: "devfileA.png", + }, + { + Name: "devfileB", + DisplayName: "Python", + Icon: "devfileB.png", + }, + }, + WantErr: false, + }, + { + Name: "icon filter v2", + FieldName: PARAM_ICON, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + Description: "A python stack.", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + Icon: "devfileA.png", + Default: true, + }, + }, + }, + { + Name: "devfileB", + DisplayName: "Python", + Description: "A python sample.", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + Icon: "devfileB.png", + }, + { + Version: "2.0.0", + Icon: "devfileB.ico", + Default: true, + }, + }, + }, + { + Name: "devfileC", + DisplayName: "Flask", + Icon: "devfileC.jpg", + }, + { + Name: "devfileAA", + DisplayName: "Python - Flask", + Icon: "devfileAA.ico", + }, + }, + Value: "ico", + WantIndex: []indexSchema.Schema{ + { + Name: "devfileB", + DisplayName: "Python", + Description: "A python sample.", + Versions: []indexSchema.Version{ + { + Version: "2.0.0", + Icon: "devfileB.ico", + Default: true, + }, + }, + }, + { + Name: "devfileAA", + DisplayName: "Python - Flask", + Icon: "devfileAA.ico", + }, + }, + WantErr: false, + }, + } + filterProjectTypeFieldTestCases = []filterDevfileStrFieldTestCase{ + { + Name: "project type filter", + FieldName: PARAM_PROJECT_TYPE, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + ProjectType: "python", + }, + { + Name: "devfileB", + DisplayName: "Python", + ProjectType: "python", + }, + { + Name: "devfileC", + DisplayName: "Flask", + ProjectType: "python", + }, + { + Name: "devfileD", + DisplayName: "Java Springboot", + ProjectType: "java", + }, + { + Name: "devfileAA", + DisplayName: "Python - Flask", + ProjectType: "python", + }, + }, + V1Index: true, + Value: "java", + WantIndex: []indexSchema.Schema{ + { + Name: "devfileD", + DisplayName: "Java Springboot", + ProjectType: "java", + }, + }, + WantErr: false, + }, + { + Name: "project type filter v2", + FieldName: PARAM_PROJECT_TYPE, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + ProjectType: "python", + }, + { + Name: "devfileB", + DisplayName: "Python", + ProjectType: "python", + }, + { + Name: "devfileC", + DisplayName: "Flask", + ProjectType: "python", + }, + { + Name: "devfileD", + DisplayName: "Java Springboot", + ProjectType: "java", + }, + { + Name: "devfileAA", + DisplayName: "Python - Flask", + ProjectType: "python", + }, + }, + Value: "java", + WantIndex: []indexSchema.Schema{ + { + Name: "devfileD", + DisplayName: "Java Springboot", + ProjectType: "java", + }, + }, + WantErr: false, + }, + } + filterLanguageFieldTestCases = []filterDevfileStrFieldTestCase{ + { + Name: "language filter", + FieldName: PARAM_LANGUAGE, + Index: []indexSchema.Schema{ + { + Name: "devfileA", + DisplayName: "Python", + Language: "Python", + }, + { + Name: "devfileB", + DisplayName: "Python", + Language: "Python", + }, + { + Name: "devfileC", + DisplayName: "Flask", + Language: "Python", + }, + { + Name: "devfileD", + DisplayName: "Java Springboot", + Language: "Java", + }, + { + Name: "devfileAA", + DisplayName: "Python - Flask", + Language: "Python", + }, + }, + V1Index: true, + Value: "java", + WantIndex: []indexSchema.Schema{ + { + Name: "devfileD", + DisplayName: "Java Springboot", + Language: "Java", + }, + }, + WantErr: false, + }, + } + filterVersionFieldTestCases = []filterDevfileStrFieldTestCase{} + filterSchemaVersionFieldTestCases = []filterDevfileStrFieldTestCase{} + filterDefaultFieldTestCases = []filterDevfileStrFieldTestCase{} + filterGitUrlFieldTestCases = []filterDevfileStrFieldTestCase{} + filterGitRemoteNameFieldTestCases = []filterDevfileStrFieldTestCase{} + filterGitSubDirFieldTestCases = []filterDevfileStrFieldTestCase{} + filterGitRevisionFieldTestCases = []filterDevfileStrFieldTestCase{} + filterProviderFieldTestCases = []filterDevfileStrFieldTestCase{} + filterSupportUrlFieldTestCases = []filterDevfileStrFieldTestCase{} +) + +func TestFilterOut(t *testing.T) { + tests := []struct { + name string + index []indexSchema.Schema + filterAtIdx int + wantIndex []indexSchema.Schema + expectPanic bool + }{ + { + name: "filter out index 2", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Architectures: []string{"amd64", "arm64"}, + }, + { + Name: "devfileB", + Architectures: []string{"amd64"}, + }, + { + Name: "devfileC", + }, + }, + filterAtIdx: 2, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Architectures: []string{"amd64", "arm64"}, + }, + { + Name: "devfileB", + Architectures: []string{"amd64"}, + }, + }, + }, + { + name: "filter out index 1", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Architectures: []string{"amd64", "arm64"}, + }, + { + Name: "devfileB", + Architectures: []string{"amd64"}, + }, + { + Name: "devfileC", + }, + }, + filterAtIdx: 1, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Architectures: []string{"amd64", "arm64"}, + }, + { + Name: "devfileC", + }, + }, + }, + { + name: "filter out non-existent index 3", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Architectures: []string{"amd64", "arm64"}, + }, + { + Name: "devfileB", + Architectures: []string{"amd64"}, + }, + { + Name: "devfileC", + }, + }, + filterAtIdx: 3, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Architectures: []string{"amd64", "arm64"}, + }, + { + Name: "devfileB", + Architectures: []string{"amd64"}, + }, + { + Name: "devfileC", + }, + }, + }, + { + name: "filter out index 0 on empty array", + index: []indexSchema.Schema{}, + filterAtIdx: 0, + wantIndex: []indexSchema.Schema{}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + gotIndex := deepcopy.Copy(test.index).([]indexSchema.Schema) + filterOut(&gotIndex, &test.filterAtIdx) + if !reflect.DeepEqual(gotIndex, test.wantIndex) { + t.Errorf("Got: %v, Expected: %v", gotIndex, test.wantIndex) + } + }) + } +} + +func TestFuzzyMatch(t *testing.T) { + tests := []struct { + name string + valueA string + valueB string + want bool + }{ + { + name: "Exact match", + valueA: "Java Springboot", + valueB: "Java Springboot", + want: true, + }, + { + name: "Extra space match", + valueA: "Java Springboot", + valueB: " Java Springboot ", + want: true, + }, + { + name: "Extra space with newlines match", + valueA: "Java Springboot", + valueB: ` Java + Springboot + + `, + want: true, + }, + { + name: "Partial match", + valueA: "Java Springboot", + valueB: "spring", + want: true, + }, + { + name: "One word match", + valueA: "Java Springboot", + valueB: "java", + want: true, + }, + { + name: "Mismatches", + valueA: "Java Springboot", + valueB: "python", + want: false, + }, + { + name: "Mismatches with dash", + valueA: "Java Springboot", + valueB: "python-flask", + want: false, + }, + { + name: "Mismatches with whitespace", + valueA: "Java Springboot", + valueB: ` python + flask `, + want: false, + }, + { + name: "Blank A", + valueA: "", + valueB: "python", + want: false, + }, + { + name: "Blank B", + valueA: "Java Springboot", + valueB: "", + want: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := fuzzyMatch(test.valueA, test.valueB) + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Got: %v, Expected: %v", got, test.want) + } + }) + } +} + +func TestFilterDevfileSchemaVersion(t *testing.T) { + + tests := []struct { + name string + index []indexSchema.Schema + minSchemaVersion string + maxSchemaVersion string + wantIndex []indexSchema.Schema + }{ + { + name: "only minSchemaVersion", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.0.0", + }, + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + { + Version: "1.2.0", + SchemaVersion: "2.2.0", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.0.0", + }, + }, + }, + }, + minSchemaVersion: "2.1", + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + { + Version: "1.2.0", + SchemaVersion: "2.2.0", + }, + }, + }, + }, + }, + { + name: "only maxSchemaVersion", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.0.0", + }, + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + { + Version: "1.2.0", + SchemaVersion: "2.2.0", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + }, + }, + }, + maxSchemaVersion: "2.1", + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.0.0", + }, + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + }, + }, + }, + }, + { + name: "both minSchemaVersion and maxSchemaVersion", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.0.0", + }, + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + { + Version: "1.2.0", + SchemaVersion: "2.2.0", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + }, + }, + }, + minSchemaVersion: "2.1", + maxSchemaVersion: "2.1", + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + gotIndex, gotErr := FilterDevfileSchemaVersion(test.index, &test.minSchemaVersion, &test.maxSchemaVersion) + if gotErr != nil { + if gotIndex != nil { + t.Errorf("Unexpected non-nil index on error: %v", gotIndex) + } + t.Errorf("Unexpected error: %v", gotErr) + } else if !reflect.DeepEqual(gotIndex, test.wantIndex) { + t.Errorf("Got: %v, Expected: %v", gotIndex, test.wantIndex) + } + }) + } +} + +func TestFilterDevfileVersion(t *testing.T) { + + tests := []struct { + name string + index []indexSchema.Schema + minVersion string + maxVersion string + wantIndex []indexSchema.Schema + }{ + { + name: "only minVersion", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.0.0", + }, + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + { + Version: "1.2.0", + SchemaVersion: "2.2.0", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.0.0", + }, + }, + }, + }, + minVersion: "1.1", + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + { + Version: "1.2.0", + SchemaVersion: "2.2.0", + }, + }, + }, + }, + }, + { + name: "only maxVersion", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.0.0", + }, + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + { + Version: "1.2.0", + SchemaVersion: "2.2.0", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + }, + }, + }, + maxVersion: "1.1", + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.0.0", + }, + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + }, + }, + }, + }, + { + name: "both minSchemaVersion and maxSchemaVersion", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.0.0", + }, + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + { + Version: "1.2.0", + SchemaVersion: "2.2.0", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + }, + }, + }, + minVersion: "1.1", + maxVersion: "1.2", + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Versions: []indexSchema.Version{ + { + Version: "1.1.0", + SchemaVersion: "2.1.0", + }, + { + Version: "1.2.0", + SchemaVersion: "2.2.0", + }, + }, + }, + { + Name: "devfileB", + Versions: []indexSchema.Version{ + { + Version: "1.1.0", SchemaVersion: "2.1.0", }, }, @@ -391,12 +2278,1144 @@ func TestFilterDevfileSchemaVersion(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - gotIndex, err := FilterDevfileSchemaVersion(test.index, test.minSchemaVersion, test.maxSchemaVersion) - if err != nil { - t.Errorf("Unexpected error: %v", err) + gotIndex, gotErr := FilterDevfileVersion(test.index, &test.minVersion, &test.maxVersion) + if gotErr != nil { + if gotIndex != nil { + t.Errorf("Unexpected non-nil index on error: %v", gotIndex) + } + t.Errorf("Unexpected error: %v", gotErr) + } else if !reflect.DeepEqual(gotIndex, test.wantIndex) { + t.Errorf("Got: %v, Expected: %v", gotIndex, test.wantIndex) + } + }) + } +} + +func TestFilterDevfileDeprecated(t *testing.T) { + tests := []struct { + name string + index []indexSchema.Schema + deprecated bool + v1Index bool + wantIndex []indexSchema.Schema + }{ + { + name: "Case 1: filter out non-deprecated stacks", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Tags: []string{ + "Go", + "Gin", + "Deprecated", + }, + }, + { + Name: "devfileB", + Tags: []string{ + "Deprecated", + "Python", + }, + }, + { + Name: "devfileC", + Tags: []string{ + "Python", + "Flask", + }, + }, + { + Name: "devfileD", + Tags: []string{ + "JS", + "Node.js", + }, + }, + }, + deprecated: true, + v1Index: true, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Tags: []string{ + "Go", + "Gin", + "Deprecated", + }, + }, + { + Name: "devfileB", + Tags: []string{ + "Deprecated", + "Python", + }, + }, + }, + }, + { + name: "Case 2: filter out non-deprecated stacks v2", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Tags: []string{ + "Go", + "Gin", + "Deprecated", + }, + Versions: []indexSchema.Version{ + { + Version: "1.2.0", + Default: true, + Tags: []string{ + "Go", + "Gin", + }, + }, + { + Version: "1.0.0", + Tags: []string{ + "Go", + "Deprecated", + }, + }, + }, + }, + { + Name: "devfileB", + Tags: []string{ + "Python", + }, + Versions: []indexSchema.Version{ + { + Version: "1.2.0", + Default: true, + Tags: []string{ + "Deprecated", + "Python", + }, + }, + }, + }, + { + Name: "devfileC", + Tags: []string{ + "Python", + "Flask", + }, + Versions: []indexSchema.Version{ + { + Version: "1.2.0", + Default: true, + Tags: []string{ + "Python", + "Flask", + }, + }, + { + Version: "1.0.0", + Tags: []string{ + "Deprecated", + "Python", + }, + }, + }, + }, + { + Name: "devfileD", + Tags: []string{ + "JS", + "Node.js", + }, + }, + }, + deprecated: true, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileA", + Tags: []string{ + "Go", + "Gin", + "Deprecated", + }, + Versions: []indexSchema.Version{ + { + Version: "1.2.0", + Default: true, + Tags: []string{ + "Go", + "Gin", + }, + }, + { + Version: "1.0.0", + Tags: []string{ + "Go", + "Deprecated", + }, + }, + }, + }, + { + Name: "devfileB", + Tags: []string{ + "Python", + }, + Versions: []indexSchema.Version{ + { + Version: "1.2.0", + Default: true, + Tags: []string{ + "Deprecated", + "Python", + }, + }, + }, + }, + }, + }, + { + name: "Case 3: filter out non-deprecated stacks with no deprecated stacks", + index: []indexSchema.Schema{ + { + Name: "devfileC", + Tags: []string{ + "Python", + "Flask", + }, + Versions: []indexSchema.Version{ + { + Version: "1.2.0", + Default: true, + Tags: []string{ + "Python", + "Flask", + }, + }, + { + Version: "1.0.0", + Tags: []string{ + "Deprecated", + "Python", + }, + }, + }, + }, + { + Name: "devfileD", + Tags: []string{ + "JS", + "Node.js", + }, + }, + }, + deprecated: true, + wantIndex: make([]indexSchema.Schema, 0, 2), + }, + { + name: "Case 4: filter out non-deprecated stacks with empty index schema", + deprecated: true, + }, + { + name: "Case 5: filter out deprecated stacks", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Tags: []string{ + "Go", + "Gin", + "Deprecated", + }, + }, + { + Name: "devfileB", + Tags: []string{ + "Deprecated", + "Python", + }, + }, + { + Name: "devfileC", + Tags: []string{ + "Python", + "Flask", + }, + }, + { + Name: "devfileD", + Tags: []string{ + "JS", + "Node.js", + }, + }, + }, + v1Index: true, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileC", + Tags: []string{ + "Python", + "Flask", + }, + }, + { + Name: "devfileD", + Tags: []string{ + "JS", + "Node.js", + }, + }, + }, + }, + { + name: "Case 6: filter out deprecated stacks v2", + index: []indexSchema.Schema{ + { + Name: "devfileA", + Tags: []string{ + "Go", + "Gin", + "Deprecated", + }, + Versions: []indexSchema.Version{ + { + Version: "1.2.0", + Default: true, + Tags: []string{ + "Go", + "Gin", + }, + }, + { + Version: "1.0.0", + Tags: []string{ + "Go", + "Deprecated", + }, + }, + }, + }, + { + Name: "devfileB", + Tags: []string{ + "Python", + }, + Versions: []indexSchema.Version{ + { + Version: "1.2.0", + Default: true, + Tags: []string{ + "Deprecated", + "Python", + }, + }, + }, + }, + { + Name: "devfileC", + Tags: []string{ + "Python", + "Flask", + }, + Versions: []indexSchema.Version{ + { + Version: "1.2.0", + Default: true, + Tags: []string{ + "Python", + "Flask", + }, + }, + { + Version: "1.0.0", + Tags: []string{ + "Deprecated", + "Python", + }, + }, + }, + }, + { + Name: "devfileD", + Tags: []string{ + "JS", + "Node.js", + }, + }, + }, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileC", + Tags: []string{ + "Python", + "Flask", + }, + Versions: []indexSchema.Version{ + { + Version: "1.2.0", + Default: true, + Tags: []string{ + "Python", + "Flask", + }, + }, + { + Version: "1.0.0", + Tags: []string{ + "Deprecated", + "Python", + }, + }, + }, + }, + { + Name: "devfileD", + Tags: []string{ + "JS", + "Node.js", + }, + }, + }, + }, + { + name: "Case 7: filter out deprecated stacks with no deprecated stacks", + index: []indexSchema.Schema{ + { + Name: "devfileC", + Tags: []string{ + "Python", + "Flask", + }, + Versions: []indexSchema.Version{ + { + Version: "1.2.0", + Default: true, + Tags: []string{ + "Python", + "Flask", + }, + }, + { + Version: "1.0.0", + Tags: []string{ + "Deprecated", + "Python", + }, + }, + }, + }, + { + Name: "devfileD", + Tags: []string{ + "JS", + "Node.js", + }, + }, + }, + wantIndex: []indexSchema.Schema{ + { + Name: "devfileC", + Tags: []string{ + "Python", + "Flask", + }, + Versions: []indexSchema.Version{ + { + Version: "1.2.0", + Default: true, + Tags: []string{ + "Python", + "Flask", + }, + }, + { + Version: "1.0.0", + Tags: []string{ + "Deprecated", + "Python", + }, + }, + }, + }, + { + Name: "devfileD", + Tags: []string{ + "JS", + "Node.js", + }, + }, + }, + }, + { + name: "Case 8: filter out deprecated stacks with empty index schema", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + filteredIndex := deepcopy.Copy(test.index).([]indexSchema.Schema) + FilterDevfileDeprecated(&filteredIndex, test.deprecated, test.v1Index) + + if !reflect.DeepEqual(filteredIndex, test.wantIndex) { + t.Errorf("\nExpected: %v\nGot: %v", test.wantIndex, filteredIndex) } - if !reflect.DeepEqual(gotIndex, test.wantIndex) { - t.Errorf("Got: %v, Expected: %v", gotIndex, test.wantIndex) + }) + } +} + +func TestFilterDevfileStrArrayField(t *testing.T) { + tests := []filterDevfileStrArrayFieldTestCase{} + tests = append(tests, filterAttributeNamesTestCases...) + tests = append(tests, filterArchitecturesTestCases...) + tests = append(tests, filterTagsTestCases...) + tests = append(tests, filterResourcesTestCases...) + tests = append(tests, filterStarterProjectsTestCases...) + tests = append(tests, filterLinksTestCases...) + tests = append(tests, filterCommandGroupsTestCases...) + tests = append(tests, filterGitRemoteNamesTestCases...) + tests = append(tests, filterGitRemotesTestCases...) + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + gotResult := FilterDevfileStrArrayField(test.Index, test.FieldName, test.Values, test.V1Index) + if gotResult.Error != nil { + t.Errorf("Unexpected error: %v", gotResult.Error) + } else if !reflect.DeepEqual(gotResult.Index, test.WantIndex) { + t.Errorf("Got: %v, Expected: %v", gotResult.Index, test.WantIndex) + } + }) + } +} + +func TestFilterDevfileStrField(t *testing.T) { + tests := []filterDevfileStrFieldTestCase{} + tests = append(tests, filterNameFieldTestCases...) + tests = append(tests, filterDisplayNameFieldTestCases...) + tests = append(tests, filterDescriptionFieldTestCases...) + tests = append(tests, filterIconFieldTestCases...) + tests = append(tests, filterProjectTypeFieldTestCases...) + tests = append(tests, filterLanguageFieldTestCases...) + tests = append(tests, filterVersionFieldTestCases...) + tests = append(tests, filterSchemaVersionFieldTestCases...) + tests = append(tests, filterDefaultFieldTestCases...) + tests = append(tests, filterGitUrlFieldTestCases...) + tests = append(tests, filterGitRemoteNameFieldTestCases...) + tests = append(tests, filterGitSubDirFieldTestCases...) + tests = append(tests, filterGitRevisionFieldTestCases...) + tests = append(tests, filterProviderFieldTestCases...) + tests = append(tests, filterSupportUrlFieldTestCases...) + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + gotResult := FilterDevfileStrField(test.Index, test.FieldName, test.Value, test.V1Index) + if !test.WantErr && gotResult.Error != nil { + t.Errorf("Unexpected error: %v", gotResult.Error) + } else if !test.WantErr && !reflect.DeepEqual(gotResult.Index, test.WantIndex) { + t.Errorf("Got: %v, Expected: %v", gotResult.Index, test.WantIndex) + } else if test.WantErr && !strings.HasPrefix(gotResult.Error.Error(), test.WantErrStr) { + t.Errorf("Got: %v, Expected: %v", gotResult.Error.Error(), test.WantErrStr) + } + }) + } +} + +func TestAndFilter(t *testing.T) { + tests := []struct { + name string + filters []*FilterResult + wantIndex []indexSchema.Schema + wantNotEval bool + wantErr bool + wantErrStr string + }{ + { + name: "Test Valid And with same results", + filters: []*FilterResult{ + { + Index: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + }, + { + Name: "python", + DisplayName: "Python Flask", + Description: "Python Flask project", + }, + }, + }, + { + Index: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + }, + { + Name: "python", + DisplayName: "Python Flask", + Description: "Python Flask project", + }, + }, + }, + }, + wantIndex: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + }, + { + Name: "python", + DisplayName: "Python Flask", + Description: "Python Flask project", + }, + }, + }, + { + name: "Test Valid And with same results v2", + filters: []*FilterResult{ + { + Index: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Go Gin Project", + Tags: []string{ + "Go", + "Gin", + }, + }, + }, + }, + { + Name: "python", + DisplayName: "Python Flask", + Description: "Python Flask project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Python Flask project", + Tags: []string{ + "Python", + "Flask", + }, + }, + { + Version: "1.1.0", + SchemaVersion: "2.2.0", + Description: "Python Flask backend-web project", + Tags: []string{ + "Python", + "Flask", + }, + }, + }, + }, + }, + }, + { + Index: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Go Gin Project", + Tags: []string{ + "Go", + "Gin", + }, + }, + }, + }, + { + Name: "python", + DisplayName: "Python Flask", + Description: "Python Flask project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Python Flask project", + Tags: []string{ + "Python", + "Flask", + }, + }, + { + Version: "1.1.0", + SchemaVersion: "2.2.0", + Description: "Python Flask backend-web project", + Tags: []string{ + "Python", + "Flask", + }, + }, + }, + }, + }, + }, + }, + wantIndex: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Go Gin Project", + Tags: []string{ + "Go", + "Gin", + }, + }, + }, + }, + { + Name: "python", + DisplayName: "Python Flask", + Description: "Python Flask project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Python Flask project", + Tags: []string{ + "Python", + "Flask", + }, + }, + { + Version: "1.1.0", + SchemaVersion: "2.2.0", + Description: "Python Flask backend-web project", + Tags: []string{ + "Python", + "Flask", + }, + }, + }, + }, + }, + }, + { + name: "Test Valid And with overlapping results", + filters: []*FilterResult{ + { + Index: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + }, + { + Name: "python", + DisplayName: "Python Flask", + Description: "Python Flask project", + }, + }, + }, + { + Index: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + }, + }, + }, + }, + wantIndex: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + }, + }, + }, + { + name: "Test Valid And with overlapping results v2", + filters: []*FilterResult{ + { + Index: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Go Gin Project", + Tags: []string{ + "Go", + "Gin", + }, + }, + }, + }, + }, + }, + { + Index: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Go Gin Project", + Tags: []string{ + "Go", + "Gin", + }, + }, + }, + }, + { + Name: "python", + DisplayName: "Python Flask", + Description: "Python Flask project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Python Flask project", + Tags: []string{ + "Python", + "Flask", + }, + }, + { + Version: "1.1.0", + SchemaVersion: "2.2.0", + Description: "Python Flask backend-web project", + Tags: []string{ + "Python", + "Flask", + }, + }, + }, + }, + }, + }, + }, + wantIndex: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Go Gin Project", + Tags: []string{ + "Go", + "Gin", + }, + }, + }, + }, + }, + }, + { + name: "Test Valid And with overlapping results and versions v2", + filters: []*FilterResult{ + { + Index: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Go Gin Project", + Tags: []string{ + "Go", + "Gin", + }, + }, + }, + }, + }, + }, + { + Index: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Go Gin Project", + Tags: []string{ + "Go", + "Gin", + }, + }, + { + Version: "2.0.0", + SchemaVersion: "2.2.0", + Description: "Go Gin Backend Project", + Tags: []string{ + "Go", + "Gin", + "MySQL", + }, + }, + }, + }, + { + Name: "python", + DisplayName: "Python Flask", + Description: "Python Flask project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Python Flask project", + Tags: []string{ + "Python", + "Flask", + }, + }, + { + Version: "1.1.0", + SchemaVersion: "2.2.0", + Description: "Python Flask backend-web project", + Tags: []string{ + "Python", + "Flask", + }, + }, + }, + }, + }, + }, + }, + wantIndex: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Go Gin Project", + Tags: []string{ + "Go", + "Gin", + }, + }, + }, + }, + }, + }, + { + name: "Test Valid And with no results", + filters: []*FilterResult{}, + wantIndex: []indexSchema.Schema{}, + }, + { + name: "Test Invalid And with single error", + filters: []*FilterResult{ + { + Error: fmt.Errorf("A test error"), + }, + }, + wantIndex: []indexSchema.Schema{}, + wantErr: true, + wantErrStr: "A test error", + }, + { + name: "Test Invalid And with multiple errors", + filters: []*FilterResult{ + { + Error: fmt.Errorf("First test error"), + }, + { + Error: fmt.Errorf("Second test error"), + }, + { + Error: fmt.Errorf("Third test error"), + }, + }, + wantIndex: []indexSchema.Schema{}, + wantErr: true, + wantErrStr: "First test error", + }, + { + name: "Test Invalid And with valid filters and errors", + filters: []*FilterResult{ + { + Index: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + }, + { + Name: "python", + DisplayName: "Python Flask", + Description: "Python Flask project", + }, + }, + }, + { + Error: fmt.Errorf("First test error"), + }, + { + Index: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + }, + }, + }, + { + Error: fmt.Errorf("Second test error"), + }, + { + Error: fmt.Errorf("Third test error"), + }, + }, + wantIndex: []indexSchema.Schema{}, + wantErr: true, + wantErrStr: "First test error", + }, + { + name: "Test Invalid And with valid filters and errors v2", + filters: []*FilterResult{ + { + Index: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Go Gin Project", + Tags: []string{ + "Go", + "Gin", + }, + }, + { + Version: "2.0.0", + SchemaVersion: "2.2.0", + Description: "Go Gin Backend Project", + Tags: []string{ + "Go", + "Gin", + "MySQL", + }, + }, + }, + }, + { + Name: "python", + DisplayName: "Python Flask", + Description: "Python Flask project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Python Flask project", + Tags: []string{ + "Python", + "Flask", + }, + }, + { + Version: "1.1.0", + SchemaVersion: "2.2.0", + Description: "Python Flask backend-web project", + Tags: []string{ + "Python", + "Flask", + }, + }, + }, + }, + }, + }, + { + Error: fmt.Errorf("First test error"), + }, + { + Index: []indexSchema.Schema{ + { + Name: "go", + DisplayName: "Go", + Description: "Go Gin Project", + Versions: []indexSchema.Version{ + { + Version: "1.0.0", + SchemaVersion: "2.2.0", + Description: "Go Gin Project", + Tags: []string{ + "Go", + "Gin", + }, + }, + }, + }, + }, + }, + { + Error: fmt.Errorf("Second test error"), + }, + { + Error: fmt.Errorf("Third test error"), + }, + }, + wantIndex: []indexSchema.Schema{}, + wantErr: true, + wantErrStr: "First test error", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + gotResult := AndFilter(test.filters...) + + // sort result and expected as sorting of result is not consistent + sort.SliceStable(gotResult.Index, func(i, j int) bool { + return gotResult.Index[i].Name < gotResult.Index[j].Name + }) + sort.SliceStable(test.wantIndex, func(i, j int) bool { + return test.wantIndex[i].Name < test.wantIndex[j].Name + }) + + if test.wantErr && !strings.Contains(gotResult.Error.Error(), test.wantErrStr) { + t.Errorf("Got: %v, Expected: %v", gotResult.Error.Error(), test.wantErrStr) + } else if !test.wantErr && gotResult.Error != nil { + t.Errorf("Unexpected error: %v", gotResult.Error) + } else if !test.wantErr && !reflect.DeepEqual(gotResult.Index, test.wantIndex) { + t.Errorf("Got: %v, Expected: %v", gotResult.Index, test.wantIndex) } }) } diff --git a/index/server/pkg/util/util.go b/index/server/pkg/util/util.go index 2e1145eee..614024749 100644 --- a/index/server/pkg/util/util.go +++ b/index/server/pkg/util/util.go @@ -23,6 +23,7 @@ import ( "net/http" "net/url" "os" + "reflect" "strconv" "strings" @@ -243,3 +244,52 @@ func MakeVersionMap(devfileIndex indexSchema.Schema) (map[string]indexSchema.Ver versionMap["latest"] = versionMap[latestVersion] return versionMap, nil } + +// StructToMap converts any struct into a map +func StructToMap[T any](s T) map[string]any { + result := make(map[string]interface{}) + + val := reflect.ValueOf(s) + + if val.Kind() == reflect.Pointer { + if val.IsNil() { + return nil + } + val = val.Elem() + } + + structType := val.Type() + for i := 0; i < val.NumField(); i++ { + fieldName := structType.Field(i).Name + field := val.Field(i) + var fieldValueKind reflect.Kind = field.Kind() + var fieldValue interface{} + + if fieldValueKind == reflect.Pointer { + if field.IsNil() { + continue + } + field = field.Elem() + fieldValueKind = field.Kind() + } else if field.IsZero() { + continue + } + + if fieldValueKind == reflect.Struct { + fieldValue = StructToMap(field.Interface()) + } else { + fieldValue = field.Interface() + } + + fieldName = strings.ToLower(string(fieldName[0])) + fieldName[1:] + + result[fieldName] = fieldValue + } + + return result +} + +// StrPtrIsSet checks if string pointer is set to a value +func StrPtrIsSet(ptr *string) bool { + return ptr != nil && *ptr != "" +} diff --git a/index/server/pkg/util/util_test.go b/index/server/pkg/util/util_test.go index 6861b08de..07db7318a 100644 --- a/index/server/pkg/util/util_test.go +++ b/index/server/pkg/util/util_test.go @@ -23,6 +23,7 @@ import ( "testing" indexSchema "github.com/devfile/registry-support/index/generator/schema" + "k8s.io/utils/pointer" ) func TestIsHtmlRequested(t *testing.T) { @@ -396,3 +397,104 @@ func TestMakeVersionMapOnBadVersion(t *testing.T) { } }) } + +func TestStructToMap(t *testing.T) { + tests := []struct { + name string + inputStruct any + wantMap map[string]any + }{ + { + name: "Case 1: Test struct type with map conversion", + inputStruct: struct { + Language string + Stack string + CreationYear int + }{ + Language: "python", + Stack: "flask", + CreationYear: 2011, + }, + wantMap: map[string]any{ + "language": "python", + "stack": "flask", + "creationYear": 2011, + }, + }, + { + name: "Case 2: Test schema type with map conversion", + inputStruct: indexSchema.Schema{ + Name: "go", + DisplayName: "Go", + Description: "A Go project.", + Version: "1.0.0", + Tags: []string{"Go", "Gin"}, + }, + wantMap: map[string]any{ + "name": "go", + "displayName": "Go", + "description": "A Go project.", + "version": "1.0.0", + "tags": []string{"Go", "Gin"}, + }, + }, + { + name: "Case 3: Test pointer fields struct type with map conversion", + inputStruct: struct { + P1 *string + P2 *string + P3 *[]string + P4 *bool + }{ + P1: pointer.String("test"), + P3: &[]string{"test1", "test2"}, + P4: nil, + }, + wantMap: map[string]any{ + "p1": "test", + "p3": []string{"test1", "test2"}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + gotMap := StructToMap(test.inputStruct) + if !reflect.DeepEqual(test.wantMap, gotMap) { + t.Errorf("Got: %v, Expected: %v", gotMap, test.wantMap) + } + }) + } +} + +func TestStrPtrIsSet(t *testing.T) { + tests := []struct { + name string + ptr *string + want bool + }{ + { + name: "Case 1: string pointer is set", + ptr: pointer.String("test"), + want: true, + }, + { + name: "Case 2: string is blank", + ptr: pointer.String(""), + want: false, + }, + { + name: "Case 3: string is nil", + want: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := StrPtrIsSet(test.ptr) + if got != test.want { + t.Errorf("Got: %v, Expected: %v", got, test.want) + } + }) + } +} diff --git a/index/server/registry-REST-API.adoc b/index/server/registry-REST-API.adoc index 56b6d4790..722bbccff 100644 --- a/index/server/registry-REST-API.adoc +++ b/index/server/registry-REST-API.adoc @@ -323,6 +323,119 @@ curl http://devfile-registry.192.168.1.1.nip.io/index ] ---- +=== Query parameters +[cols="1,1"] +|=== +|Parameter|Description + +|Name +|Search string to filter stacks by their name + +|DisplayName +|Search string to filter stacks by their display names + +|Description +|Search string to filter stacks by the description text + +|AttributeNames +|Collection of search strings to filter stacks by the names of defined free-form attributes + +|Arch +|Collection of search strings to filter stacks by their architectures + +|Tags +|Collection of search strings to filter stacks by their tags + +|Icon +|Toggle on encoding content passed + +|IconUri +|Search string to filter stacks by their icon uri + +|ProjectType +|Search string to filter stacks by their project type + +|Language +|Search string to filter stacks by their programming language + +|Resources +|Collection of search strings to filter stacks by their resource files + +|StarterProjects +|Collection of search strings to filter stacks by the names of the starter projects + +|LinkNames +|Collection of search strings to filter stacks by the names of the link sources + +|Links +|Collection of search strings to filter stacks by their link sources + +|GitRemoteNames +|Collection of search strings to filter stacks by the names of the git remotes + +|GitRemotes +|Collection of search strings to filter stacks by the URIs of the git remotes + +|GitUrl +|Search string to filter stacks by their git urls + +|GitRemoteName +|Search string to filter stacks by their git remote name + +|GitSubDir +|Search string to filter stacks by their target subdirectory of the git repository + +|GitRevision +|Search string to filter stacks by their git revision + +|Provider +|Search string to filter stacks by the stack provider + +|SupportUrl +|Search string to filter stacks by their given support url + +|=== + +=== Request example +.... +curl 'http://devfile-registry.192.168.1.1.nip.io/index?provider=Red%22Hat&resources=.zip' +.... + +*Note:* Field filter parameters accept fuzzy search strings, meaning if `.zip` is passed into `resources` parameter, the results will be any stack with resource entries which contain `.zip` as partial matches. + +=== Response example +[source,json] +---- +[ + { + "name": "go", + "version": "1.2.0", + "displayName": "Go Runtime", + "description": "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.", + "type": "stack", + "tags": [ + "testtag" + ], + "icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg", + "projectType": "Go", + "language": "Go", + "links": { + "self": "devfile-catalog/go:1.2.0" + }, + "resources": [ + "devfile.yaml", + "go-starter-offline.zip" + ], + "starterProjects": [ + "go-starter", + "go-starter-offline" + ], + "provider": "Red Hat" + } +] +---- + + == Gets registry index of sample devfile type Gets the registry index file content of sample devfile type from HTTP response @@ -427,6 +540,164 @@ curl http://devfile-registry.192.168.1.1.nip.io/index/sample ] --- +=== Query parameters +[cols="1,1"] +|=== +|Parameter|Description + +|Name +|Search string to filter samples by their name + +|DisplayName +|Search string to filter samples by their display names + +|Description +|Search string to filter samples by the description text + +|AttributeNames +|Collection of search strings to filter samples by the names of defined free-form attributes + +|Arch +|Collection of search strings to filter samples by their architectures + +|Tags +|Collection of search strings to filter samples by their tags + +|Icon +|Toggle on encoding content passed + +|IconUri +|Search string to filter samples by their icon uri + +|ProjectType +|Search string to filter samples by their project type + +|Language +|Search string to filter samples by their programming language + +|Resources +|Collection of search strings to filter samples by their resource files + +|StarterProjects +|Collection of search strings to filter samples by the names of the starter projects + +|LinkNames +|Collection of search strings to filter samples by the names of the link sources + +|Links +|Collection of search strings to filter samples by their link sources + +|GitRemoteNames +|Collection of search strings to filter samples by the names of the git remotes + +|GitRemotes +|Collection of search strings to filter samples by the URIs of the git remotes + +|GitUrl +|Search string to filter samples by their git urls + +|GitRemoteName +|Search string to filter samples by their git remote name + +|GitSubDir +|Search string to filter samples by their target subdirectory of the git repository + +|GitRevision +|Search string to filter samples by their git revision + +|Provider +|Search string to filter samples by the stack provider + +|SupportUrl +|Search string to filter samples by their given support url + +|=== + +=== Request example +.... +curl 'http://devfile-registry.192.168.1.1.nip.io/index/sample?description=Hello%22World' +.... + +*Note:* Field filter parameters accept fuzzy search strings, meaning if `Hello World` is passed into `description` parameter, the results will be any sample with resource entries which contain `Hello World` as partial matches. + +=== Response example +[source,json] +---- +[ + { + "name": "python-basic", + "displayName": "Basic Python", + "description": "A simple Hello World application using Python", + "type": "sample", + "tags": [ + "Python" + ], + "icon": "https://www.python.org/static/community_logos/python-logo-generic.svg", + "projectType": "python", + "language": "python", + "git": { + "remotes": { + "origin": "https://github.com/devfile-samples/devfile-sample-python-basic.git" + } + } + }, + { + "name": "nodejs-basic", + "displayName": "Basic Node.js", + "description": "A simple Hello World Node.js application", + "type": "sample", + "tags": [ + "NodeJS", + "Express" + ], + "icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/node-js.svg", + "projectType": "nodejs", + "language": "nodejs", + "git": { + "remotes": { + "origin": "https://github.com/nodeshift-starters/devfile-sample.git" + } + } + }, + { + "name": "code-with-quarkus", + "displayName": "Basic Quarkus", + "description": "A simple Hello World Java application using Quarkus", + "type": "sample", + "tags": [ + "Java", + "Quarkus" + ], + "icon": "https://design.jboss.org/quarkus/logo/final/SVG/quarkus_icon_rgb_default.svg", + "projectType": "quarkus", + "language": "java", + "git": { + "remotes": { + "origin": "https://github.com/devfile-samples/devfile-sample-code-with-quarkus.git" + } + } + }, + { + "name": "java-springboot-basic", + "displayName": "Basic Spring Boot", + "description": "A simple Hello World Java Spring Boot application using Maven", + "type": "sample", + "tags": [ + "Java", + "Spring" + ], + "icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/spring.svg", + "projectType": "springboot", + "language": "java", + "git": { + "remotes": { + "origin": "https://github.com/devfile-samples/devfile-sample-java-springboot-basic.git" + } + } + } +] +---- + == Gets registry index of all devfile types Gets the registry index file content of all devfile types from HTTP response @@ -789,6 +1060,141 @@ curl http://devfile-registry.192.168.1.1.nip.io/index/all ] ---- +=== Query parameters +[cols="1,1"] +|=== +|Parameter|Description + +|Name +|Search string to filter stacks/samples by their name + +|DisplayName +|Search string to filter stacks/samples by their display names + +|Description +|Search string to filter stacks/samples by the description text + +|AttributeNames +|Collection of search strings to filter stacks/samples by the names of defined free-form attributes + +|Arch +|Collection of search strings to filter stacks/samples by their architectures + +|Tags +|Collection of search strings to filter stacks/samples by their tags + +|Icon +|Toggle on encoding content passed + +|IconUri +|Search string to filter stacks/samples by their icon uri + +|ProjectType +|Search string to filter stacks/samples by their project type + +|Language +|Search string to filter stacks/samples by their programming language + +|Resources +|Collection of search strings to filter stacks/samples by their resource files + +|StarterProjects +|Collection of search strings to filter stacks/samples by the names of the starter projects + +|LinkNames +|Collection of search strings to filter stacks/samples by the names of the link sources + +|Links +|Collection of search strings to filter stacks/samples by their link sources + +|GitRemoteNames +|Collection of search strings to filter stacks/samples by the names of the git remotes + +|GitRemotes +|Collection of search strings to filter stacks/samples by the URIs of the git remotes + +|GitUrl +|Search string to filter stacks/samples by their git urls + +|GitRemoteName +|Search string to filter stacks/samples by their git remote name + +|GitSubDir +|Search string to filter stacks/samples by their target subdirectory of the git repository + +|GitRevision +|Search string to filter stacks/samples by their git revision + +|Provider +|Search string to filter stacks/samples by the stack provider + +|SupportUrl +|Search string to filter stacks/samples by their given support url + +|=== + +=== Request example +.... +curl 'http://devfile-registry.192.168.1.1.nip.io/index/all?description=node' +.... + +*Note:* Field filter parameters accept fuzzy search strings, meaning if `node` is passed into `description` parameter, the results will be any stack or sample with resource entries which contain `node` as partial matches. + +=== Response example +[source,json] +---- +[ + { + "name": "nodejs-basic", + "displayName": "Basic Node.js", + "description": "A simple Hello World Node.js application", + "type": "sample", + "tags": [ + "NodeJS", + "Express" + ], + "icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/node-js.svg", + "projectType": "nodejs", + "language": "nodejs", + "git": { + "remotes": { + "origin": "https://github.com/nodeshift-starters/devfile-sample.git" + } + } + }, + { + "name": "nodejs", + "version": "1.0.0", + "displayName": "NodeJS Runtime", + "description": "Stack with NodeJS 12", + "type": "stack", + "tags": [ + "NodeJS", + "Express", + "ubi8" + ], + "architectures": [ + "amd64", + "arm64" + ], + "icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/node-js.svg", + "projectType": "nodejs", + "language": "nodejs", + "links": { + "self": "devfile-catalog/nodejs:1.0.0" + }, + "resources": [ + "devfile.yaml" + ], + "starterProjects": [ + "nodejs-starter" + ], + "provider": "Red Hat", + "supportUrl": "http://testurl/support.md" + } +] +---- + == Gets registry v2 index of stack devfile type Gets the registry v2 index file content of stack devfile type, which contains versions information, from HTTP response @@ -1045,11 +1451,17 @@ curl http://devfile-registry.192.168.1.1.nip.io/v2index ] ---- -=== Query parameters +=== Query (range) parameters [cols="1,1"] |=== |Parameter|Description +|MaxVersion +|The maximum stack version + +|MinVersion +|The minimum stack version + |MaxSchemaVersion |The maximum devfile schema version @@ -1159,8 +1571,205 @@ curl http://devfile-registry.192.168.1.1.nip.io/v2index?minSchemaVersion=2.1&max ] ---- -== Gets registry v2 index of sample devfile type -Gets the registry v2 index file content of sample devfile type, which contains versions information, from HTTP response +=== Query (field) parameters +[cols="1,1"] +|=== +|Parameter|Description + +|Name +|Search string to filter stacks by their name + +|DisplayName +|Search string to filter stacks by their display names + +|Description +|Search string to filter stacks by the description text + +|AttributeNames +|Collection of search strings to filter stacks by the names of defined free-form attributes + +|Arch +|Collection of search strings to filter stacks by their architectures + +|Tags +|Collection of search strings to filter stacks by their tags + +|Icon +|Toggle on encoding content passed + +|IconUri +|Search string to filter stacks by their icon uri + +|ProjectType +|Search string to filter stacks by their project type + +|Language +|Search string to filter stacks by their programming language + +|Default +|Boolean to filter stacks if they are default or not + +|Resources +|Collection of search strings to filter stacks by their resource files + +|StarterProjects +|Collection of search strings to filter stacks by the names of the starter projects + +|LinkNames +|Collection of search strings to filter stacks by the names of the link sources + +|Links +|Collection of search strings to filter stacks by their link sources + +|CommandGroups +|Collection of search strings to filter stacks by their present command groups + +|GitRemoteNames +|Collection of search strings to filter stacks by the names of the git remotes + +|GitRemotes +|Collection of search strings to filter stacks by the URIs of the git remotes + +|GitUrl +|Search string to filter stacks by their git urls + +|GitRemoteName +|Search string to filter stacks by their git remote name + +|GitSubDir +|Search string to filter stacks by their target subdirectory of the git repository + +|GitRevision +|Search string to filter stacks by their git revision + +|Provider +|Search string to filter stacks by the stack provider + +|SupportUrl +|Search string to filter stacks by their given support url + +|=== + +=== Request example +.... +curl 'http://devfile-registry.192.168.1.1.nip.io/v2index?name=java&default=true' +.... + +*Note:* Field filter parameters accept fuzzy search strings, meaning if `java` is passed into `name` parameter, the results will be any stack with resource entries which contain `java` as partial matches. + +=== Response example +[source,json] +---- +[ + { + "name": "java-maven", + "displayName": "Maven Java", + "description": "Upstream Maven and OpenJDK 11", + "type": "stack", + "tags": [ + "Java", + "Maven" + ], + "architectures": [ + "amd64", + "arm64", + "s390x" + ], + "icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/java-maven.jpg", + "projectType": "maven", + "language": "java", + "versions": [ + { + "version": "1.1.0", + "schemaVersion": "2.1.0", + "default": true, + "description": "Upstream Maven and OpenJDK 11", + "tags": [ + "Java", + "Maven" + ], + "architectures": [ + "amd64", + "arm64", + "s390x" + ], + "icon": "https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/java-maven.jpg", + "links": { + "self": "devfile-catalog/java-maven:1.1.0" + }, + "commandGroups": { + "build": true, + "debug": true, + "deploy": false, + "run": true, + "test": false + }, + "resources": [ + "devfile.yaml" + ], + "starterProjects": [ + "springbootproject-offline", + "springbootproject" + ] + } + ] + }, + { + "name": "java-quarkus", + "displayName": "Quarkus Java", + "description": "Quarkus with Java", + "type": "stack", + "tags": [ + "Java", + "Quarkus" + ], + "architectures": [ + "amd64" + ], + "icon": "https://design.jboss.org/quarkus/logo/final/SVG/quarkus_icon_rgb_default.svg", + "projectType": "quarkus", + "language": "java", + "versions": [ + { + "version": "1.1.0", + "schemaVersion": "2.0.0", + "default": true, + "description": "Quarkus with Java", + "tags": [ + "Java", + "Quarkus" + ], + "architectures": [ + "amd64" + ], + "icon": "https://design.jboss.org/quarkus/logo/final/SVG/quarkus_icon_rgb_default.svg", + "links": { + "self": "devfile-catalog/java-quarkus:1.1.0" + }, + "commandGroups": { + "build": false, + "debug": true, + "deploy": false, + "run": true, + "test": false + }, + "resources": [ + "community-offline.zip", + "devfile.yaml" + ], + "starterProjects": [ + "community-offline", + "community", + "redhat-product" + ] + } + ] + } +] +---- + +== Gets registry v2 index of sample devfile type +Gets the registry v2 index file content of sample devfile type, which contains versions information, from HTTP response === HTTP request .... @@ -1254,11 +1863,17 @@ curl http://devfile-registry.192.168.1.1.nip.io/v2index/sample ] ---- -=== Query parameters +=== Query (range) parameters [cols="1,1"] |=== |Parameter|Description +|MaxVersion +|The maximum sample version + +|MinVersion +|The minimum sample version + |MaxSchemaVersion |The maximum devfile schema version @@ -1303,6 +1918,125 @@ curl http://devfile-registry.192.168.1.1.nip.io/v2index/sample?maxSchemaVersion= ] ---- +=== Query (field) parameters +[cols="1,1"] +|=== +|Parameter|Description + +|Name +|Search string to filter samples by their name + +|DisplayName +|Search string to filter samples by their display names + +|Description +|Search string to filter samples by the description text + +|AttributeNames +|Collection of search strings to filter samples by the names of defined free-form attributes + +|Arch +|Collection of search strings to filter samples by their architectures + +|Tags +|Collection of search strings to filter samples by their tags + +|Icon +|Toggle on encoding content passed + +|IconUri +|Search string to filter samples by their icon uri + +|ProjectType +|Search string to filter samples by their project type + +|Language +|Search string to filter samples by their programming language + +|Default +|Boolean to filter samples if they are default or not + +|Resources +|Collection of search strings to filter samples by their resource files + +|StarterProjects +|Collection of search strings to filter samples by the names of the starter projects + +|LinkNames +|Collection of search strings to filter samples by the names of the link sources + +|Links +|Collection of search strings to filter samples by their link sources + +|CommandGroups +|Collection of search strings to filter samples by their present command groups + +|GitRemoteNames +|Collection of search strings to filter samples by the names of the git remotes + +|GitRemotes +|Collection of search strings to filter samples by the URIs of the git remotes + +|GitUrl +|Search string to filter samples by their git urls + +|GitRemoteName +|Search string to filter samples by their git remote name + +|GitSubDir +|Search string to filter samples by their target subdirectory of the git repository + +|GitRevision +|Search string to filter samples by their git revision + +|Provider +|Search string to filter samples by the stack provider + +|SupportUrl +|Search string to filter samples by their given support url + +|=== + +=== Request example +.... +curl 'http://devfile-registry.192.168.1.1.nip.io/v2index/sample?description=java&default=true' +.... + +*Note:* Field filter parameters accept fuzzy search strings, meaning if `java` is passed into `description` parameter, the results will be any sample with resource entries which contain `java` as partial matches. + +=== Response example +[source,json] +---- +[ + { + "name": "code-with-quarkus", + "displayName": "Basic Quarkus", + "description": "A simple Hello World Java application using Quarkus", + "type": "sample", + "tags": [ + "Java", + "Quarkus" + ], + "icon": "https://design.jboss.org/quarkus/logo/final/SVG/quarkus_icon_rgb_default.svg", + "projectType": "quarkus", + "language": "java", + "versions": [ + { + "version": "1.1.0", + "schemaVersion": "2.2.0", + "default": true, + "git": { + "remotes": { + "origin": "https://github.com/devfile-samples/devfile-sample-code-with-quarkus.git" + } + }, + "description": "java quarkus with devfile v2.2.0" + } + ] + } +] +---- + == Gets registry v2 index of all devfile types Gets the registry v2 index file content of all devfile types, which contains versions information, from HTTP response @@ -1656,11 +2390,17 @@ curl http://devfile-registry.192.168.1.1.nip.io/v2index/all ] ---- -=== Query parameters +=== Query (range) parameters [cols="1,1"] |=== |Parameter|Description +|MaxVersion +|The maximum stack/sample version + +|MinVersion +|The minimum stack/sample version + |MaxSchemaVersion |The maximum devfile schema version @@ -1822,6 +2562,176 @@ curl http://devfile-registry.192.168.1.1.nip.io/v2index/sample?minSchemaVersion= ] ---- +=== Query (field) parameters +[cols="1,1"] +|=== +|Parameter|Description + +|Name +|Search string to filter stacks/samples by their name + +|DisplayName +|Search string to filter stacks/samples by their display names + +|Description +|Search string to filter stacks/samples by the description text + +|AttributeNames +|Collection of search strings to filter stacks/samples by the names of defined free-form attributes + +|Arch +|Collection of search strings to filter stacks/samples by their architectures + +|Tags +|Collection of search strings to filter stacks/samples by their tags + +|Icon +|Toggle on encoding content passed + +|IconUri +|Search string to filter stacks/samples by their icon uri + +|ProjectType +|Search string to filter stacks/samples by their project type + +|Language +|Search string to filter stacks/samples by their programming language + +|Default +|Boolean to filter stacks/samples if they are default or not + +|Resources +|Collection of search strings to filter stacks/samples by their resource files + +|StarterProjects +|Collection of search strings to filter stacks/samples by the names of the starter projects + +|LinkNames +|Collection of search strings to filter stacks/samples by the names of the link sources + +|Links +|Collection of search strings to filter stacks/samples by their link sources + +|CommandGroups +|Collection of search strings to filter stacks/samples by their present command groups + +|GitRemoteNames +|Collection of search strings to filter stacks/samples by the names of the git remotes + +|GitRemotes +|Collection of search strings to filter stacks/samples by the URIs of the git remotes + +|GitUrl +|Search string to filter stacks/samples by their git urls + +|GitRemoteName +|Search string to filter stacks/samples by their git remote name + +|GitSubDir +|Search string to filter stacks/samples by their target subdirectory of the git repository + +|GitRevision +|Search string to filter stacks/samples by their git revision + +|Provider +|Search string to filter stacks/samples by the stack provider + +|SupportUrl +|Search string to filter stacks/samples by their given support url + +|=== + +=== Request example +.... +curl 'http://devfile-registry.192.168.1.1.nip.io/v2index/all?description=java&default=true' +.... + +*Note:* Field filter parameters accept fuzzy search strings, meaning if `java` is passed into `description` parameter, the results will be any stack or sample with resource entries which contain `java` as partial matches. + +=== Response example +[source,json] +---- +[ + { + "name": "java-quarkus", + "displayName": "Quarkus Java", + "description": "Quarkus with Java", + "type": "stack", + "tags": [ + "Java", + "Quarkus" + ], + "architectures": [ + "amd64" + ], + "icon": "https://design.jboss.org/quarkus/logo/final/SVG/quarkus_icon_rgb_default.svg", + "projectType": "quarkus", + "language": "java", + "versions": [ + { + "version": "1.1.0", + "schemaVersion": "2.0.0", + "default": true, + "description": "Quarkus with Java", + "tags": [ + "Java", + "Quarkus" + ], + "architectures": [ + "amd64" + ], + "icon": "https://design.jboss.org/quarkus/logo/final/SVG/quarkus_icon_rgb_default.svg", + "links": { + "self": "devfile-catalog/java-quarkus:1.1.0" + }, + "commandGroups": { + "build": false, + "debug": true, + "deploy": false, + "run": true, + "test": false + }, + "resources": [ + "community-offline.zip", + "devfile.yaml" + ], + "starterProjects": [ + "community-offline", + "community", + "redhat-product" + ] + } + ] + }, + { + "name": "code-with-quarkus", + "displayName": "Basic Quarkus", + "description": "A simple Hello World Java application using Quarkus", + "type": "sample", + "tags": [ + "Java", + "Quarkus" + ], + "icon": "https://design.jboss.org/quarkus/logo/final/SVG/quarkus_icon_rgb_default.svg", + "projectType": "quarkus", + "language": "java", + "versions": [ + { + "version": "1.1.0", + "schemaVersion": "2.2.0", + "default": true, + "git": { + "remotes": { + "origin": "https://github.com/devfile-samples/devfile-sample-code-with-quarkus.git" + } + }, + "description": "java quarkus with devfile v2.2.0" + } + ] + } +] +---- + == Gets registry stack devfile Gets the specific registry stack devfile content from HTTP response diff --git a/index/server/vendor/github.com/hashicorp/go-set/.gitignore b/index/server/vendor/github.com/hashicorp/go-set/.gitignore new file mode 100644 index 000000000..aa88329c1 --- /dev/null +++ b/index/server/vendor/github.com/hashicorp/go-set/.gitignore @@ -0,0 +1,23 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go work files +go.work +go.work.sum + +# IDE files +.idea +.fleet diff --git a/index/server/vendor/github.com/hashicorp/go-set/.golangci.yaml b/index/server/vendor/github.com/hashicorp/go-set/.golangci.yaml new file mode 100644 index 000000000..46e52ee9d --- /dev/null +++ b/index/server/vendor/github.com/hashicorp/go-set/.golangci.yaml @@ -0,0 +1,17 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +run: + timeout: 5m +linters: + enable: + - gofmt + - errcheck + - errname + - errorlint + - bodyclose + - durationcheck + - exhaustive + - goconst + - whitespace + diff --git a/index/server/vendor/github.com/hashicorp/go-set/LICENSE b/index/server/vendor/github.com/hashicorp/go-set/LICENSE new file mode 100644 index 000000000..1786756f5 --- /dev/null +++ b/index/server/vendor/github.com/hashicorp/go-set/LICENSE @@ -0,0 +1,375 @@ +Copyright (c) 2022 HashiCorp, Inc. + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/index/server/vendor/github.com/hashicorp/go-set/README.md b/index/server/vendor/github.com/hashicorp/go-set/README.md new file mode 100644 index 000000000..5d4dacff7 --- /dev/null +++ b/index/server/vendor/github.com/hashicorp/go-set/README.md @@ -0,0 +1,228 @@ +# go-set + +[![Go Reference](https://pkg.go.dev/badge/github.com/hashicorp/go-set.svg)](https://pkg.go.dev/github.com/hashicorp/go-set) +[![Run CI Tests](https://github.com/hashicorp/go-set/actions/workflows/ci.yaml/badge.svg)](https://github.com/hashicorp/go-set/actions/workflows/ci.yaml) +[![GitHub](https://img.shields.io/github/license/hashicorp/go-set)](LICENSE) + +The `go-set` repository provides a `set` package containing a few +generic [Set](https://en.wikipedia.org/wiki/Set) implementations for Go. + +Each implementation is optimal for a particular use case. + +`Set` is ideal for `comparable` types. + - backed by `map` builtin + - commonly used with `string`, `int`, simple `struct` types, etc. + +`HashSet` is useful for types that implement a `Hash()` function. + - backed by `map` builtin + - commonly used with complex structs + +`TreeSet` is useful for comparable data (via `Compare[T]`) + - backed by Red-Black Binary Search Tree + - commonly used with complex structs with extrinsic order + - efficient iteration in sort order + - additional methods `Min` / `Max` / `TopK` / `BottomK` + +This package is not thread-safe. + +# Documentation + +The full documentation is available on [pkg.go.dev](https://pkg.go.dev/github.com/hashicorp/go-set). + +# Motivation + +Package `set` helps reduce the boiler plate of using a `map[]struct{}` as a set. + +Say we want to de-duplicate a slice of strings +```go +items := []string{"mitchell", "armon", "jack", "dave", "armon", "dave"} +``` + +A typical example of the classic way using `map` built-in: +```go +m := make(map[string]struct{}) +for _, item := range items { + m[item] = struct{}{} +} +list := make([]string, 0, len(items)) +for k := range m { + list = append(list, k) +} +``` + +The same result, but in one line using package `go-set`. +```go +list := set.From[string](items).Slice() +``` + +# Set + +The `go-set` package includes `Set` for types that satisfy the `comparable` constraint. +Note that complex structs (i.e. is a pointer or contains pointer fields) will not be +"comparable" in the sense of deep equality, but rather in the sense of pointer addresses. +The `Set` type should be used with builtin types like `string`, `int`, or simple struct +types with no pointers. + +# HashSet + +The `go-set` package includes `HashSet` for types that implement a `Hash()` function. +The custom type must satisfy `HashFunc[H Hash]` - essentially any `Hash()` function +that returns a `string` or `integer`. This enables types to use string-y hash +functions like `md5`, `sha1`, or even `GoString()`, but also enables types to +implement an efficient hash function using a hash code based on prime multiples. + +# TreeSet + +The `go-set` package includes `TreeSet` for creating sorted sets. A `TreeSet` may +be used with any type `T` as the comparison between elements is provided by implementing +`Compare[T]`. The `Cmp[builtin]` helper provides a convenient implementation of +`Compare` for `builtin` types like `string` or `int`. A `TreeSet` is backed by +an underlying balanced binary search tree, making operations like in-order traversal +efficient, in addition to enabling functions like `Min()`, `Max()`, `TopK()`, and +`BottomK()`. + + +### Methods + +Implements the following set operations + +- Insert +- InsertSlice +- InsertSet +- Remove +- RemoveSlice +- RemoveSet +- RemoveFunc +- Contains +- ContainsAll +- ContainsSlice +- Subset +- Size +- Empty +- Union +- Difference +- Intersect + +Provides helper methods + +- Equal +- Copy +- Slice +- String + +TreeSet helper methods +- Min +- Max +- TopK +- BottomK +- FirstAbove +- FirstAboveEqual +- Above +- AboveEqual +- FirstBelow +- FirstBelowEqual +- Below +- BelowEqual + +# Install + +``` +go get github.com/hashicorp/go-set@latest +``` + +``` +import "github.com/hashicorp/go-set" +``` + +# Set Examples + +Below are simple example usages of `Set` + +```go +s := set.New[int](10) +s.Insert(1) +s.InsertSlice([]int{2, 3, 4}) +s.Size() +``` + +```go +s := set.From[string]([]string{"one", "two", "three"}) +s.Contains("three") +s.Remove("one") +``` + + +```go +a := set.From[int]([]int{2, 4, 6, 8}) +b := set.From[int]([]int{4, 5, 6}) +a.Intersect(b) +``` + +# HashSet Examples + +Below are simple example usages of `HashSet` + +(using a hash code) +```go +type inventory struct { + item int + serial int +} + +func (i *inventory) Hash() int { + code := 3 * item * 5 * serial + return code +} + +i1 := &inventory{item: 42, serial: 101} + +s := set.NewHashSet[*inventory, int](10) +s.Insert(i1) +``` + +(using a string hash) +```go +type employee struct { + name string + id int +} + +func (e *employee) Hash() string { + return fmt.Sprintf("%s:%d", e.name, e.id) +} + +e1 := &employee{name: "armon", id: 2} + +s := set.NewHashSet[*employee, string](10) +s.Insert(e1) +``` + +# TreeSet Examples + +Below are simple example usages of `TreeSet` + +(using `Cmp` as `Compare`) + +```go +ts := NewTreeSet[int, Compare[int]](Cmp[int]) +ts.Insert(5) +``` + +(using custom `Compare`) + +```go +type waypoint struct { + distance int + name string +} + +cmp := func(w1, w2 *waypoint) int { + return w1.distance - w2.distance +} + +ts := NewTreeSet[*waypoint, Compare[*waypoint]](cmp) +ts.Insert(&waypoint{distance: 42, name: "tango"}) +ts.Insert(&waypoint{distance: 13, name: "alpha"}) +ts.Insert(&waypoint{distance: 71, name: "xray"}) +``` + diff --git a/index/server/vendor/github.com/hashicorp/go-set/common.go b/index/server/vendor/github.com/hashicorp/go-set/common.go new file mode 100644 index 000000000..644995b64 --- /dev/null +++ b/index/server/vendor/github.com/hashicorp/go-set/common.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package set + +// Common is a minimal interface that all sets implement. +type Common[T any] interface { + + // Slice returns a slice of all elements in the set. + // + // Note: order of elements depends on the underlying implementation. + Slice() []T + + // Insert an element into the set. + // + // Returns true if the set is modified as a result. + Insert(T) bool + + // InsertSlice inserts all elements from the slice into the set. + // + // Returns true if the set was modified as a result. + InsertSlice([]T) bool + + // Size returns the number of elements in the set. + Size() int + + // ForEach will call the callback function for each element in the set. + // If the callback returns false, the iteration will stop. + // + // Note: iteration order depends on the underlying implementation. + ForEach(func(T) bool) +} + +// InsertSliceFunc inserts all elements from the slice into the set +func InsertSliceFunc[T, E any](s Common[T], items []E, f func(element E) T) { + for _, item := range items { + s.Insert(f(item)) + } +} + +// InsertSetFunc inserts the elements of a into b, applying the transform function +// to each element before insertion. +// +// Returns true if b was modified as a result. +func InsertSetFunc[T, E any](a Common[T], b Common[E], transform func(T) E) bool { + modified := false + a.ForEach(func(item T) bool { + if b.Insert(transform(item)) { + modified = true + } + return true + }) + return modified +} + +// SliceFunc produces a slice of the elements in s, applying the transform +// function to each element first. +func SliceFunc[T, E any](s Common[T], transform func(T) E) []E { + slice := make([]E, 0, s.Size()) + s.ForEach(func(item T) bool { + slice = append(slice, transform(item)) + return true + }) + return slice +} diff --git a/index/server/vendor/github.com/hashicorp/go-set/hashset.go b/index/server/vendor/github.com/hashicorp/go-set/hashset.go new file mode 100644 index 000000000..98c417c75 --- /dev/null +++ b/index/server/vendor/github.com/hashicorp/go-set/hashset.go @@ -0,0 +1,342 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package set + +import ( + "fmt" + "sort" +) + +// Hash represents the output type of a Hash() function defined on a type. +// +// A Hash could be string-like or int-like. A string hash could be something like +// and md5, sha1, or GoString() representation of a type. An int hash could be +// something like the prime multiple hash code of a type. +type Hash interface { + ~string | ~int | ~uint | ~int64 | ~uint64 | ~int32 | ~uint32 | ~int16 | ~uint16 | ~int8 | ~uint8 +} + +// HashFunc is a generic type constraint for any type that implements a Hash() +// method with a Hash return type. +type HashFunc[H Hash] interface { + Hash() H +} + +// HashSet is a generic implementation of the mathematical data structure, oriented +// around the use of a HashFunc to make hash values from other types. +type HashSet[T HashFunc[H], H Hash] struct { + items map[H]T +} + +// NewHashSet creates a HashSet with underlying capacity of size. +// +// A HashSet will automatically grow or shrink its capacity as items are added +// or removed. +// +// T must implement HashFunc[H], where H is of Hash type. This allows custom types +// that include non-comparable fields to provide their own hash algorithm. +func NewHashSet[T HashFunc[H], H Hash](size int) *HashSet[T, H] { + return &HashSet[T, H]{ + items: make(map[H]T, max(0, size)), + } +} + +// HashSetFrom creates a new HashSet containing each item in items. +// +// T must implement HashFunc[H], where H is of type Hash. This allows custom types +// that include non-comparable fields to provide their own hash algorithm. +func HashSetFrom[T HashFunc[H], H Hash](items []T) *HashSet[T, H] { + s := NewHashSet[T, H](len(items)) + s.InsertSlice(items) + return s +} + +// Insert item into s. +// +// Return true if s was modified (item was not already in s), false otherwise. +func (s *HashSet[T, H]) Insert(item T) bool { + key := item.Hash() + if _, exists := s.items[key]; exists { + return false + } + s.items[key] = item + return true +} + +// InsertAll will insert each item in items into s. +// +// Return true if s was modified (at least one item was not already in s), false otherwise. +// +// Deprecated: use InsertSlice instead. +func (s *HashSet[T, H]) InsertAll(items []T) bool { + return s.InsertSlice(items) +} + +// InsertSlice will insert each item in items into s. +// +// Return true if s was modified (at least one item was not already in s), false otherwise. +func (s *HashSet[T, H]) InsertSlice(items []T) bool { + modified := false + for _, item := range items { + if s.Insert(item) { + modified = true + } + } + return modified +} + +// InsertSet will insert each element of o into s. +// +// Return true if s was modified (at least one item of o was not already in s), false otherwise. +func (s *HashSet[T, H]) InsertSet(o *HashSet[T, H]) bool { + modified := false + for key, value := range o.items { + if _, exists := s.items[key]; !exists { + modified = true + } + s.items[key] = value + } + return modified +} + +// Remove will remove item from s. +// +// Return true if s was modified (item was present), false otherwise. +func (s *HashSet[T, H]) Remove(item T) bool { + key := item.Hash() + if _, exists := s.items[key]; !exists { + return false + } + delete(s.items, key) + return true +} + +// RemoveAll will remove each item in items from s. +// +// Return true if s was modified (any item was present), false otherwise. +// +// Deprecated: use RemoveSlice instead. +func (s *HashSet[T, H]) RemoveAll(items []T) bool { + return s.RemoveSlice(items) +} + +// RemoveSlice will remove each item in items from s. +// +// Return true if s was modified (any item was present), false otherwise. +func (s *HashSet[T, H]) RemoveSlice(items []T) bool { + modified := false + for _, item := range items { + if s.Remove(item) { + modified = true + } + } + return modified +} + +// RemoveSet will remove each element of o from s. +// +// Return true if s was modified (any item of o was present in s), false otherwise. +func (s *HashSet[T, H]) RemoveSet(o *HashSet[T, H]) bool { + modified := false + for key := range o.items { + if _, exists := s.items[key]; exists { + modified = true + delete(s.items, key) + } + } + return modified +} + +// RemoveFunc will remove each element from s that satisfies condition f. +// +// Return true if s was modified, false otherwise. +func (s *HashSet[T, H]) RemoveFunc(f func(item T) bool) bool { + modified := false + for _, item := range s.items { + if applies := f(item); applies { + s.Remove(item) + modified = true + } + } + return modified +} + +// Contains returns whether item is present in s. +func (s *HashSet[T, H]) Contains(item T) bool { + _, exists := s.items[item.Hash()] + return exists +} + +// ContainsAll returns whether s contains at least every item in items. +func (s *HashSet[T, H]) ContainsAll(items []T) bool { + if len(s.items) < len(items) { + return false + } + for _, item := range items { + if !s.Contains(item) { + return false + } + } + return true +} + +// ContainsSlice returns whether s contains the same set of of elements +// that are in items. The elements of items may contain duplicates. +// +// If the slice is known to be set-like (no duplicates), EqualSlice provides +// a more efficient implementation. +func (s *HashSet[T, H]) ContainsSlice(items []T) bool { + return s.Equal(HashSetFrom[T, H](items)) +} + +// Subset returns whether o is a subset of s. +func (s *HashSet[T, H]) Subset(o *HashSet[T, H]) bool { + if len(s.items) < len(o.items) { + return false + } + for _, item := range o.items { + if !s.Contains(item) { + return false + } + } + return true +} + +// Size returns the cardinality of s. +func (s *HashSet[T, H]) Size() int { + return len(s.items) +} + +// Empty returns true if s contains no elements, false otherwise. +func (s *HashSet[T, H]) Empty() bool { + return s.Size() == 0 +} + +// Union returns a set that contains all elements of s and o combined. +func (s *HashSet[T, H]) Union(o *HashSet[T, H]) *HashSet[T, H] { + result := NewHashSet[T, H](s.Size()) + for key, item := range s.items { + result.items[key] = item + } + for key, item := range o.items { + result.items[key] = item + } + return result +} + +// Difference returns a set that contains elements of s that are not in o. +func (s *HashSet[T, H]) Difference(o *HashSet[T, H]) *HashSet[T, H] { + result := NewHashSet[T, H](max(0, s.Size()-o.Size())) + for key, item := range s.items { + if _, exists := o.items[key]; !exists { + result.items[key] = item + } + } + return result +} + +// Intersect returns a set that contains elements that are present in both s and o. +func (s *HashSet[T, H]) Intersect(o *HashSet[T, H]) *HashSet[T, H] { + result := NewHashSet[T, H](0) + big, small := s, o + if s.Size() < o.Size() { + big, small = o, s + } + for _, item := range small.items { + if big.Contains(item) { + result.Insert(item) + } + } + return result +} + +// Copy creates a shallow copy of s. +func (s *HashSet[T, H]) Copy() *HashSet[T, H] { + result := NewHashSet[T, H](s.Size()) + for key, item := range s.items { + result.items[key] = item + } + return result +} + +// Slice creates a copy of s as a slice. +// +// The result is not ordered. +func (s *HashSet[T, H]) Slice() []T { + result := make([]T, 0, s.Size()) + for _, item := range s.items { + result = append(result, item) + } + return result +} + +// List creates a copy of s as a slice. +// +// Deprecated: use Slice() instead. +func (s *HashSet[T, H]) List() []T { + return s.Slice() +} + +// String creates a string representation of s, using "%v" printf formatting to transform +// each element into a string. The result contains elements sorted by their lexical +// string order. +func (s *HashSet[T, H]) String() string { + return s.StringFunc(func(element T) string { + return fmt.Sprintf("%v", element) + }) +} + +// StringFunc creates a string representation of s, using f to transform each element +// into a string. The result contains elements sorted by their string order. +func (s *HashSet[T, H]) StringFunc(f func(element T) string) string { + l := make([]string, 0, s.Size()) + for _, item := range s.items { + l = append(l, f(item)) + } + sort.Strings(l) + return fmt.Sprintf("%s", l) +} + +// Equal returns whether s and o contain the same elements. +func (s *HashSet[T, H]) Equal(o *HashSet[T, H]) bool { + if len(s.items) != len(o.items) { + return false + } + for _, item := range s.items { + if !o.Contains(item) { + return false + } + } + return true +} + +// EqualSlice returns whether s and items contain the same elements. +// +// If items contains duplicates EqualSlice will return false; it is +// assumed that items is itself set-like. For comparing equality with +// a slice that may contain duplicates, use ContainsSlice. +func (s *HashSet[T, H]) EqualSlice(items []T) bool { + if len(s.items) != len(items) { + return false + } + return s.ContainsAll(items) +} + +// MarshalJSON implements the json.Marshaler interface. +func (s *HashSet[T, H]) MarshalJSON() ([]byte, error) { + return marshalJSON[T](s) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (s *HashSet[T, H]) UnmarshalJSON(data []byte) error { + return unmarshalJSON[T](s, data) +} + +func (s *HashSet[T, H]) ForEach(visit func(T) bool) { + for _, item := range s.items { + if !visit(item) { + return + } + } +} diff --git a/index/server/vendor/github.com/hashicorp/go-set/serialization.go b/index/server/vendor/github.com/hashicorp/go-set/serialization.go new file mode 100644 index 000000000..482cabcad --- /dev/null +++ b/index/server/vendor/github.com/hashicorp/go-set/serialization.go @@ -0,0 +1,22 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package set + +import "encoding/json" + +// marshalJSON will serialize a Serializable[T] into a json byte array +func marshalJSON[T any](s Common[T]) ([]byte, error) { + return json.Marshal(s.Slice()) +} + +// unmarshalJSON will deserialize a json byte array into a Serializable[T] +func unmarshalJSON[T any](s Common[T], data []byte) error { + slice := make([]T, 0) + err := json.Unmarshal(data, &slice) + if err != nil { + return err + } + s.InsertSlice(slice) + return nil +} diff --git a/index/server/vendor/github.com/hashicorp/go-set/set.go b/index/server/vendor/github.com/hashicorp/go-set/set.go new file mode 100644 index 000000000..cec1f5831 --- /dev/null +++ b/index/server/vendor/github.com/hashicorp/go-set/set.go @@ -0,0 +1,350 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package set provides a basic generic set implementation. +// +// https://en.wikipedia.org/wiki/Set_(mathematics) +package set + +import ( + "fmt" + "sort" +) + +type nothing struct{} + +var sentinel = nothing{} + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +// New creates a new Set with initial underlying capacity of size. +// +// A Set will automatically grow or shrink its capacity as items are added or +// removed. +// +// T must *not* be of pointer type, nor contain pointer fields, which are comparable +// but not in the way you expect. For these types, use HashSet instead. +func New[T comparable](size int) *Set[T] { + return &Set[T]{ + items: make(map[T]nothing, max(0, size)), + } +} + +// From creates a new Set containing each item in items. +// +// T must *not* be of pointer type, nor contain pointer fields, which are comparable +// but not in the way you expect. For these types, use HashSet instead. +func From[T comparable](items []T) *Set[T] { + s := New[T](len(items)) + s.InsertSlice(items) + return s +} + +// FromFunc creates a new Set containing a conversion of each item in items. +// +// T must *not* be of pointer type, nor contain pointer fields, which are comparable +// but not in the way you expect. For these types, use HashSet instead. +func FromFunc[A any, T comparable](items []A, conversion func(A) T) *Set[T] { + s := New[T](len(items)) + for _, item := range items { + s.Insert(conversion(item)) + } + return s +} + +// Set is a simple, generic implementation of the set mathematical data structure. +// It is optimized for correctness and convenience, as a replacement for the use +// of map[interface{}]struct{}. +type Set[T comparable] struct { + items map[T]nothing +} + +// Insert item into s. +// +// Return true if s was modified (item was not already in s), false otherwise. +func (s *Set[T]) Insert(item T) bool { + if _, exists := s.items[item]; exists { + return false + } + s.items[item] = sentinel + return true +} + +// InsertAll will insert each item in items into s. +// +// Return true if s was modified (at least one item was not already in s), false otherwise. +// +// Deprecated: use InsertSlice instead. +func (s *Set[T]) InsertAll(items []T) bool { + return s.InsertSlice(items) +} + +// InsertSlice will insert each item in items into s. +// +// Return true if s was modified (at least one item was not already in s), false otherwise. +func (s *Set[T]) InsertSlice(items []T) bool { + modified := false + for _, item := range items { + if s.Insert(item) { + modified = true + } + } + return modified +} + +// InsertSet will insert each element of o into s. +// +// Return true if s was modified (at least one item of o was not already in s), false otherwise. +func (s *Set[T]) InsertSet(o *Set[T]) bool { + modified := false + for item := range o.items { + if s.Insert(item) { + modified = true + } + } + return modified +} + +// Remove will remove item from s. +// +// Return true if s was modified (item was present), false otherwise. +func (s *Set[T]) Remove(item T) bool { + if _, exists := s.items[item]; !exists { + return false + } + delete(s.items, item) + return true +} + +// RemoveAll will remove each item in items from s. +// +// Return true if s was modified (any item was present), false otherwise. +// +// Deprecated: use RemoveSlice instead. +func (s *Set[T]) RemoveAll(items []T) bool { + return s.RemoveSlice(items) +} + +// RemoveSlice will remove each item in items from s. +// +// Return true if s was modified (any item was present), false otherwise. +func (s *Set[T]) RemoveSlice(items []T) bool { + modified := false + for _, item := range items { + if s.Remove(item) { + modified = true + } + } + return modified +} + +// RemoveSet will remove each element of o from s. +// +// Return true if s was modified (any item of o was present in s), false otherwise. +func (s *Set[T]) RemoveSet(o *Set[T]) bool { + modified := false + for item := range o.items { + if s.Remove(item) { + modified = true + } + } + return modified +} + +// RemoveFunc will remove each element from s that satisfies condition f. +// +// Return true if s was modified, false otherwise. +func (s *Set[T]) RemoveFunc(f func(T) bool) bool { + modified := false + for item := range s.items { + if applies := f(item); applies { + s.Remove(item) + modified = true + } + } + return modified +} + +// Contains returns whether item is present in s. +func (s *Set[T]) Contains(item T) bool { + _, exists := s.items[item] + return exists +} + +// ContainsAll returns whether s contains at least every item in items. +func (s *Set[T]) ContainsAll(items []T) bool { + if len(s.items) < len(items) { + return false + } + for _, item := range items { + if !s.Contains(item) { + return false + } + } + return true +} + +// ContainsSlice returns whether s contains the same set of of elements +// that are in items. The elements of items may contain duplicates. +// +// If the slice is known to be set-like (no duplicates), EqualSlice provides +// a more efficient implementation. +func (s *Set[T]) ContainsSlice(items []T) bool { + return s.Equal(From(items)) +} + +// Subset returns whether o is a subset of s. +func (s *Set[T]) Subset(o *Set[T]) bool { + if len(s.items) < len(o.items) { + return false + } + for item := range o.items { + if !s.Contains(item) { + return false + } + } + return true +} + +// Size returns the cardinality of s. +func (s *Set[T]) Size() int { + return len(s.items) +} + +// Empty returns true if s contains no elements, false otherwise. +func (s *Set[T]) Empty() bool { + return s.Size() == 0 +} + +// Union returns a set that contains all elements of s and o combined. +func (s *Set[T]) Union(o *Set[T]) *Set[T] { + result := New[T](s.Size()) + for item := range s.items { + result.items[item] = sentinel + } + for item := range o.items { + result.items[item] = sentinel + } + return result +} + +// Difference returns a set that contains elements of s that are not in o. +func (s *Set[T]) Difference(o *Set[T]) *Set[T] { + result := New[T](max(0, s.Size()-o.Size())) + for item := range s.items { + if !o.Contains(item) { + result.items[item] = sentinel + } + } + return result +} + +// Intersect returns a set that contains elements that are present in both s and o. +func (s *Set[T]) Intersect(o *Set[T]) *Set[T] { + result := New[T](0) + big, small := s, o + if s.Size() < o.Size() { + big, small = o, s + } + for item := range small.items { + if big.Contains(item) { + result.Insert(item) + } + } + return result +} + +// Copy creates a copy of s. +func (s *Set[T]) Copy() *Set[T] { + result := New[T](s.Size()) + for item := range s.items { + result.items[item] = sentinel + } + return result +} + +// Slice creates a copy of s as a slice. Elements are in no particular order. +func (s *Set[T]) Slice() []T { + result := make([]T, 0, s.Size()) + for item := range s.items { + result = append(result, item) + } + return result +} + +// List creates a copy of s as a slice. +// +// Deprecated: use Slice() instead. +func (s *Set[T]) List() []T { + return s.Slice() +} + +// String creates a string representation of s, using "%v" printf formating to transform +// each element into a string. The result contains elements sorted by their lexical +// string order. +func (s *Set[T]) String() string { + return s.StringFunc(func(element T) string { + return fmt.Sprintf("%v", element) + }) +} + +// StringFunc creates a string representation of s, using f to transform each element +// into a string. The result contains elements sorted by their lexical string order. +func (s *Set[T]) StringFunc(f func(element T) string) string { + l := make([]string, 0, s.Size()) + for item := range s.items { + l = append(l, f(item)) + } + sort.Strings(l) + return fmt.Sprintf("%s", l) +} + +// Equal returns whether s and o contain the same elements. +func (s *Set[T]) Equal(o *Set[T]) bool { + if len(s.items) != len(o.items) { + return false + } + + for item := range s.items { + if !o.Contains(item) { + return false + } + } + + return true +} + +// EqualSlice returns whether s and items contain the same elements. +// +// If items contains duplicates EqualSlice will return false; it is +// assumed that items is itself set-like. For comparing equality with +// a slice that may contain duplicates, use ContainsSlice. +func (s *Set[T]) EqualSlice(items []T) bool { + if len(s.items) != len(items) { + return false + } + return s.ContainsAll(items) +} + +// MarshalJSON implements the json.Marshaler interface. +func (s *Set[T]) MarshalJSON() ([]byte, error) { + return marshalJSON[T](s) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (s *Set[T]) UnmarshalJSON(data []byte) error { + return unmarshalJSON[T](s, data) +} + +func (s *Set[T]) ForEach(visit func(T) bool) { + for item := range s.items { + if !visit(item) { + return + } + } +} diff --git a/index/server/vendor/github.com/hashicorp/go-set/stack.go b/index/server/vendor/github.com/hashicorp/go-set/stack.go new file mode 100644 index 000000000..bd791c7d9 --- /dev/null +++ b/index/server/vendor/github.com/hashicorp/go-set/stack.go @@ -0,0 +1,36 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package set + +type stack[T any] struct { + top *object[T] +} + +type object[T any] struct { + item T + next *object[T] +} + +func makeStack[T any]() *stack[T] { + return new(stack[T]) +} + +func (s *stack[T]) push(item T) { + obj := &object[T]{ + item: item, + next: s.top, + } + s.top = obj +} + +func (s *stack[T]) pop() T { + obj := s.top + s.top = obj.next + obj.next = nil + return obj.item +} + +func (s *stack[T]) empty() bool { + return s.top == nil +} diff --git a/index/server/vendor/github.com/hashicorp/go-set/treeset.go b/index/server/vendor/github.com/hashicorp/go-set/treeset.go new file mode 100644 index 000000000..aa36843c9 --- /dev/null +++ b/index/server/vendor/github.com/hashicorp/go-set/treeset.go @@ -0,0 +1,1026 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package set + +import ( + "fmt" +) + +// Compare represents a function that compares two elements. +// +// Must return +// < 0 if the first parameter is less than the second parameter +// 0 if the two parameters are equal +// > 0 if the first parameters is greater than the second parameter +type Compare[T any] func(T, T) int + +// BuiltIn types compatible with Cmp +type BuiltIn interface { + ~string | ~int | ~uint | ~int64 | ~uint64 | ~int32 | ~uint32 | ~int16 | ~uint16 | ~int8 | ~uint8 +} + +// Cmp is a Compare function for the specified builtin type B. +// +// Common to use with string, int, etc. +func Cmp[B BuiltIn](x, y B) int { + switch { + case x < y: + return -1 + case x > y: + return 1 + default: + return 0 + } +} + +// TreeSet provides a generic sortable set implementation for Go. +// Enables fast storage and retrieval of ordered information. Most effective +// in cases where data is regularly being added and/or removed and fast +// lookup properties must be maintained. +// +// The underlying data structure is a Red-Black Binary Search Tree. +// https://en.wikipedia.org/wiki/Red–black_tree +// +// Not thread safe, and not safe for concurrent modification. +type TreeSet[T any, C Compare[T]] struct { + comparison C + root *node[T] + marker *node[T] + size int +} + +// NewTreeSet creates a TreeSet of type T, comparing elements via C. +// +// T may be any type. +// +// C is an implementation of Compare[T]. For builtin types, Cmp provides +// a convenient Compare implementation. +func NewTreeSet[T any, C Compare[T]](compare C) *TreeSet[T, C] { + return &TreeSet[T, C]{ + comparison: compare, + root: nil, + marker: &node[T]{color: black}, + size: 0, + } +} + +// TreeSetFrom creates a new TreeSet containing each item in items. +// +// T may be any type. +// +// C is an implementation of Compare[T]. For builtin types, Cmp provides a +// convenient Compare implementation. +func TreeSetFrom[T any, C Compare[T]](items []T, compare C) *TreeSet[T, C] { + s := NewTreeSet[T](compare) + s.InsertSlice(items) + return s +} + +// Insert item into s. +// +// Returns true if s was modified (item was not already in s), false otherwise. +func (s *TreeSet[T, C]) Insert(item T) bool { + return s.insert(&node[T]{ + element: item, + color: red, + }) +} + +// InsertSlice will insert each item in items into s. +// +// Return true if s was modified (at least one item was not already in s), false otherwise. +func (s *TreeSet[T, C]) InsertSlice(items []T) bool { + modified := false + for _, item := range items { + if s.Insert(item) { + modified = true + } + } + return modified +} + +// InsertSet will insert each element of o into s. +// +// Return true if s was modified (at least one item of o was not already in s), false otherwise. +func (s *TreeSet[T, C]) InsertSet(o *TreeSet[T, C]) bool { + modified := false + insert := func(item T) bool { + if s.Insert(item) { + modified = true + } + return true + } + o.ForEach(insert) + return modified +} + +// Remove item from s. +// +// Returns true if s was modified (item was in s), false otherwise. +func (s *TreeSet[T, C]) Remove(item T) bool { + return s.delete(item) +} + +// RemoveSlice will remove each item in items from s. +// +// Return true if s was modified (any item was in s), false otherwise. +func (s *TreeSet[T, C]) RemoveSlice(items []T) bool { + modified := false + for _, item := range items { + if s.Remove(item) { + modified = true + } + } + return modified +} + +// RemoveSet will remove each element in o from s. +// +// Returns true if s was modified (at least one item in o was in s), false otherwise. +func (s *TreeSet[T, C]) RemoveSet(o *TreeSet[T, C]) bool { + modified := false + remove := func(item T) bool { + if s.Remove(item) { + modified = true + } + return true + } + o.ForEach(remove) + return modified +} + +// RemoveFunc will remove each element from s that satisifies condition f. +// +// Return true if s was modified, false otherwise. +func (s *TreeSet[T, C]) RemoveFunc(f func(T) bool) bool { + removeIds := s.FilterSlice(f) + return s.RemoveSlice(removeIds) +} + +// Min returns the smallest item in the set. +// +// Must not be called on an empty set. +func (s *TreeSet[T, C]) Min() T { + if s.root == nil { + panic("min: tree is empty") + } + n := s.min(s.root) + return n.element +} + +// Max returns the largest item in s. +// +// Must not be called on an empty set. +func (s *TreeSet[T, C]) Max() T { + if s.root == nil { + panic("max: tree is empty") + } + n := s.max(s.root) + return n.element +} + +// TopK returns the top n (smallest) elements in s, in ascending order. +func (s *TreeSet[T, C]) TopK(n int) []T { + result := make([]T, 0, n) + s.fillLeft(s.root, &result) + return result +} + +// BottomK returns the bottom n (largest) elements in s, in descending order. +func (s *TreeSet[T, C]) BottomK(n int) []T { + result := make([]T, 0, n) + s.fillRight(s.root, &result) + return result +} + +// FirstBelow returns the first element strictly below item. +// +// A zero value and false are returned if no such element exists. +func (s *TreeSet[T, C]) FirstBelow(item T) (T, bool) { + var candidate *node[T] = nil + var n = s.root + for n != nil { + c := s.comparison(item, n.element) + switch { + case c > 0: + candidate = n + n = n.right + case c <= 0: + n = n.left + } + } + return candidate.get() +} + +// FirstBelowEqual returns the first element below item (or item itself if present). +// +// A zero value and false are returned if no such element exists. +func (s *TreeSet[T, C]) FirstBelowEqual(item T) (T, bool) { + var candidate *node[T] = nil + var n = s.root + for n != nil { + c := s.comparison(item, n.element) + switch { + case c == 0: + return n.get() + case c > 0: + candidate = n + n = n.right + case c < 0: + n = n.left + } + } + return candidate.get() +} + +// Below returns a TreeSet containing the elements of s that are < item. +func (s *TreeSet[T, C]) Below(item T) *TreeSet[T, C] { + result := NewTreeSet[T](s.comparison) + s.filterLeft(s.root, func(element T) bool { + return s.comparison(element, item) < 0 + }, result) + return result +} + +// BelowEqual returns a TreeSet containing the elements of s that are ≤ item. +func (s *TreeSet[T, C]) BelowEqual(item T) *TreeSet[T, C] { + result := NewTreeSet[T](s.comparison) + s.filterLeft(s.root, func(element T) bool { + return s.comparison(element, item) <= 0 + }, result) + return result +} + +// FirstAbove returns the first element strictly above item. +// +// A zero value and false are returned if no such element exists. +func (s *TreeSet[T, C]) FirstAbove(item T) (T, bool) { + var candidate *node[T] = nil + var n = s.root + for n != nil { + c := s.comparison(item, n.element) + switch { + case c < 0: + candidate = n + n = n.left + case c >= 0: + n = n.right + } + } + return candidate.get() +} + +// FirstAboveEqual returns the first element above item (or item itself if present). +// +// A zero value and false are returned if no such element exists. +func (s *TreeSet[T, C]) FirstAboveEqual(item T) (T, bool) { + var candidate *node[T] + var n = s.root + for n != nil { + c := s.comparison(item, n.element) + switch { + case c == 0: + return n.get() + case c < 0: + candidate = n + n = n.left + case c > 0: + n = n.right + } + } + return candidate.get() +} + +// After returns a TreeSet containing the elements of s that are > item. +func (s *TreeSet[T, C]) Above(item T) *TreeSet[T, C] { + result := NewTreeSet[T](s.comparison) + s.filterRight(s.root, func(element T) bool { + return s.comparison(element, item) > 0 + }, result) + return result +} + +// AfterEqual returns a TreeSet containing the elements of s that are ≥ item. +func (s *TreeSet[T, C]) AboveEqual(item T) *TreeSet[T, C] { + result := NewTreeSet[T](s.comparison) + s.filterRight(s.root, func(element T) bool { + return s.comparison(element, item) >= 0 + }, result) + return result +} + +// Contains returns whether item is present in s. +func (s *TreeSet[T, C]) Contains(item T) bool { + return s.locate(s.root, item) != nil +} + +// ContainsSlice returns whether s contains the same set of elements that are in +// items. The items slice may contain duplicate elements. +// +// If the items slice is known to be set-like (no duplicates), EqualSlice provides +// a more efficient implementation. +func (s *TreeSet[T, C]) ContainsSlice(items []T) bool { + for _, item := range items { + if !s.Contains(item) { + return false + } + } + return true +} + +// Size returns the number of elements in s. +func (s *TreeSet[T, C]) Size() int { + return s.size +} + +// Empty returns true if there are no elements in s. +func (s *TreeSet[T, C]) Empty() bool { + return s.Size() == 0 +} + +// Slice returns the elements of s as a slice, in order. +func (s *TreeSet[T, C]) Slice() []T { + result := make([]T, 0, s.Size()) + s.ForEach(func(element T) bool { + result = append(result, element) + return true + }) + return result +} + +// FilterSlice returns the elements of s that satisfy the predicate f. +func (s *TreeSet[T, C]) FilterSlice(filter func(T) bool) []T { + result := make([]T, 0, s.Size()) + s.ForEach(func(t T) bool { + if filter != nil && filter(t) { + result = append(result, t) + } + return true + }) + return result +} + +// Subset returns whether o is a subset of s. +func (s *TreeSet[T, C]) Subset(o *TreeSet[T, C]) bool { + // try the fast paths + if o.Empty() { + return true + } + if s.Empty() { + return false + } + if s.Size() < o.Size() { + return false + } + + // iterate o, and increment s finding each element + // i.e. merge algorithm but with channels + iterO := o.iterate() + iterS := s.iterate() + + idxO := 0 + idxS := 0 + +next: + for ; idxO < o.Size(); idxO++ { + nextO := iterO() + for idxS < s.Size() { + idxS++ + nextS := iterS() + cmp := s.compare(nextS, nextO) + switch { + case cmp > 0: + return false + case cmp < 0: + continue + default: + continue next + } + } + return false + } + return true +} + +// Union returns a set that contains all elements of s and o combined. +func (s *TreeSet[T, C]) Union(o *TreeSet[T, C]) *TreeSet[T, C] { + tree := NewTreeSet[T](s.comparison) + f := func(n *node[T]) { tree.Insert(n.element) } + s.prefix(f, s.root) + o.prefix(f, o.root) + return tree +} + +// Difference returns a set that contains elements of s that are not in o. +func (s *TreeSet[T, C]) Difference(o *TreeSet[T, C]) *TreeSet[T, C] { + tree := NewTreeSet[T](s.comparison) + f := func(n *node[T]) { + if !o.Contains(n.element) { + tree.Insert(n.element) + } + } + s.prefix(f, s.root) + return tree +} + +// Intersect returns a set that contains elements that are present in both s and o. +func (s *TreeSet[T, C]) Intersect(o *TreeSet[T, C]) *TreeSet[T, C] { + tree := NewTreeSet[T](s.comparison) + f := func(n *node[T]) { + if o.Contains(n.element) { + tree.Insert(n.element) + } + } + s.prefix(f, s.root) + return tree +} + +// Copy creates a copy of s. +// +// Individual elements are reference copies. +func (s *TreeSet[T, C]) Copy() *TreeSet[T, C] { + tree := NewTreeSet[T](s.comparison) + f := func(n *node[T]) { + tree.Insert(n.element) + } + s.prefix(f, s.root) + return tree +} + +// Equal return whether s and o contain the same elements. +func (s *TreeSet[T, C]) Equal(o *TreeSet[T, C]) bool { + // try the fast fail paths + if s.Empty() || o.Empty() { + return s.Size() == o.Size() + } + switch { + case s.Size() != o.Size(): + return false + case s.comparison(s.Min(), o.Min()) != 0: + return false + case s.comparison(s.Max(), o.Max()) != 0: + return false + } + + iterS := s.iterate() + iterO := o.iterate() + for i := 0; i < s.Size(); i++ { + nextS := iterS() + nextO := iterO() + if s.compare(nextS, nextO) != 0 { + return false + } + } + + return true +} + +// EqualSlice returns whether s and items contain the same elements. +func (s *TreeSet[T, C]) EqualSlice(items []T) bool { + if s.Size() != len(items) { + return false + } + return s.ContainsSlice(items) +} + +// String creates a string representation of s, using "%v" printf formatting +// each element into a string. The result contains elements in order. +func (s *TreeSet[T, C]) String() string { + return s.StringFunc(func(element T) string { + return fmt.Sprintf("%v", element) + }) +} + +// StringFunc creates a string representation of s, using f to transform each +// element into a string. The result contains elements in order. +func (s *TreeSet[T, C]) StringFunc(f func(element T) string) string { + l := make([]string, 0, s.Size()) + s.ForEach(func(element T) bool { + l = append(l, f(element)) + return true + }) + return fmt.Sprintf("%s", l) +} + +func (s *TreeSet[T, C]) ForEach(visit func(T) bool) { + s.infix(func(n *node[T]) (next bool) { + return visit(n.element) + }, s.root) +} + +// Red-Black Tree Invariants +// +// 1. each node is either red or black +// 2. the root node is always black +// 3. nil leaf nodes are always black +// 4. a red node must not have red children +// 5. all simple paths from a node to nil leaf contain the same number of +// black nodes + +type color bool + +const ( + red color = false + black color = true +) + +type node[T any] struct { + element T + color color + parent *node[T] + left *node[T] + right *node[T] +} + +func (n *node[T]) black() bool { + return n == nil || n.color == black +} + +func (n *node[T]) red() bool { + return n != nil && n.color == red +} + +func (n *node[T]) get() (T, bool) { + if n == nil { + var zero T + return zero, false + } + return n.element, true +} + +func (s *TreeSet[T, C]) locate(start *node[T], target T) *node[T] { + n := start + for { + if n == nil { + return nil + } + cmp := s.compare(n, &node[T]{element: target}) + switch { + case cmp < 0: + n = n.right + case cmp > 0: + n = n.left + default: + return n + } + } +} + +func (s *TreeSet[T, C]) rotateRight(n *node[T]) { + parent := n.parent + leftChild := n.left + + n.left = leftChild.right + if leftChild.right != nil { + leftChild.right.parent = n + } + + leftChild.right = n + n.parent = leftChild + + s.replaceChild(parent, n, leftChild) +} + +func (s *TreeSet[T, C]) rotateLeft(n *node[T]) { + parent := n.parent + rightChild := n.right + + n.right = rightChild.left + if rightChild.left != nil { + rightChild.left.parent = n + } + + rightChild.left = n + n.parent = rightChild + + s.replaceChild(parent, n, rightChild) +} + +func (s *TreeSet[T, C]) replaceChild(parent, previous, next *node[T]) { + switch { + case parent == nil: + s.root = next + case parent.left == previous: + parent.left = next + case parent.right == previous: + parent.right = next + default: + panic("node is not child of its parent") + } + + if next != nil { + next.parent = parent + } +} + +func (s *TreeSet[T, C]) insert(n *node[T]) bool { + var ( + parent *node[T] = nil + tmp *node[T] = s.root + ) + + for tmp != nil { + parent = tmp + + cmp := s.compare(n, tmp) + switch { + case cmp < 0: + tmp = tmp.left + case cmp > 0: + tmp = tmp.right + default: + // already exists in tree + return false + } + } + + n.color = red + switch { + case parent == nil: + s.root = n + case s.compare(n, parent) < 0: + parent.left = n + default: + parent.right = n + } + n.parent = parent + + s.rebalanceInsertion(n) + s.size++ + return true +} + +func (s *TreeSet[T, C]) rebalanceInsertion(n *node[T]) { + parent := n.parent + + // case 1: parent is nil + // - means we are the root + // - our color must be black + if parent == nil { + n.color = black + return + } + + // if parent is black there is nothing to do + if parent.black() { + return + } + + // case 2: no grandparent + // - implies the parent is root + // - we must now be black + grandparent := parent.parent + if grandparent == nil { + parent.color = black + return + } + + uncle := s.uncleOf(parent) + + switch { + // case 3: uncle is red + // - fix color of parent, grandparent, uncle + // - recurse upwards as necessary + case uncle != nil && uncle.red(): + parent.color = black + grandparent.color = red + uncle.color = black + s.rebalanceInsertion(grandparent) + + case parent == grandparent.left: + // case 4a: uncle is black + // + node is left->right child of its grandparent + if n == parent.right { + s.rotateLeft(parent) + parent = n // recolor in case 5a + } + + // case 5a: uncle is black + // + node is left->left child of its grandparent + s.rotateRight(grandparent) + + // fix color of original parent and grandparent + parent.color = black + grandparent.color = red + + // parent is right child of grandparent + default: + // case 4b: uncle is black + // + node is right->left child of its grandparent + if n == parent.left { + s.rotateRight(parent) + // points to root of rotated sub tree + parent = n // recolor in case 5b + } + + // case 5b: uncle is black + // + node is right->right child of its grandparent + s.rotateLeft(grandparent) + + // fix color of original parent and grandparent + parent.color = black + grandparent.color = red + } +} + +func (s *TreeSet[T, C]) delete(element T) bool { + n := s.locate(s.root, element) + if n == nil { + return false + } + + var ( + moved *node[T] + deleted color + ) + + if n.left == nil || n.right == nil { + // case where deleted node had zero or one child + moved = s.delete01(n) + deleted = n.color + } else { + // case where node has two children + + // find minimum of right subtree + successor := s.min(n.right) + + // copy successor data into n + n.element = successor.element + + // delete successor + moved = s.delete01(successor) + deleted = successor.color + } + + // re-balance if the node was black + if deleted == black { + s.rebalanceDeletion(moved) + + // remove marker + if moved == s.marker { + s.replaceChild(moved.parent, moved, nil) + } + } + + // element was removed + s.size-- + s.marker.color = black + s.marker.left = nil + s.marker.right = nil + s.marker.parent = nil + return true +} + +func (s *TreeSet[T, C]) delete01(n *node[T]) *node[T] { + // node only has left child, replace by left child + if n.left != nil { + s.replaceChild(n.parent, n, n.left) + return n.left + } + + // node only has right child, replace by right child + if n.right != nil { + s.replaceChild(n.parent, n, n.right) + return n.right + } + + // node has both children + // if node is black replace with marker + // if node is red we just remove it + if n.black() { + s.replaceChild(n.parent, n, s.marker) + return s.marker + } else { + s.replaceChild(n.parent, n, nil) + return nil + } +} + +func (s *TreeSet[T, C]) rebalanceDeletion(n *node[T]) { + // base case: node is root + if n == s.root { + n.color = black + return + } + + sibling := s.siblingOf(n) + + // case: sibling is red + if sibling.red() { + s.fixRedSibling(n, sibling) + sibling = s.siblingOf(n) + } + + // case: black sibling with two black children + if sibling.left.black() && sibling.right.black() { + sibling.color = red + + // case: black sibling with to black children and a red parent + if n.parent.red() { + n.parent.color = black + } else { + // case: black sibling with two black children and black parent + s.rebalanceDeletion(n.parent) + } + } else { + // case: black sibling with at least one red child + s.fixBlackSibling(n, sibling) + } +} + +func (s *TreeSet[T, C]) fixRedSibling(n *node[T], sibling *node[T]) { + sibling.color = black + n.parent.color = red + + switch { + case n == n.parent.left: + s.rotateLeft(n.parent) + default: + s.rotateRight(n.parent) + } +} + +func (s *TreeSet[T, C]) fixBlackSibling(n, sibling *node[T]) { + isLeftChild := n == n.parent.left + + if isLeftChild && sibling.right.black() { + sibling.left.color = black + sibling.color = red + s.rotateRight(sibling) + sibling = n.parent.right + } else if !isLeftChild && sibling.left.black() { + sibling.right.color = black + sibling.color = red + s.rotateLeft(sibling) + sibling = n.parent.left + } + + sibling.color = n.parent.color + n.parent.color = black + if isLeftChild { + sibling.right.color = black + s.rotateLeft(n.parent) + } else { + sibling.left.color = black + s.rotateRight(n.parent) + } +} + +func (s *TreeSet[T, C]) siblingOf(n *node[T]) *node[T] { + parent := n.parent + switch { + case n == parent.left: + return parent.right + case n == parent.right: + return parent.left + default: + panic("bug: parent is not a child of its grandparent") + } +} + +func (*TreeSet[T, C]) uncleOf(n *node[T]) *node[T] { + grandparent := n.parent + switch { + case grandparent.left == n: + return grandparent.right + case grandparent.right == n: + return grandparent.left + default: + panic("bug: parent is not a child of our childs grandparent") + } +} + +func (s *TreeSet[T, C]) min(n *node[T]) *node[T] { + for n.left != nil { + n = n.left + } + return n +} + +func (s *TreeSet[T, C]) max(n *node[T]) *node[T] { + for n.right != nil { + n = n.right + } + return n +} + +func (s *TreeSet[T, C]) compare(a, b *node[T]) int { + return s.comparison(a.element, b.element) +} + +// TreeNodeVisit is a function that is called for each node in the tree. +type TreeNodeVisit[T any] func(*node[T]) (next bool) + +func (s *TreeSet[T, C]) infix(visit TreeNodeVisit[T], n *node[T]) (next bool) { + if n == nil { + return true + } + if next = s.infix(visit, n.left); !next { + return + } + if next = visit(n); !next { + return + } + return s.infix(visit, n.right) +} + +func (s *TreeSet[T, C]) fillLeft(n *node[T], k *[]T) { + if n == nil { + return + } + + if len(*k) < cap(*k) { + s.fillLeft(n.left, k) + } + + if len(*k) < cap(*k) { + *k = append(*k, n.element) + } + + if len(*k) < cap(*k) { + s.fillLeft(n.right, k) + } +} + +func (s *TreeSet[T, C]) fillRight(n *node[T], k *[]T) { + if n == nil { + return + } + + if len(*k) < cap(*k) { + s.fillRight(n.right, k) + } + + if len(*k) < cap(*k) { + *k = append(*k, n.element) + } + + if len(*k) < cap(*k) { + s.fillRight(n.left, k) + } +} + +func (s *TreeSet[T, C]) prefix(visit func(*node[T]), n *node[T]) { + if n == nil { + return + } + visit(n) + s.prefix(visit, n.left) + s.prefix(visit, n.right) +} + +func (s *TreeSet[T, C]) iterate() func() *node[T] { + stck := makeStack[*node[T]]() + + for n := s.root; n != nil; n = n.left { + stck.push(n) + } + + return func() *node[T] { + if stck.empty() { + return nil + } + n := stck.pop() + for r := n.right; r != nil; r = r.left { + stck.push(r) + } + return n + } +} + +// MarshalJSON implements the json.Marshaler interface. +func (s *TreeSet[T, C]) MarshalJSON() ([]byte, error) { + return marshalJSON[T](s) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (s *TreeSet[T, C]) UnmarshalJSON(data []byte) error { + return unmarshalJSON[T](s, data) +} + +func (s *TreeSet[T, C]) filterLeft(n *node[T], accept func(element T) bool, result *TreeSet[T, C]) { + if n == nil { + return + } + + s.filterLeft(n.left, accept, result) + + if accept(n.element) { + result.Insert(n.element) + s.filterLeft(n.right, accept, result) + } +} + +func (s *TreeSet[T, C]) filterRight(n *node[T], accept func(element T) bool, result *TreeSet[T, C]) { + if n == nil { + return + } + + s.filterRight(n.right, accept, result) + + if accept(n.element) { + result.Insert(n.element) + s.filterRight(n.left, accept, result) + } +} diff --git a/index/server/vendor/modules.txt b/index/server/vendor/modules.txt index ef539c62e..0c60971c4 100644 --- a/index/server/vendor/modules.txt +++ b/index/server/vendor/modules.txt @@ -401,6 +401,9 @@ github.com/hashicorp/errwrap # github.com/hashicorp/go-multierror v1.1.1 ## explicit; go 1.13 github.com/hashicorp/go-multierror +# github.com/hashicorp/go-set v0.1.13 +## explicit; go 1.18 +github.com/hashicorp/go-set # github.com/hashicorp/go-version v1.4.0 ## explicit github.com/hashicorp/go-version diff --git a/tests/integration/pkg/tests/indexserver_tests.go b/tests/integration/pkg/tests/indexserver_tests.go index 207b6efe5..345add157 100644 --- a/tests/integration/pkg/tests/indexserver_tests.go +++ b/tests/integration/pkg/tests/indexserver_tests.go @@ -18,6 +18,7 @@ package tests import ( "io" "net/http" + "strings" devfilePkg "github.com/devfile/library/v2/pkg/devfile" "github.com/devfile/library/v2/pkg/devfile/parser" @@ -127,6 +128,68 @@ var _ = ginkgo.Describe("[Verify index server is working properly]", func() { gomega.Expect(hasStacks && hasSamples).To(gomega.BeTrue()) }) + ginkgo.It("/index/all?deprecated=true endpoint should return stacks and samples which are deprecated", func() { + if config.IsTestRegistry { + registryIndex := util.GetRegistryIndex(config.Registry + "/index/all?deprecated=true") + + hasStacks := false + hasSamples := false + hasAllDeprecated := true + for _, index := range registryIndex { + foundDeprecated := false + + if index.Type == indexSchema.StackDevfileType { + hasStacks = true + } + if index.Type == indexSchema.SampleDevfileType { + hasSamples = true + } + + for _, tag := range index.Tags { + if tag == "Deprecated" { + foundDeprecated = true + break + } + } + + if !foundDeprecated { + hasAllDeprecated = false + } + } + + if len(registryIndex) > 0 { + gomega.Expect((hasStacks || hasSamples) && hasAllDeprecated).To(gomega.BeTrue()) + } + } else { + ginkgo.Skip("cannot guarantee test outside of test registry, skipping test") + } + }) + + ginkgo.It("/index/all?deprecated=false endpoint should return stacks and samples which are non-deprecated", func() { + if config.IsTestRegistry { + registryIndex := util.GetRegistryIndex(config.Registry + "/index/all?deprecated=false") + + hasStacks := false + hasSamples := false + for _, index := range registryIndex { + if index.Type == indexSchema.StackDevfileType { + hasStacks = true + } + if index.Type == indexSchema.SampleDevfileType { + hasSamples = true + } + + gomega.Expect(index.Tags).ShouldNot(gomega.ContainElement("Deprecated")) + } + + if len(registryIndex) > 0 { + gomega.Expect(hasStacks || hasSamples).To(gomega.BeTrue()) + } + } else { + ginkgo.Skip("cannot guarantee test outside of test registry, skipping test") + } + }) + ginkgo.It("/index/all?arch=amd64&arch=arm64 endpoint should return stacks and samples for arch amd64 and arm64", func() { registryIndex := util.GetRegistryIndex(config.Registry + "/index/all?arch=amd64&arch=arm64") @@ -149,6 +212,71 @@ var _ = ginkgo.Describe("[Verify index server is working properly]", func() { } }) + ginkgo.It("/index?provider=Red%22Hat&resources=.zip endpoint should return stacks for provider Red Hat and resources containing .zip partial match", func() { + if config.IsTestRegistry { + registryIndex := util.GetRegistryIndex(config.Registry + "/index?provider=Red%22Hat&resources=.zip") + + hasStacks := false + for _, index := range registryIndex { + if index.Type == indexSchema.StackDevfileType { + hasStacks = true + } + gomega.Expect(index.Resources).Should(gomega.ContainElement(gomega.ContainSubstring(".zip"))) + gomega.Expect(index.Provider).Should(gomega.Equal("Red Hat")) + } + + if len(registryIndex) > 0 { + gomega.Expect(hasStacks).To(gomega.BeTrue()) + } + } else { + ginkgo.Skip("cannot guarantee test outside of test registry, skipping test") + } + }) + + ginkgo.It("/index/sample?description=Hello%22World endpoint should return samples with Hello World in the description", func() { + if config.IsTestRegistry { + registryIndex := util.GetRegistryIndex(config.Registry + "/index/sample?description=Hello%22World") + + hasSamples := false + for _, index := range registryIndex { + if index.Type == indexSchema.SampleDevfileType { + hasSamples = true + } + gomega.Expect(index.Description).Should(gomega.ContainSubstring("Hello World")) + } + + if len(registryIndex) > 0 { + gomega.Expect(hasSamples).To(gomega.BeTrue()) + } + } else { + ginkgo.Skip("cannot guarantee test outside of test registry, skipping test") + } + }) + + ginkgo.It("/index/all?description=node endpoint should return stacks and samples with node (non-case sensitive) in the description", func() { + if config.IsTestRegistry { + registryIndex := util.GetRegistryIndex(config.Registry + "/index/all?description=node") + + hasStacks := false + hasSamples := false + for _, index := range registryIndex { + if index.Type == indexSchema.StackDevfileType { + hasStacks = true + } else if index.Type == indexSchema.SampleDevfileType { + hasSamples = true + } + // strings.ToLower is used to do a non-case match as the fuzzy filtering is non-case sensitive + gomega.Expect(strings.ToLower(index.Description)).Should(gomega.ContainSubstring("node")) + } + + if len(registryIndex) > 0 { + gomega.Expect(hasStacks && hasSamples).To(gomega.BeTrue()) + } + } else { + ginkgo.Skip("cannot guarantee test outside of test registry, skipping test") + } + }) + // v2index tests ginkgo.It("/v2index endpoint should return list of stacks", func() { registryIndex := util.GetRegistryIndex(config.Registry + "/v2index") @@ -208,6 +336,88 @@ var _ = ginkgo.Describe("[Verify index server is working properly]", func() { } }) + ginkgo.It("/v2index/all?deprecated=true endpoint should return stacks and samples which are deprecated", func() { + if config.IsTestRegistry { + registryIndex := util.GetRegistryIndex(config.Registry + "/v2index/all?deprecated=true") + + hasStacks := false + hasSamples := false + hasAllDeprecated := true + for _, index := range registryIndex { + foundDeprecated := false + + if index.Type == indexSchema.StackDevfileType { + hasStacks = true + } + if index.Type == indexSchema.SampleDevfileType { + hasSamples = true + } + + for _, tag := range index.Tags { + if tag == "Deprecated" { + foundDeprecated = true + break + } + } + + for _, version := range index.Versions { + if version.Default { + for _, tag := range version.Tags { + if tag == "Deprecrated" { + foundDeprecated = true + break + } + } + break + } + } + + if !foundDeprecated { + hasAllDeprecated = false + break + } + } + + if len(registryIndex) > 0 { + gomega.Expect((hasStacks || hasSamples) && hasAllDeprecated).To(gomega.BeTrue()) + } + } else { + ginkgo.Skip("cannot guarantee test outside of test registry, skipping test") + } + }) + + ginkgo.It("/v2index/all?deprecated=false endpoint should return stacks and samples which are non-deprecated", func() { + if config.IsTestRegistry { + registryIndex := util.GetRegistryIndex(config.Registry + "/v2index/all?deprecated=false") + + hasStacks := false + hasSamples := false + for _, index := range registryIndex { + if index.Type == indexSchema.StackDevfileType { + hasStacks = true + } + if index.Type == indexSchema.SampleDevfileType { + hasSamples = true + } + + gomega.Expect(index.Tags).ShouldNot(gomega.ContainElement("Deprecated")) + + for _, version := range index.Versions { + if version.Default { + gomega.Expect(version.Tags).ShouldNot(gomega.ContainElement("Deprecated")) + break + } + } + } + + if len(registryIndex) > 0 { + gomega.Expect(hasStacks || hasSamples).To(gomega.BeTrue()) + } + } else { + ginkgo.Skip("cannot guarantee test outside of test registry, skipping test") + } + }) + ginkgo.It("/v2index/all?arch=amd64&arch=arm64 endpoint should return stacks and samples for arch amd64 and arm64", func() { if config.IsTestRegistry { registryIndex := util.GetRegistryIndex(config.Registry + "/v2index/all?arch=amd64&arch=arm64") @@ -234,6 +444,114 @@ var _ = ginkgo.Describe("[Verify index server is working properly]", func() { } }) + ginkgo.It("/v2index/all?arch=arm64&language=java endpoint should return stacks and samples for arch 'arm64' and language 'java'", func() { + if config.IsTestRegistry { + registryIndex := util.GetRegistryIndex(config.Registry + "/v2index/all?arch=arm64&language=java") + + hasStacks := false + hasSamples := false + for _, index := range registryIndex { + if index.Type == indexSchema.SampleDevfileType { + hasSamples = true + } + if index.Type == indexSchema.StackDevfileType { + hasStacks = true + } + if len(index.Architectures) != 0 { + gomega.Expect(index.Architectures).Should(gomega.ContainElement("arm64")) + } + + gomega.Expect(index.Language).Should(gomega.ContainSubstring("java")) + } + + if len(registryIndex) > 0 { + gomega.Expect(hasStacks && hasSamples).To(gomega.BeTrue()) + } + } else { + ginkgo.Skip("cannot guarantee test outside of test registry, skipping test") + } + }) + + ginkgo.It("/v2index?name=java&default=true endpoint should return stacks for name contains 'java' and is default stack version", func() { + if config.IsTestRegistry { + registryIndex := util.GetRegistryIndex(config.Registry + "/v2index?name=java&default=true") + + hasStacks := false + for _, index := range registryIndex { + if index.Type == indexSchema.StackDevfileType { + hasStacks = true + } + gomega.Expect(index.Name).Should(gomega.ContainSubstring("java")) + + gomega.Expect(len(index.Versions) == 1).To(gomega.BeTrue()) + if len(index.Versions) == 1 { + gomega.Expect(index.Versions[0].Default).To(gomega.BeTrue()) + } + } + + if len(registryIndex) > 0 { + gomega.Expect(hasStacks).To(gomega.BeTrue()) + } + } else { + ginkgo.Skip("cannot guarantee test outside of test registry, skipping test") + } + }) + + ginkgo.It("/v2index/sample?description=java&default=true endpoint should return samples for description contains 'java' and is default sample version", func() { + if config.IsTestRegistry { + registryIndex := util.GetRegistryIndex(config.Registry + "/v2index/sample?description=java&default=true") + + hasSamples := false + for _, index := range registryIndex { + if index.Type == indexSchema.SampleDevfileType { + hasSamples = true + } + // strings.ToLower is used to do a non-case match as the fuzzy filtering is non-case sensitive + gomega.Expect(strings.ToLower(index.Description)).Should(gomega.ContainSubstring("java")) + + gomega.Expect(len(index.Versions) == 1).To(gomega.BeTrue()) + if len(index.Versions) == 1 { + gomega.Expect(index.Versions[0].Default).To(gomega.BeTrue()) + } + } + + if len(registryIndex) > 0 { + gomega.Expect(hasSamples).To(gomega.BeTrue()) + } + } else { + ginkgo.Skip("cannot guarantee test outside of test registry, skipping test") + } + }) + + ginkgo.It("/v2index/all?description=java&default=true endpoint should return stacks and samples for description contains 'java' and is default sample version", func() { + if config.IsTestRegistry { + registryIndex := util.GetRegistryIndex(config.Registry + "/v2index/all?description=java&default=true") + + hasStacks := false + hasSamples := false + for _, index := range registryIndex { + if index.Type == indexSchema.StackDevfileType { + hasStacks = true + } else if index.Type == indexSchema.SampleDevfileType { + hasSamples = true + } + // strings.ToLower is used to do a non-case match as the fuzzy filtering is non-case sensitive + gomega.Expect(strings.ToLower(index.Description)).Should(gomega.ContainSubstring("java")) + + gomega.Expect(len(index.Versions) == 1).To(gomega.BeTrue()) + if len(index.Versions) == 1 { + gomega.Expect(index.Versions[0].Default).To(gomega.BeTrue()) + } + } + + if len(registryIndex) > 0 { + gomega.Expect(hasStacks && hasSamples).To(gomega.BeTrue()) + } + } else { + ginkgo.Skip("cannot guarantee test outside of test registry, skipping test") + } + }) + ginkgo.It("/v2index?minSchemaVersion=2.1&maxSchemaVersion=2.1 endpoint should return stacks for devfile schema version 2.1.0", func() { registryIndex := util.GetRegistryIndex(config.Registry + "/v2index/all?minSchemaVersion=2.1&maxSchemaVersion=2.1") @@ -258,6 +576,38 @@ var _ = ginkgo.Describe("[Verify index server is working properly]", func() { } }) + ginkgo.It("/v2index?minVersion=1.1&maxVersion=1.1 endpoint should return stacks for devfile version 1.1.0", func() { + if config.IsTestRegistry { + registryIndex := util.GetRegistryIndex(config.Registry + "/v2index/all?minVersion=1.1&maxVersion=1.1") + + for _, index := range registryIndex { + if len(index.Versions) != 0 { + for _, version := range index.Versions { + gomega.Expect(version.Version).Should(gomega.Equal("1.1.0")) + } + } + } + } else { + ginkgo.Skip("cannot guarantee test outside of test registry, skipping test") + } + }) + + ginkgo.It("/v2index?minVersion=1.1.0&maxVersion=1.1.0 endpoint should return stacks for devfile version 1.1.0", func() { + if config.IsTestRegistry { + registryIndex := util.GetRegistryIndex(config.Registry + "/v2index/all?minVersion=1.1.0&maxVersion=1.1.0") + + for _, index := range registryIndex { + if len(index.Versions) != 0 { + for _, version := range index.Versions { + gomega.Expect(version.Version).Should(gomega.Equal("1.1.0")) + } + } + } + } else { + ginkgo.Skip("cannot guarantee test outside of test registry, skipping test") + } + }) + ginkgo.It("/devfiles/ endpoint should return a devfile for stacks", func() { parserArgs := parser.ParserArgs{ URL: config.Registry + "/devfiles/nodejs", diff --git a/tests/registry/stacks/go/1.0.2/devfile.yaml b/tests/registry/stacks/go/1.0.2/devfile.yaml index bb9aaa146..ab5aee40a 100644 --- a/tests/registry/stacks/go/1.0.2/devfile.yaml +++ b/tests/registry/stacks/go/1.0.2/devfile.yaml @@ -9,6 +9,7 @@ metadata: language: Go tags: - Go + - Deprecated version: 1.0.2 starterProjects: - name: go-starter diff --git a/tests/registry/stacks/go/1.2.0/devfile.yaml b/tests/registry/stacks/go/1.2.0/devfile.yaml index 0bb7f96bb..27d5bf0cb 100644 --- a/tests/registry/stacks/go/1.2.0/devfile.yaml +++ b/tests/registry/stacks/go/1.2.0/devfile.yaml @@ -9,6 +9,7 @@ metadata: projectType: go tags: - testtag + - Deprecated version: 1.2.0 starterProjects: - name: go-starter