Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions typesense/api/generator/generator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ components:
- fields
type: object
CollectionUpdateSchema:
minProperties: 1
properties:
fields:
description: A list of fields for querying, filtering and faceting
Expand All @@ -396,6 +397,7 @@ components:
items:
$ref: '#/components/schemas/Field'
type: array
x-go-type-skip-optional-pointer: true
metadata:
description: |
Optional details about the collection, e.g., when it was created, who created it etc.
Expand All @@ -406,8 +408,6 @@ components:
example: synonym_set_1
type: string
type: array
required:
- fields
type: object
ConversationModelCreateSchema:
allOf:
Expand Down Expand Up @@ -1075,14 +1075,14 @@ components:
properties:
conversation:
$ref: '#/components/schemas/SearchResultConversation'
results:
items:
$ref: '#/components/schemas/MultiSearchResultItem'
type: array
hits:
items:
$ref: '#/components/schemas/SearchResultHit'
type: array
results:
items:
$ref: '#/components/schemas/MultiSearchResultItem'
type: array
required:
- results
type: object
Expand Down
19 changes: 19 additions & 0 deletions typesense/api/generator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ func processOpenAPISpec(m *yml) {
// Remove additionalProperties from SearchResultHit -> document
log.Println("Removing additionalProperties from SearchResultHit")
searchResultHit(m)
// Keep CollectionUpdateSchema -> fields as a plain slice
log.Println("Skipping the optional pointer for CollectionUpdateSchema fields")
collectionUpdateFields(m)
// Extract anonymous structs to named types
log.Println("Extracting anonymous structs to named types")
extractAnonymousStructs(m)
Expand Down Expand Up @@ -147,6 +150,22 @@ func searchResultHit(m *yml) {
delete(document, "additionalProperties")
}

// collectionUpdateFields keeps CollectionUpdateSchema -> fields generated as []Field
// rather than *[]Field.
//
// The collection update endpoint accepts an update that changes only `metadata`, so
// the spec does not list `fields` as required. oapi-codegen renders an optional array
// as a pointer, which would break every caller constructing a CollectionUpdateSchema.
// x-go-type-skip-optional-pointer leaves the slice unwrapped, and `omitempty` is still
// applied because the property is optional -- so a nil or empty Fields sends no
// `fields` key at all. That is what the endpoint needs: it rejects both `"fields":null`
// and `"fields":[]` with a 400.
func collectionUpdateFields(m *yml) {
properties := (*m)["components"].(yml)["schemas"].(yml)["CollectionUpdateSchema"].(yml)["properties"].(yml)
fields := properties["fields"].(yml)
fields["x-go-type-skip-optional-pointer"] = true
}

func unwrapDeleteDocument(m *yml) {
parameters := (*m)["paths"].(yml)["/collections/{collectionName}/documents"].(yml)["delete"].(yml)["parameters"].([]interface{})
deleteParameters := parameters[1].(yml)["schema"].(yml)["properties"].(yml)
Expand Down
3 changes: 1 addition & 2 deletions typesense/api/generator/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2479,9 +2479,8 @@ components:
description: >
Optional details about the collection, e.g., when it was created, who created it etc.
CollectionUpdateSchema:
required:
- fields
type: object
minProperties: 1
properties:
fields:
type: array
Expand Down
2 changes: 1 addition & 1 deletion typesense/api/types_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions typesense/collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package typesense

import (
"context"
"encoding/json"
"errors"
"net/http"
"testing"
Expand Down Expand Up @@ -178,6 +179,46 @@ func TestCollectionUpdate(t *testing.T) {
assert.Equal(t, expectedResult, result)
}

// A collection update that changes only the metadata must not send a `fields` key at
// all: Typesense rejects both `"fields":null` and `"fields":[]` with a 400. This relies
// on the `omitempty` tag generated for CollectionUpdateSchema.Fields.
func TestCollectionUpdateSchemaOmitsEmptyFields(t *testing.T) {
tests := []struct {
name string
fields []api.Field
wantFields bool
}{
{
name: "nil fields",
fields: nil,
},
{
name: "empty fields",
fields: []api.Field{},
},
{
name: "populated fields",
fields: []api.Field{{Name: "country", Drop: pointer.True()}},
wantFields: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
body, err := json.Marshal(&api.CollectionUpdateSchema{
Fields: tt.fields,
Metadata: &map[string]interface{}{"revision": "2"},
})
assert.NoError(t, err)

payload := map[string]json.RawMessage{}
assert.NoError(t, json.Unmarshal(body, &payload))

_, hasFields := payload["fields"]
assert.Equal(t, tt.wantFields, hasFields, "body: %s", body)
})
}
}

func TestCollectionUpdateOnApiClientErrorReturnsError(t *testing.T) {
updateSchema := updateExistingSchema()

Expand Down
30 changes: 30 additions & 0 deletions typesense/test/collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,33 @@ func TestCollectionUpdate(t *testing.T) {
require.Equal(t, pointer.True(), result.Fields[0].Drop)
require.Equal(t, "2", (*result.Metadata)["revision"].(string))
}

// Updating only the metadata has to leave `fields` out of the request entirely, since
// Typesense rejects both `"fields":null` and `"fields":[]` with a 400.
func TestCollectionUpdateMetadataOnly(t *testing.T) {
for _, tt := range []struct {
name string
fields []api.Field
}{
{name: "nil fields", fields: nil},
{name: "empty fields", fields: []api.Field{}},
} {
t.Run(tt.name, func(t *testing.T) {
collectionName := createNewCollection(t, "companies")

result, err := typesenseClient.Collection(collectionName).Update(context.Background(),
&api.CollectionUpdateSchema{
Fields: tt.fields,
Metadata: &map[string]interface{}{"revision": "2"},
})
require.NoError(t, err)
require.Equal(t, "2", (*result.Metadata)["revision"].(string))

// Confirm the change reached the collection and left the schema alone.
updated, err := typesenseClient.Collection(collectionName).Retrieve(context.Background())
require.NoError(t, err)
require.Equal(t, "2", (*updated.Metadata)["revision"].(string))
require.Len(t, updated.Fields, 3)
})
}
}
Loading