feat(mc): Add dynamic filter options endpoint for model catalog (#1727)

* feat: generate server and client files only

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* feat: add support for catalog dynamic catalog filtering

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* feat: catalog test fix

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* feat: add tests for filter options

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* fix: remove duplicate filterQuery parameter and regenerate OpenAPI files

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* fix: check range bug

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* feat: add filter options and fix openapi spec conflicts

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* feat: add filteroptionrange

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* fix: gitattributes file

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* fix: regenerate OpenAPI code and remove stale converter files

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* fix(catalog): regenerate files

And fix badly generated openapi code.

Signed-off-by: Paul Boyd <paul@pboyd.io>

* Update api/openapi/src/catalog.yaml

Co-authored-by: Paul Boyd <paul@pboyd.io>
Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* Update api/openapi/src/catalog.yaml

Co-authored-by: Paul Boyd <paul@pboyd.io>
Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* Update api/openapi/src/catalog.yaml

Co-authored-by: Paul Boyd <paul@pboyd.io>
Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* fix: pr feedback changes

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* feat: regen files

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* fix: build and remove sourceLabel fields

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* fix: regen on openapi gen 7.13.0

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* feat: regen openapi files after rebase

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* fix: build and tests and rebase

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* Update catalog/internal/server/openapi/api_model_catalog_service_service_test.go

Co-authored-by: Paul Boyd <paul@pboyd.io>
Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* fix: revert logic on older refactor

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* feat: regen files

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

* feat: regen catalog

Signed-off-by: Sidney Glinton <sglinton@redhat.com>

---------

Signed-off-by: Sidney Glinton <sglinton@redhat.com>
Signed-off-by: Paul Boyd <paul@pboyd.io>
Co-authored-by: Paul Boyd <paul@pboyd.io>
This commit is contained in:
Sidney Glinton 2025-10-15 15:04:51 -04:00 committed by GitHub
parent ceaf413d44
commit f61929fbae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 1220 additions and 3 deletions

3
.gitattributes vendored
View File

@ -20,6 +20,9 @@ catalog/pkg/openapi/model_catalog_model_list.go linguist-generated=true
catalog/pkg/openapi/model_catalog_source.go linguist-generated=true
catalog/pkg/openapi/model_catalog_source_list.go linguist-generated=true
catalog/pkg/openapi/model_error.go linguist-generated=true
catalog/pkg/openapi/model_filter_option.go linguist-generated=true
catalog/pkg/openapi/model_filter_option_range.go linguist-generated=true
catalog/pkg/openapi/model_filter_options_list.go linguist-generated=true
catalog/pkg/openapi/model_metadata_bool_value.go linguist-generated=true
catalog/pkg/openapi/model_metadata_double_value.go linguist-generated=true
catalog/pkg/openapi/model_metadata_int_value.go linguist-generated=true

View File

@ -67,6 +67,22 @@ paths:
"500":
$ref: "#/components/responses/InternalServerError"
operationId: findModels
/api/model_catalog/v1alpha1/models/filter_options:
description: Lists options for `filterQuery` when listing models.
get:
summary: Lists fields and available options that can be used in `filterQuery` on the list models endpoint.
tags:
- ModelCatalogService
responses:
"200":
$ref: "#/components/responses/FilterOptionsResponse"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalServerError"
operationId: findModelsFilterOptions
/api/model_catalog/v1alpha1/sources:
summary: Path used to get the list of catalog sources.
description: >-
@ -434,6 +450,40 @@ components:
message:
description: Error message
type: string
FilterOption:
type: object
required:
- type
properties:
type:
type: string
description: The data type of the filter option
enum:
- string
- number
values:
type: array
description: Known values of the property for string types with a small number of possible options.
items: {}
range:
$ref: "#/components/schemas/FilterOptionRange"
FilterOptionRange:
type: object
description: Min and max values for number types.
properties:
min:
type: number
max:
type: number
FilterOptionsList:
description: List of FilterOptions
type: object
properties:
filters:
type: object
description: A single filter option.
additionalProperties:
$ref: "#/components/schemas/FilterOption"
MetadataBoolValue:
description: A bool property value.
type: object
@ -598,6 +648,12 @@ components:
schema:
$ref: "#/components/schemas/Error"
description: Conflict with current state of target resource
FilterOptionsResponse:
content:
application/json:
schema:
$ref: "#/components/schemas/FilterOptionsList"
description: A response containing options for a `filterQuery` parameter.
InternalServerError:
content:
application/json:

View File

@ -67,6 +67,22 @@ paths:
"500":
$ref: "#/components/responses/InternalServerError"
operationId: findModels
/api/model_catalog/v1alpha1/models/filter_options:
description: Lists options for `filterQuery` when listing models.
get:
summary: Lists fields and available options that can be used in `filterQuery` on the list models endpoint.
tags:
- ModelCatalogService
responses:
"200":
$ref: "#/components/responses/FilterOptionsResponse"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalServerError"
operationId: findModelsFilterOptions
/api/model_catalog/v1alpha1/sources:
summary: Path used to get the list of catalog sources.
description: >-
@ -295,6 +311,40 @@ components:
items:
$ref: "#/components/schemas/CatalogSource"
- $ref: "#/components/schemas/BaseResourceList"
FilterOption:
type: object
required:
- type
properties:
type:
type: string
description: The data type of the filter option
enum:
- string
- number
values:
type: array
description: Known values of the property for string types with a small number of possible options.
items: {}
range:
$ref: "#/components/schemas/FilterOptionRange"
FilterOptionRange:
type: object
description: Min and max values for number types.
properties:
min:
type: number
max:
type: number
FilterOptionsList:
description: List of FilterOptions
type: object
properties:
filters:
type: object
description: A single filter option.
additionalProperties:
$ref: "#/components/schemas/FilterOption"
OrderByField:
description: Supported fields for ordering result entities.
enum:
@ -335,6 +385,12 @@ components:
schema:
$ref: "#/components/schemas/CatalogSource"
description: A response containing a `CatalogSource` entity.
FilterOptionsResponse:
content:
application/json:
schema:
$ref: "#/components/schemas/FilterOptionsList"
description: A response containing options for a `filterQuery` parameter.
parameters:
orderBy:

View File

@ -41,4 +41,8 @@ type APIProvider interface {
// model is found with that name, it returns nil. If the model is
// found, but has no artifacts, an empty list is returned.
GetArtifacts(ctx context.Context, modelName string, sourceID string, params ListArtifactsParams) (model.CatalogArtifactList, error)
// GetFilterOptions returns all available filter options for models.
// This includes field names, data types, and available values or ranges.
GetFilterOptions(ctx context.Context) (*model.FilterOptionsList, error)
}

View File

@ -416,6 +416,10 @@ func (m *MockCatalogModelRepository) Save(model dbmodels.CatalogModel) (dbmodels
return savedModel, nil
}
func (m *MockCatalogModelRepository) GetFilterableProperties(maxLength int) (map[string][]string, error) {
return make(map[string][]string), nil
}
// MockCatalogModelArtifactRepository mocks the CatalogModelArtifactRepository interface.
type MockCatalogModelArtifactRepository struct {
SavedArtifacts []dbmodels.CatalogModelArtifact

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"sort"
"strconv"
dbmodels "github.com/kubeflow/model-registry/catalog/internal/db/models"
@ -184,6 +185,68 @@ func (d *dbCatalogImpl) GetArtifacts(ctx context.Context, modelName string, sour
return *artifactList, nil
}
func (d *dbCatalogImpl) GetFilterOptions(ctx context.Context) (*apimodels.FilterOptionsList, error) {
// Max length threshold for filter values (excludes verbose fields like readme, description)
const maxFilterValueLength = 100
// Query database for filterable properties
filterableProps, err := d.catalogModelRepository.GetFilterableProperties(maxFilterValueLength)
if err != nil {
return nil, err
}
// Build FilterOptionsList
options := make(map[string]apimodels.FilterOption, maxFilterValueLength)
// Process each property and its values
for fieldName, values := range filterableProps {
// Skip internal/technical fields that shouldn't be exposed as filters
if fieldName == "source_id" || fieldName == "logo" || fieldName == "license_link" {
continue
}
// Deduplicate values
uniqueValues := make(map[string]bool)
// Parse JSON arrays for fields like language and tasks
for _, value := range values {
var arrayValues []string
if err := json.Unmarshal([]byte(value), &arrayValues); err == nil {
// Successfully parsed as array, add individual values
for _, v := range arrayValues {
uniqueValues[v] = true
}
} else {
// Not a JSON array
uniqueValues[value] = true
}
}
if len(uniqueValues) > 0 {
sortedValues := make([]string, 0, len(uniqueValues))
for v := range uniqueValues {
sortedValues = append(sortedValues, v)
}
sort.Strings(sortedValues)
// Convert to []interface{} (supports future non-string filter types)
expandedValues := make([]interface{}, len(sortedValues))
for i, v := range sortedValues {
expandedValues[i] = v
}
options[fieldName] = apimodels.FilterOption{
Type: "string",
Values: expandedValues,
}
}
}
return &apimodels.FilterOptionsList{
Filters: &options,
}, nil
}
func mapDBModelToAPIModel(m dbmodels.CatalogModel) apimodels.CatalogModel {
res := apimodels.CatalogModel{}

View File

@ -801,6 +801,168 @@ func TestDBCatalog(t *testing.T) {
assert.Contains(t, err.Error(), "invalid model name")
})
})
t.Run("TestGetFilterOptions", func(t *testing.T) {
// Create models with various properties for filter options testing
model1 := &models.CatalogModelImpl{
TypeID: apiutils.Of(int32(catalogModelTypeID)),
Attributes: &models.CatalogModelAttributes{
Name: apiutils.Of("filter-options-model-1"),
ExternalID: apiutils.Of("filter-opt-1"),
},
Properties: &[]mr_models.Properties{
{Name: "source_id", StringValue: apiutils.Of("filter-test-source")},
{Name: "license", StringValue: apiutils.Of("MIT")},
{Name: "provider", StringValue: apiutils.Of("HuggingFace")},
{Name: "maturity", StringValue: apiutils.Of("stable")},
{Name: "library_name", StringValue: apiutils.Of("transformers")},
{Name: "language", StringValue: apiutils.Of(`["python", "rust"]`)},
{Name: "tasks", StringValue: apiutils.Of(`["text-classification", "token-classification"]`)},
},
}
model2 := &models.CatalogModelImpl{
TypeID: apiutils.Of(int32(catalogModelTypeID)),
Attributes: &models.CatalogModelAttributes{
Name: apiutils.Of("filter-options-model-2"),
ExternalID: apiutils.Of("filter-opt-2"),
},
Properties: &[]mr_models.Properties{
{Name: "source_id", StringValue: apiutils.Of("filter-test-source")},
{Name: "license", StringValue: apiutils.Of("Apache-2.0")},
{Name: "provider", StringValue: apiutils.Of("OpenAI")},
{Name: "maturity", StringValue: apiutils.Of("experimental")},
{Name: "library_name", StringValue: apiutils.Of("openai")},
{Name: "language", StringValue: apiutils.Of(`["python", "javascript"]`)},
{Name: "tasks", StringValue: apiutils.Of(`["text-generation", "conversational"]`)},
{Name: "readme", StringValue: apiutils.Of("This is a very long readme that exceeds 100 characters and should be excluded from filter options because it's too verbose for filtering purposes.")},
},
}
model3 := &models.CatalogModelImpl{
TypeID: apiutils.Of(int32(catalogModelTypeID)),
Attributes: &models.CatalogModelAttributes{
Name: apiutils.Of("filter-options-model-3"),
ExternalID: apiutils.Of("filter-opt-3"),
},
Properties: &[]mr_models.Properties{
{Name: "source_id", StringValue: apiutils.Of("filter-test-source")},
{Name: "license", StringValue: apiutils.Of("MIT")},
{Name: "provider", StringValue: apiutils.Of("PyTorch")},
{Name: "maturity", StringValue: apiutils.Of("stable")},
{Name: "language", StringValue: apiutils.Of(`["python"]`)},
{Name: "tasks", StringValue: apiutils.Of(`["image-classification"]`)},
{Name: "logo", StringValue: apiutils.Of("https://example.com/logo.png")},
{Name: "license_link", StringValue: apiutils.Of("https://example.com/license")},
},
}
_, err := catalogModelRepo.Save(model1)
require.NoError(t, err)
_, err = catalogModelRepo.Save(model2)
require.NoError(t, err)
_, err = catalogModelRepo.Save(model3)
require.NoError(t, err)
// Test GetFilterOptions
filterOptions, err := dbCatalog.GetFilterOptions(ctx)
require.NoError(t, err)
require.NotNil(t, filterOptions)
require.NotNil(t, filterOptions.Filters)
filters := *filterOptions.Filters
// Should include short properties
assert.Contains(t, filters, "license")
assert.Contains(t, filters, "provider")
assert.Contains(t, filters, "maturity")
assert.Contains(t, filters, "library_name")
assert.Contains(t, filters, "language")
assert.Contains(t, filters, "tasks")
// Should exclude internal/verbose fields
assert.NotContains(t, filters, "source_id", "source_id should be excluded")
assert.NotContains(t, filters, "logo", "logo should be excluded")
assert.NotContains(t, filters, "license_link", "license_link should be excluded")
assert.NotContains(t, filters, "readme", "readme should be excluded (too long)")
licenseFilter := filters["license"]
assert.Equal(t, "string", licenseFilter.Type)
assert.NotNil(t, licenseFilter.Values)
assert.GreaterOrEqual(t, len(licenseFilter.Values), 2, "Should have at least MIT and Apache-2.0")
// Convert to string slice for easier checking
licenseValues := make([]string, 0)
for _, v := range licenseFilter.Values {
if strVal, ok := v.(string); ok {
licenseValues = append(licenseValues, strVal)
}
}
assert.Contains(t, licenseValues, "MIT")
assert.Contains(t, licenseValues, "Apache-2.0")
// Verify provider filter options
providerFilter := filters["provider"]
assert.Equal(t, "string", providerFilter.Type)
providerValues := make([]string, 0)
for _, v := range providerFilter.Values {
if strVal, ok := v.(string); ok {
providerValues = append(providerValues, strVal)
}
}
assert.Contains(t, providerValues, "HuggingFace")
assert.Contains(t, providerValues, "OpenAI")
assert.Contains(t, providerValues, "PyTorch")
// Verify JSON array fields are properly parsed and expanded
languageFilter := filters["language"]
assert.Equal(t, "string", languageFilter.Type)
languageValues := make([]string, 0)
for _, v := range languageFilter.Values {
if strVal, ok := v.(string); ok {
languageValues = append(languageValues, strVal)
}
}
// Should contain individual values from JSON arrays
assert.Contains(t, languageValues, "python")
assert.Contains(t, languageValues, "rust")
assert.Contains(t, languageValues, "javascript")
// Verify tasks are properly expanded
tasksFilter := filters["tasks"]
assert.Equal(t, "string", tasksFilter.Type)
tasksValues := make([]string, 0)
for _, v := range tasksFilter.Values {
if strVal, ok := v.(string); ok {
tasksValues = append(tasksValues, strVal)
}
}
assert.Contains(t, tasksValues, "text-classification")
assert.Contains(t, tasksValues, "token-classification")
assert.Contains(t, tasksValues, "text-generation")
assert.Contains(t, tasksValues, "conversational")
assert.Contains(t, tasksValues, "image-classification")
// Verify no duplicates
pythonCount := 0
for _, v := range languageValues {
if v == "python" {
pythonCount++
}
}
assert.Equal(t, 1, pythonCount, "python should appear only once (deduplicated)")
// Verify maturity options
maturityFilter := filters["maturity"]
maturityValues := make([]string, 0)
for _, v := range maturityFilter.Values {
if strVal, ok := v.(string); ok {
maturityValues = append(maturityValues, strVal)
}
}
assert.Contains(t, maturityValues, "stable")
assert.Contains(t, maturityValues, "experimental")
})
}
// Helper functions to get type IDs from database

View File

@ -49,6 +49,15 @@ func (h *hfCatalogImpl) GetArtifacts(ctx context.Context, modelName string, sour
}, nil
}
func (h *hfCatalogImpl) GetFilterOptions(ctx context.Context) (*openapi.FilterOptionsList, error) {
// TODO: Implement HuggingFace filter options retrieval
// For now, return empty options to satisfy interface
emptyFilters := make(map[string]openapi.FilterOption)
return &openapi.FilterOptionsList{
Filters: &emptyFilters,
}, nil
}
// validateCredentials checks if the HuggingFace API credentials are valid
func (h *hfCatalogImpl) validateCredentials(ctx context.Context) error {
glog.Infof("Validating HuggingFace API credentials")

View File

@ -37,4 +37,7 @@ type CatalogModelRepository interface {
GetByName(name string) (CatalogModel, error)
List(listOptions CatalogModelListOptions) (*models.ListWrapper[CatalogModel], error)
Save(model CatalogModel) (CatalogModel, error)
// GetFilterableProperties returns a map of property names to their unique values
// Only includes string properties where all values are shorter than maxLength
GetFilterableProperties(maxLength int) (map[string][]string, error)
}

View File

@ -231,3 +231,56 @@ func mapDataLayerToCatalogModel(modelCtx schema.Context, propertiesCtx []schema.
return catalogModel
}
// GetFilterableProperties returns property names and their unique values
// Only includes properties where ALL values are shorter than maxLength
func (r *CatalogModelRepositoryImpl) GetFilterableProperties(maxLength int) (map[string][]string, error) {
config := r.GetConfig()
// Get table names using GORM utilities for database compatibility
contextTable := utils.GetTableName(config.DB, &schema.Context{})
propertyTable := utils.GetTableName(config.DB, &schema.ContextProperty{})
// Simplified query: get distinct property name/value pairs
query := fmt.Sprintf(`
SELECT DISTINCT cp.name, cp.string_value
FROM %s cp
WHERE cp.context_id IN (
SELECT id FROM %s WHERE type_id = ?
)
AND cp.name IN (
SELECT name FROM (
SELECT name, MAX(CHAR_LENGTH(string_value)) as max_len
FROM %s
WHERE context_id IN (
SELECT id FROM %s WHERE type_id = ?
)
AND string_value IS NOT NULL
AND string_value != ''
GROUP BY name
) AS field_lengths
WHERE max_len <= ?
)
AND cp.string_value IS NOT NULL
AND cp.string_value != ''
ORDER BY cp.name, cp.string_value
`, propertyTable, contextTable, propertyTable, contextTable)
type propertyRow struct {
Name string
StringValue string
}
var rows []propertyRow
if err := config.DB.Raw(query, config.TypeID, config.TypeID, maxLength).Scan(&rows).Error; err != nil {
return nil, fmt.Errorf("error querying filterable properties: %w", err)
}
// Aggregate values by property name in Go
result := make(map[string][]string)
for _, row := range rows {
result[row.Name] = append(result[row.Name], row.StringValue)
}
return result, nil
}

View File

@ -337,6 +337,85 @@ func TestCatalogModelRepository(t *testing.T) {
require.NotNil(t, retrieved.GetCustomProperties())
assert.Len(t, *retrieved.GetCustomProperties(), 2)
})
t.Run("TestGetFilterableProperties", func(t *testing.T) {
// Create models with various property lengths
shortValueModel := &models.CatalogModelImpl{
Attributes: &models.CatalogModelAttributes{
Name: apiutils.Of("short-value-model"),
ExternalID: apiutils.Of("short-ext"),
},
Properties: &[]dbmodels.Properties{
{Name: "license", StringValue: apiutils.Of("MIT")},
{Name: "provider", StringValue: apiutils.Of("HuggingFace")},
{Name: "maturity", StringValue: apiutils.Of("stable")},
},
}
longValueModel := &models.CatalogModelImpl{
Attributes: &models.CatalogModelAttributes{
Name: apiutils.Of("long-value-model"),
ExternalID: apiutils.Of("long-ext"),
},
Properties: &[]dbmodels.Properties{
{Name: "license", StringValue: apiutils.Of("Apache-2.0")},
{Name: "readme", StringValue: apiutils.Of("This is a very long readme that should be excluded from filterable properties because it exceeds the maximum length threshold of 100 characters. It contains detailed information about the model.")},
{Name: "description", StringValue: apiutils.Of("This is also a very long description that should be excluded from filterable properties because it exceeds 100 chars")},
},
}
jsonArrayModel := &models.CatalogModelImpl{
Attributes: &models.CatalogModelAttributes{
Name: apiutils.Of("json-array-model"),
ExternalID: apiutils.Of("json-ext"),
},
Properties: &[]dbmodels.Properties{
{Name: "language", StringValue: apiutils.Of(`["python", "go"]`)},
{Name: "tasks", StringValue: apiutils.Of(`["text-classification", "question-answering"]`)},
},
}
_, err := repo.Save(shortValueModel)
require.NoError(t, err)
_, err = repo.Save(longValueModel)
require.NoError(t, err)
_, err = repo.Save(jsonArrayModel)
require.NoError(t, err)
// Test with max length of 100
result, err := repo.GetFilterableProperties(100)
require.NoError(t, err)
require.NotNil(t, result)
// Should include short properties
assert.Contains(t, result, "license")
assert.Contains(t, result, "provider")
assert.Contains(t, result, "maturity")
assert.Contains(t, result, "language")
assert.Contains(t, result, "tasks")
// Should exclude long properties
assert.NotContains(t, result, "readme")
assert.NotContains(t, result, "description")
// Verify license has both values
licenseValues := result["license"]
assert.GreaterOrEqual(t, len(licenseValues), 2)
assert.Contains(t, licenseValues, "MIT")
assert.Contains(t, licenseValues, "Apache-2.0")
// Test with smaller max length
result, err = repo.GetFilterableProperties(10)
require.NoError(t, err)
require.NotNil(t, result)
// Should include only very short properties
assert.Contains(t, result, "license")
// Should exclude longer properties
assert.NotContains(t, result, "provider") // "HuggingFace" is > 10 chars
assert.NotContains(t, result, "language")
assert.NotContains(t, result, "tasks")
})
}
// Helper function to get or create CatalogModel type ID

View File

@ -18,6 +18,9 @@ model_catalog_model_list.go
model_catalog_source.go
model_catalog_source_list.go
model_error.go
model_filter_option.go
model_filter_option_range.go
model_filter_options_list.go
model_metadata_bool_value.go
model_metadata_double_value.go
model_metadata_int_value.go

View File

@ -22,6 +22,7 @@ import (
// pass the data to a ModelCatalogServiceAPIServicer to perform the required actions, then write the service results to the http response.
type ModelCatalogServiceAPIRouter interface {
FindModels(http.ResponseWriter, *http.Request)
FindModelsFilterOptions(http.ResponseWriter, *http.Request)
FindSources(http.ResponseWriter, *http.Request)
GetModel(http.ResponseWriter, *http.Request)
GetAllModelArtifacts(http.ResponseWriter, *http.Request)
@ -33,6 +34,7 @@ type ModelCatalogServiceAPIRouter interface {
// and updated with the logic required for the API.
type ModelCatalogServiceAPIServicer interface {
FindModels(context.Context, []string, string, []string, string, string, model.OrderByField, model.SortOrder, string) (ImplResponse, error)
FindModelsFilterOptions(context.Context) (ImplResponse, error)
FindSources(context.Context, string, string, model.OrderByField, model.SortOrder, string) (ImplResponse, error)
GetModel(context.Context, string, string) (ImplResponse, error)
GetAllModelArtifacts(context.Context, string, string, string, string, model.OrderByField, model.SortOrder, string) (ImplResponse, error)

View File

@ -57,6 +57,11 @@ func (c *ModelCatalogServiceAPIController) Routes() Routes {
"/api/model_catalog/v1alpha1/models",
c.FindModels,
},
"FindModelsFilterOptions": Route{
strings.ToUpper("Get"),
"/api/model_catalog/v1alpha1/models/filter_options",
c.FindModelsFilterOptions,
},
"FindSources": Route{
strings.ToUpper("Get"),
"/api/model_catalog/v1alpha1/sources",
@ -96,6 +101,18 @@ func (c *ModelCatalogServiceAPIController) FindModels(w http.ResponseWriter, r *
EncodeJSONResponse(result.Body, &result.Code, w)
}
// FindModelsFilterOptions - Lists fields and available options that can be used in `filterQuery` on the list models endpoint.
func (c *ModelCatalogServiceAPIController) FindModelsFilterOptions(w http.ResponseWriter, r *http.Request) {
result, err := c.service.FindModelsFilterOptions(r.Context())
// If an error occurred, encode the error with the status code
if err != nil {
c.errorHandler(w, r, err, &result)
return
}
// If no error, encode the body and the result code
EncodeJSONResponse(result.Body, &result.Code, w)
}
// FindSources - List All CatalogSources
func (c *ModelCatalogServiceAPIController) FindSources(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()

View File

@ -42,11 +42,11 @@ func (m *ModelCatalogServiceAPIService) GetAllModelArtifacts(ctx context.Context
}
artifacts, err := m.provider.GetArtifacts(ctx, modelName, sourceID, catalog.ListArtifactsParams{
ArtifactType: &artifactType,
PageSize: pageSizeInt,
OrderBy: orderBy,
SortOrder: sortOrder,
NextPageToken: &nextPageToken,
ArtifactType: &artifactType,
})
if err != nil {
statusCode := api.ErrToStatus(err)
@ -64,7 +64,7 @@ func (m *ModelCatalogServiceAPIService) GetAllModelArtifacts(ctx context.Context
return Response(http.StatusOK, artifacts), nil
}
func (m *ModelCatalogServiceAPIService) FindModels(ctx context.Context, sourceIDs []string, q string, sourceLabels []string, filterQuery, pageSize string, orderBy model.OrderByField, sortOrder model.SortOrder, nextPageToken string) (ImplResponse, error) {
func (m *ModelCatalogServiceAPIService) FindModels(ctx context.Context, sourceIDs []string, q string, sourceLabels []string, filterQuery string, pageSize string, orderBy model.OrderByField, sortOrder model.SortOrder, nextPageToken string) (ImplResponse, error) {
var err error
pageSizeInt := int32(10)
@ -107,6 +107,15 @@ func (m *ModelCatalogServiceAPIService) FindModels(ctx context.Context, sourceID
return Response(http.StatusOK, models), nil
}
func (m *ModelCatalogServiceAPIService) FindModelsFilterOptions(ctx context.Context) (ImplResponse, error) {
filterOptions, err := m.provider.GetFilterOptions(ctx)
if err != nil {
return ErrorResponse(http.StatusInternalServerError, err), err
}
return Response(http.StatusOK, filterOptions), nil
}
func (m *ModelCatalogServiceAPIService) GetModel(ctx context.Context, sourceID, modelName string) (ImplResponse, error) {
if newName, err := url.PathUnescape(modelName); err == nil {
modelName = newName

View File

@ -776,6 +776,11 @@ func (m *mockModelProvider) GetArtifacts(ctx context.Context, name string, sourc
}, nil
}
func (m *mockModelProvider) GetFilterOptions(ctx context.Context) (*model.FilterOptionsList, error) {
emptyFilters := make(map[string]model.FilterOption)
return &model.FilterOptionsList{Filters: &emptyFilters}, nil
}
func TestGetModel(t *testing.T) {
testCases := []struct {
name string
@ -1002,3 +1007,44 @@ func TestGetAllModelArtifacts(t *testing.T) {
})
}
}
func TestFindModelsFilterOptions(t *testing.T) {
testCases := []struct {
name string
provider catalog.APIProvider
expectedStatus int
expectedError bool
}{
{
name: "Successfully retrieve filter options",
provider: &mockModelProvider{
models: map[string]*model.CatalogModel{},
},
expectedStatus: http.StatusOK,
expectedError: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
sources := catalog.NewSourceCollection()
service := NewModelCatalogServiceAPIService(tc.provider, sources)
resp, err := service.FindModelsFilterOptions(context.Background())
assert.Equal(t, tc.expectedStatus, resp.Code)
if tc.expectedError {
assert.Error(t, err)
return
}
require.NotNil(t, resp.Body)
// Type assertion to access the FilterOptionsList
filterOptions, ok := resp.Body.(*model.FilterOptionsList)
require.True(t, ok, "Response body should be a FilterOptionsList")
require.NotNil(t, filterOptions.Filters)
})
}
}

View File

@ -257,6 +257,26 @@ func AssertErrorRequired(obj model.Error) error {
return nil
}
// AssertFilterOptionRangeConstraints checks if the values respects the defined constraints
func AssertFilterOptionRangeConstraints(obj model.FilterOptionRange) error {
return nil
}
// AssertFilterOptionRangeRequired checks if the required fields are not zero-ed
func AssertFilterOptionRangeRequired(obj model.FilterOptionRange) error {
return nil
}
// AssertFilterOptionsListConstraints checks if the values respects the defined constraints
func AssertFilterOptionsListConstraints(obj model.FilterOptionsList) error {
return nil
}
// AssertFilterOptionsListRequired checks if the required fields are not zero-ed
func AssertFilterOptionsListRequired(obj model.FilterOptionsList) error {
return nil
}
// AssertMetadataBoolValueConstraints checks if the values respects the defined constraints
func AssertMetadataBoolValueConstraints(obj model.MetadataBoolValue) error {
return nil

View File

@ -10,3 +10,22 @@ func AssertCatalogArtifactRequired(obj model.CatalogArtifact) error {
// checks the fields from CatalogModelArtifact, which doesn't compile.
return nil
}
// AssertFilterOptionRequired checks if the required fields are not zero-ed
func AssertFilterOptionRequired(obj model.FilterOption) error {
elements := map[string]interface{}{
"type": obj.Type,
}
for name, el := range elements {
if isZero := IsZeroValue(el); isZero {
return &RequiredError{Field: name}
}
}
if obj.Range != nil {
if err := AssertFilterOptionRangeRequired(*obj.Range); err != nil {
return err
}
}
return nil
}

View File

@ -15,6 +15,9 @@ model_catalog_model_list.go
model_catalog_source.go
model_catalog_source_list.go
model_error.go
model_filter_option.go
model_filter_option_range.go
model_filter_options_list.go
model_metadata_bool_value.go
model_metadata_double_value.go
model_metadata_int_value.go

View File

@ -260,6 +260,136 @@ func (a *ModelCatalogServiceAPIService) FindModelsExecute(r ApiFindModelsRequest
return localVarReturnValue, localVarHTTPResponse, nil
}
type ApiFindModelsFilterOptionsRequest struct {
ctx context.Context
ApiService *ModelCatalogServiceAPIService
}
func (r ApiFindModelsFilterOptionsRequest) Execute() (*FilterOptionsList, *http.Response, error) {
return r.ApiService.FindModelsFilterOptionsExecute(r)
}
/*
FindModelsFilterOptions Lists fields and available options that can be used in `filterQuery` on the list models endpoint.
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@return ApiFindModelsFilterOptionsRequest
*/
func (a *ModelCatalogServiceAPIService) FindModelsFilterOptions(ctx context.Context) ApiFindModelsFilterOptionsRequest {
return ApiFindModelsFilterOptionsRequest{
ApiService: a,
ctx: ctx,
}
}
// Execute executes the request
//
// @return FilterOptionsList
func (a *ModelCatalogServiceAPIService) FindModelsFilterOptionsExecute(r ApiFindModelsFilterOptionsRequest) (*FilterOptionsList, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodGet
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *FilterOptionsList
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ModelCatalogServiceAPIService.FindModelsFilterOptions")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/api/model_catalog/v1alpha1/models/filter_options"
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
if localVarHTTPResponse.StatusCode == 400 {
var v Error
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
return localVarReturnValue, localVarHTTPResponse, newErr
}
if localVarHTTPResponse.StatusCode == 401 {
var v Error
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
return localVarReturnValue, localVarHTTPResponse, newErr
}
if localVarHTTPResponse.StatusCode == 500 {
var v Error
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}
type ApiFindSourcesRequest struct {
ctx context.Context
ApiService *ModelCatalogServiceAPIService

189
catalog/pkg/openapi/model_filter_option.go generated Normal file
View File

@ -0,0 +1,189 @@
/*
Model Catalog REST API
REST API for Model Registry to create and manage ML model metadata
API version: v1alpha1
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
package openapi
import (
"encoding/json"
)
// checks if the FilterOption type satisfies the MappedNullable interface at compile time
var _ MappedNullable = &FilterOption{}
// FilterOption struct for FilterOption
type FilterOption struct {
// The data type of the filter option
Type string `json:"type"`
// Known values of the property for string types with a small number of possible options.
Values []interface{} `json:"values,omitempty"`
Range *FilterOptionRange `json:"range,omitempty"`
}
// NewFilterOption instantiates a new FilterOption object
// This constructor will assign default values to properties that have it defined,
// and makes sure properties required by API are set, but the set of arguments
// will change when the set of required properties is changed
func NewFilterOption(type_ string) *FilterOption {
this := FilterOption{}
this.Type = type_
return &this
}
// NewFilterOptionWithDefaults instantiates a new FilterOption object
// This constructor will only assign default values to properties that have it defined,
// but it doesn't guarantee that properties required by API are set
func NewFilterOptionWithDefaults() *FilterOption {
this := FilterOption{}
return &this
}
// GetType returns the Type field value
func (o *FilterOption) GetType() string {
if o == nil {
var ret string
return ret
}
return o.Type
}
// GetTypeOk returns a tuple with the Type field value
// and a boolean to check if the value has been set.
func (o *FilterOption) GetTypeOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.Type, true
}
// SetType sets field value
func (o *FilterOption) SetType(v string) {
o.Type = v
}
// GetValues returns the Values field value if set, zero value otherwise.
func (o *FilterOption) GetValues() []interface{} {
if o == nil || IsNil(o.Values) {
var ret []interface{}
return ret
}
return o.Values
}
// GetValuesOk returns a tuple with the Values field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *FilterOption) GetValuesOk() ([]interface{}, bool) {
if o == nil || IsNil(o.Values) {
return nil, false
}
return o.Values, true
}
// HasValues returns a boolean if a field has been set.
func (o *FilterOption) HasValues() bool {
if o != nil && !IsNil(o.Values) {
return true
}
return false
}
// SetValues gets a reference to the given []interface{} and assigns it to the Values field.
func (o *FilterOption) SetValues(v []interface{}) {
o.Values = v
}
// GetRange returns the Range field value if set, zero value otherwise.
func (o *FilterOption) GetRange() FilterOptionRange {
if o == nil || IsNil(o.Range) {
var ret FilterOptionRange
return ret
}
return *o.Range
}
// GetRangeOk returns a tuple with the Range field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *FilterOption) GetRangeOk() (*FilterOptionRange, bool) {
if o == nil || IsNil(o.Range) {
return nil, false
}
return o.Range, true
}
// HasRange returns a boolean if a field has been set.
func (o *FilterOption) HasRange() bool {
if o != nil && !IsNil(o.Range) {
return true
}
return false
}
// SetRange gets a reference to the given FilterOptionRange and assigns it to the Range field.
func (o *FilterOption) SetRange(v FilterOptionRange) {
o.Range = &v
}
func (o FilterOption) MarshalJSON() ([]byte, error) {
toSerialize, err := o.ToMap()
if err != nil {
return []byte{}, err
}
return json.Marshal(toSerialize)
}
func (o FilterOption) ToMap() (map[string]interface{}, error) {
toSerialize := map[string]interface{}{}
toSerialize["type"] = o.Type
if !IsNil(o.Values) {
toSerialize["values"] = o.Values
}
if !IsNil(o.Range) {
toSerialize["range"] = o.Range
}
return toSerialize, nil
}
type NullableFilterOption struct {
value *FilterOption
isSet bool
}
func (v NullableFilterOption) Get() *FilterOption {
return v.value
}
func (v *NullableFilterOption) Set(val *FilterOption) {
v.value = val
v.isSet = true
}
func (v NullableFilterOption) IsSet() bool {
return v.isSet
}
func (v *NullableFilterOption) Unset() {
v.value = nil
v.isSet = false
}
func NewNullableFilterOption(val *FilterOption) *NullableFilterOption {
return &NullableFilterOption{value: val, isSet: true}
}
func (v NullableFilterOption) MarshalJSON() ([]byte, error) {
return json.Marshal(v.value)
}
func (v *NullableFilterOption) UnmarshalJSON(src []byte) error {
v.isSet = true
return json.Unmarshal(src, &v.value)
}

View File

@ -0,0 +1,160 @@
/*
Model Catalog REST API
REST API for Model Registry to create and manage ML model metadata
API version: v1alpha1
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
package openapi
import (
"encoding/json"
)
// checks if the FilterOptionRange type satisfies the MappedNullable interface at compile time
var _ MappedNullable = &FilterOptionRange{}
// FilterOptionRange Min and max values for number types.
type FilterOptionRange struct {
Min *float32 `json:"min,omitempty"`
Max *float32 `json:"max,omitempty"`
}
// NewFilterOptionRange instantiates a new FilterOptionRange object
// This constructor will assign default values to properties that have it defined,
// and makes sure properties required by API are set, but the set of arguments
// will change when the set of required properties is changed
func NewFilterOptionRange() *FilterOptionRange {
this := FilterOptionRange{}
return &this
}
// NewFilterOptionRangeWithDefaults instantiates a new FilterOptionRange object
// This constructor will only assign default values to properties that have it defined,
// but it doesn't guarantee that properties required by API are set
func NewFilterOptionRangeWithDefaults() *FilterOptionRange {
this := FilterOptionRange{}
return &this
}
// GetMin returns the Min field value if set, zero value otherwise.
func (o *FilterOptionRange) GetMin() float32 {
if o == nil || IsNil(o.Min) {
var ret float32
return ret
}
return *o.Min
}
// GetMinOk returns a tuple with the Min field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *FilterOptionRange) GetMinOk() (*float32, bool) {
if o == nil || IsNil(o.Min) {
return nil, false
}
return o.Min, true
}
// HasMin returns a boolean if a field has been set.
func (o *FilterOptionRange) HasMin() bool {
if o != nil && !IsNil(o.Min) {
return true
}
return false
}
// SetMin gets a reference to the given float32 and assigns it to the Min field.
func (o *FilterOptionRange) SetMin(v float32) {
o.Min = &v
}
// GetMax returns the Max field value if set, zero value otherwise.
func (o *FilterOptionRange) GetMax() float32 {
if o == nil || IsNil(o.Max) {
var ret float32
return ret
}
return *o.Max
}
// GetMaxOk returns a tuple with the Max field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *FilterOptionRange) GetMaxOk() (*float32, bool) {
if o == nil || IsNil(o.Max) {
return nil, false
}
return o.Max, true
}
// HasMax returns a boolean if a field has been set.
func (o *FilterOptionRange) HasMax() bool {
if o != nil && !IsNil(o.Max) {
return true
}
return false
}
// SetMax gets a reference to the given float32 and assigns it to the Max field.
func (o *FilterOptionRange) SetMax(v float32) {
o.Max = &v
}
func (o FilterOptionRange) MarshalJSON() ([]byte, error) {
toSerialize, err := o.ToMap()
if err != nil {
return []byte{}, err
}
return json.Marshal(toSerialize)
}
func (o FilterOptionRange) ToMap() (map[string]interface{}, error) {
toSerialize := map[string]interface{}{}
if !IsNil(o.Min) {
toSerialize["min"] = o.Min
}
if !IsNil(o.Max) {
toSerialize["max"] = o.Max
}
return toSerialize, nil
}
type NullableFilterOptionRange struct {
value *FilterOptionRange
isSet bool
}
func (v NullableFilterOptionRange) Get() *FilterOptionRange {
return v.value
}
func (v *NullableFilterOptionRange) Set(val *FilterOptionRange) {
v.value = val
v.isSet = true
}
func (v NullableFilterOptionRange) IsSet() bool {
return v.isSet
}
func (v *NullableFilterOptionRange) Unset() {
v.value = nil
v.isSet = false
}
func NewNullableFilterOptionRange(val *FilterOptionRange) *NullableFilterOptionRange {
return &NullableFilterOptionRange{value: val, isSet: true}
}
func (v NullableFilterOptionRange) MarshalJSON() ([]byte, error) {
return json.Marshal(v.value)
}
func (v *NullableFilterOptionRange) UnmarshalJSON(src []byte) error {
v.isSet = true
return json.Unmarshal(src, &v.value)
}

View File

@ -0,0 +1,125 @@
/*
Model Catalog REST API
REST API for Model Registry to create and manage ML model metadata
API version: v1alpha1
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
package openapi
import (
"encoding/json"
)
// checks if the FilterOptionsList type satisfies the MappedNullable interface at compile time
var _ MappedNullable = &FilterOptionsList{}
// FilterOptionsList List of FilterOptions
type FilterOptionsList struct {
// A single filter option.
Filters *map[string]FilterOption `json:"filters,omitempty"`
}
// NewFilterOptionsList instantiates a new FilterOptionsList object
// This constructor will assign default values to properties that have it defined,
// and makes sure properties required by API are set, but the set of arguments
// will change when the set of required properties is changed
func NewFilterOptionsList() *FilterOptionsList {
this := FilterOptionsList{}
return &this
}
// NewFilterOptionsListWithDefaults instantiates a new FilterOptionsList object
// This constructor will only assign default values to properties that have it defined,
// but it doesn't guarantee that properties required by API are set
func NewFilterOptionsListWithDefaults() *FilterOptionsList {
this := FilterOptionsList{}
return &this
}
// GetFilters returns the Filters field value if set, zero value otherwise.
func (o *FilterOptionsList) GetFilters() map[string]FilterOption {
if o == nil || IsNil(o.Filters) {
var ret map[string]FilterOption
return ret
}
return *o.Filters
}
// GetFiltersOk returns a tuple with the Filters field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *FilterOptionsList) GetFiltersOk() (*map[string]FilterOption, bool) {
if o == nil || IsNil(o.Filters) {
return nil, false
}
return o.Filters, true
}
// HasFilters returns a boolean if a field has been set.
func (o *FilterOptionsList) HasFilters() bool {
if o != nil && !IsNil(o.Filters) {
return true
}
return false
}
// SetFilters gets a reference to the given map[string]FilterOption and assigns it to the Filters field.
func (o *FilterOptionsList) SetFilters(v map[string]FilterOption) {
o.Filters = &v
}
func (o FilterOptionsList) MarshalJSON() ([]byte, error) {
toSerialize, err := o.ToMap()
if err != nil {
return []byte{}, err
}
return json.Marshal(toSerialize)
}
func (o FilterOptionsList) ToMap() (map[string]interface{}, error) {
toSerialize := map[string]interface{}{}
if !IsNil(o.Filters) {
toSerialize["filters"] = o.Filters
}
return toSerialize, nil
}
type NullableFilterOptionsList struct {
value *FilterOptionsList
isSet bool
}
func (v NullableFilterOptionsList) Get() *FilterOptionsList {
return v.value
}
func (v *NullableFilterOptionsList) Set(val *FilterOptionsList) {
v.value = val
v.isSet = true
}
func (v NullableFilterOptionsList) IsSet() bool {
return v.isSet
}
func (v *NullableFilterOptionsList) Unset() {
v.value = nil
v.isSet = false
}
func NewNullableFilterOptionsList(val *FilterOptionsList) *NullableFilterOptionsList {
return &NullableFilterOptionsList{value: val, isSet: true}
}
func (v NullableFilterOptionsList) MarshalJSON() ([]byte, error) {
return json.Marshal(v.value)
}
func (v *NullableFilterOptionsList) UnmarshalJSON(src []byte) error {
v.isSet = true
return json.Unmarshal(src, &v.value)
}

View File

@ -7,7 +7,9 @@ ASSERT_FILE_PATH="$1/type_asserts.go"
PROJECT_ROOT=$(realpath "$(dirname "$0")"/..)
# These files generate with incorrect logic:
rm -f "$1/model_metadata_value.go" "$1/model_catalog_artifact.go"
rm -f "$1/model_metadata_value.go" \
"$1/model_catalog_artifact.go" \
"$1/model_filter_option.go"
python3 "${PROJECT_ROOT}/scripts/gen_type_asserts.py" $1 >"$ASSERT_FILE_PATH"