Skip to content

Commit ea16f2e

Browse files
authored
[#genai] Fixed func names (#118)
* [#genai] Fixed func names, removed last flag from streaming handler, added schema gen
1 parent 570fd90 commit ea16f2e

File tree

4 files changed

+341
-8
lines changed

4 files changed

+341
-8
lines changed

data/schema.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
11
package data
22

3+
const (
4+
// SchemaTypeBool is a bool schema type.
5+
SchemaTypeBool = "boolean"
6+
// SchemaTypeString is a string schema type.
7+
SchemaTypeString = "string"
8+
//SchemaTypeNumber is a number schema type.
9+
SchemaTypeNumber = "number"
10+
// SchemaTypeObject is an object schema type.
11+
SchemaTypeObject = "object"
12+
// SchemaTypeArray is an array schema type.
13+
SchemaTypeArray = "array"
14+
// SchemaTypeInteger is an integer schema type.
15+
SchemaTypeInteger = "integer"
16+
17+
// SchemaFormatFloat is a float schema type.
18+
SchemaFormatFloat = "float"
19+
// SchemaFormatDouble is a double schema type.
20+
SchemaFormatDouble = "double"
21+
// SchemaFormatInt32 is a int32 schema type.
22+
SchemaFormatInt32 = "int32"
23+
// SchemaFormatInt64 is a int64 schema type.
24+
SchemaFormatInt64 = "int64"
25+
)
26+
327
// Schema used to define structure of the data
428
// This is a subset of OpenAPI 3.0 schema
529
type Schema struct {

data/schema_gen.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package data
2+
3+
import (
4+
"errors"
5+
"reflect"
6+
"strings"
7+
)
8+
9+
// ErrUnsupportedType is returned when the type is not supported
10+
var ErrUnsupportedType = errors.New("unsupported type")
11+
12+
// GenerateSchema converts a Go type to a JSON Schema.
13+
//
14+
// This function uses reflection to analyze Go types and produce corresponding JSON Schema representations.
15+
// It supports various Go types including structs, slices, maps, primitive types, and pointers.
16+
//
17+
// For structs, it creates an object schema with properties corresponding to the struct fields.
18+
// JSON field names are extracted from json tags if present.
19+
// Unexported fields are skipped.
20+
//
21+
// For slices, it creates an array schema with the element type defined in the Items field.
22+
//
23+
// For maps, it creates an object schema with the value type defined in AdditionalItems.
24+
//
25+
// The function handles pointers by resolving to their base types.
26+
//
27+
// Parameters:
28+
// - t: The reflect.Type to convert to a JSON Schema
29+
//
30+
// Returns:
31+
// - schema: A pointer to the generated Schema
32+
// - err: An error if the type is unsupported or if a nested type cannot be processed
33+
func GenerateSchema(t reflect.Type) (schema *Schema, err error) {
34+
35+
switch t.Kind() {
36+
case reflect.Ptr:
37+
schema, err = GenerateSchema(t.Elem())
38+
39+
case reflect.Struct:
40+
41+
schema = &Schema{
42+
Type: "object",
43+
Properties: make(map[string]*Schema),
44+
}
45+
for i := 0; i < t.NumField(); i++ {
46+
field := t.Field(i)
47+
if field.PkgPath != "" {
48+
continue
49+
}
50+
prop, err := GenerateSchema(field.Type)
51+
if err != nil {
52+
return nil, err
53+
}
54+
fieldName := field.Name
55+
if tag, ok := field.Tag.Lookup("json"); ok {
56+
57+
// check and Split the tag by comma and take the first part as the field name
58+
parts := strings.Split(tag, ",")
59+
if parts[0] != "" {
60+
fieldName = parts[0]
61+
} else {
62+
fieldName = tag
63+
}
64+
65+
}
66+
schema.Properties[fieldName] = prop
67+
}
68+
case reflect.Slice:
69+
schema = &Schema{
70+
Type: "array",
71+
}
72+
elemSchema, err := GenerateSchema(t.Elem())
73+
if err != nil {
74+
return nil, err
75+
}
76+
schema.Items = elemSchema
77+
case reflect.Map:
78+
schema = &Schema{
79+
Type: "object",
80+
Properties: make(map[string]*Schema),
81+
}
82+
elemSchema, err := GenerateSchema(t.Elem())
83+
if err != nil {
84+
return nil, err
85+
}
86+
schema.AdditionalItems = elemSchema
87+
88+
case reflect.String:
89+
schema = &Schema{
90+
Type: "string",
91+
}
92+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
93+
schema = &Schema{
94+
Type: "integer",
95+
}
96+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
97+
schema = &Schema{
98+
Type: "integer",
99+
}
100+
case reflect.Float32, reflect.Float64:
101+
schema = &Schema{
102+
Type: "number",
103+
}
104+
case reflect.Bool:
105+
schema = &Schema{
106+
Type: "boolean",
107+
}
108+
case reflect.Interface:
109+
schema = &Schema{
110+
Type: "object",
111+
}
112+
113+
default:
114+
err = ErrUnsupportedType
115+
}
116+
117+
return
118+
}

data/schema_gen_test.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// TestGenerateSchema tests the GenerateSchema function with various input types.
2+
// It verifies that the function correctly generates JSON schemas for different Go types:
3+
// - primitive types (string, integer, float, boolean)
4+
// - composite types (slices, maps, structs)
5+
// - pointers to structs
6+
// - structs with custom JSON tags
7+
// - error handling for unsupported types (channels)
8+
//
9+
// Each test case validates that the generated schema matches the expected schema structure,
10+
// including correct type identification, items definitions for arrays, additional items for maps,
11+
// and property mappings for struct fields with proper JSON tag handling.
12+
package data
13+
14+
import (
15+
"reflect"
16+
"testing"
17+
)
18+
19+
func TestGenerateSchema(t *testing.T) {
20+
tests := []struct {
21+
name string
22+
input interface{}
23+
expected *Schema
24+
expectError bool
25+
}{
26+
{
27+
name: "string type",
28+
input: "",
29+
expected: &Schema{
30+
Type: "string",
31+
},
32+
},
33+
{
34+
name: "integer type",
35+
input: 0,
36+
expected: &Schema{
37+
Type: "integer",
38+
},
39+
},
40+
{
41+
name: "float type",
42+
input: 0.0,
43+
expected: &Schema{
44+
Type: "number",
45+
},
46+
},
47+
{
48+
name: "boolean type",
49+
input: false,
50+
expected: &Schema{
51+
Type: "boolean",
52+
},
53+
},
54+
{
55+
name: "slice type",
56+
input: []string{},
57+
expected: &Schema{
58+
Type: "array",
59+
Items: &Schema{
60+
Type: "string",
61+
},
62+
},
63+
},
64+
{
65+
name: "map type",
66+
input: map[string]int{},
67+
expected: &Schema{
68+
Type: "object",
69+
Properties: map[string]*Schema{},
70+
AdditionalItems: &Schema{
71+
Type: "integer",
72+
},
73+
},
74+
},
75+
{
76+
name: "struct type",
77+
input: struct {
78+
Name string `json:"name"`
79+
Age int `json:"age"`
80+
}{},
81+
expected: &Schema{
82+
Type: "object",
83+
Properties: map[string]*Schema{
84+
"name": {
85+
Type: "string",
86+
},
87+
"age": {
88+
Type: "integer",
89+
},
90+
},
91+
},
92+
},
93+
{
94+
name: "struct with pointer",
95+
input: &struct {
96+
Name string `json:"name"`
97+
}{},
98+
expected: &Schema{
99+
Type: "object",
100+
Properties: map[string]*Schema{
101+
"name": {
102+
Type: "string",
103+
},
104+
},
105+
},
106+
},
107+
{
108+
name: "struct with custom json tag",
109+
input: struct {
110+
UserName string `json:"user_name"`
111+
IsActive bool `json:"is_active,omitempty"`
112+
}{},
113+
expected: &Schema{
114+
Type: "object",
115+
Properties: map[string]*Schema{
116+
"user_name": {
117+
Type: "string",
118+
},
119+
"is_active": {
120+
Type: "boolean",
121+
},
122+
},
123+
},
124+
},
125+
{
126+
name: "unsupported type",
127+
input: make(chan int),
128+
expectError: true,
129+
},
130+
}
131+
132+
for _, tt := range tests {
133+
t.Run(tt.name, func(t *testing.T) {
134+
schema, err := GenerateSchema(reflect.TypeOf(tt.input))
135+
136+
if tt.expectError {
137+
if err == nil {
138+
t.Errorf("expected error but got nil")
139+
}
140+
return
141+
}
142+
143+
if err != nil {
144+
t.Errorf("unexpected error: %v", err)
145+
return
146+
}
147+
148+
if schema == nil {
149+
t.Errorf("schema is nil")
150+
return
151+
}
152+
153+
if schema.Type != tt.expected.Type {
154+
t.Errorf("expected type %s but got %s", tt.expected.Type, schema.Type)
155+
}
156+
157+
if tt.expected.Items != nil {
158+
if schema.Items == nil {
159+
t.Errorf("expected Items but got nil")
160+
} else if schema.Items.Type != tt.expected.Items.Type {
161+
t.Errorf("expected Items.Type %s but got %s", tt.expected.Items.Type, schema.Items.Type)
162+
}
163+
}
164+
165+
if tt.expected.AdditionalItems != nil {
166+
if schema.AdditionalItems == nil {
167+
t.Errorf("expected AdditionalItems but got nil")
168+
} else if schema.AdditionalItems.Type != tt.expected.AdditionalItems.Type {
169+
t.Errorf("expected AdditionalItems.Type %s but got %s", tt.expected.AdditionalItems.Type, schema.AdditionalItems.Type)
170+
}
171+
}
172+
173+
if tt.expected.Properties != nil {
174+
if schema.Properties == nil {
175+
t.Errorf("expected Properties but got nil")
176+
} else if len(schema.Properties) != len(tt.expected.Properties) {
177+
t.Errorf("expected Properties length %d but got %d", len(tt.expected.Properties), len(schema.Properties))
178+
} else {
179+
for k, v := range tt.expected.Properties {
180+
prop, ok := schema.Properties[k]
181+
if !ok {
182+
t.Errorf("expected property %s but not found", k)
183+
} else if prop.Type != v.Type {
184+
t.Errorf("expected property %s type %s but got %s", k, v.Type, prop.Type)
185+
}
186+
}
187+
}
188+
}
189+
})
190+
}
191+
}

genai/provider.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ var GetUnsupportedProviderErr = errutils.NewCustomError("unsupported provider fo
3434

3535
var Providers managers.ItemManager[Provider] = managers.NewItemManager[Provider]()
3636

37-
type StreamingHandller func(last bool, messages ...*Message)
37+
type StreamingHandller func(messages ...*Message)
3838

3939
type Model struct {
4040
Id string `json:"id" yaml:"id" bson:"id"`
@@ -394,11 +394,11 @@ func (o *Options) GetEcho(defaultValue bool) bool {
394394
return defaultValue
395395
}
396396

397-
// GetOutputMimes retrieves the "output_mimes" option from the Options.
397+
// GetOutputMime retrieves the "output_mimes" option from the Options.
398398
// Returns the value as a slice of strings, or the provided default value if the option does not exist.
399-
func (o *Options) GetOutputMimes(defaultValue []string) []string {
399+
func (o *Options) GetOutputMime(defaultValue string) string {
400400
if o.Has(OptionOutputMime) {
401-
return o.GetStrings(OptionOutputMime)
401+
return o.GetString(OptionOutputMime)
402402
}
403403
return defaultValue
404404
}
@@ -694,14 +694,14 @@ func (o *OptionsBuilder) SetEcho(echo bool) *OptionsBuilder {
694694
return o
695695
}
696696

697-
// SetOutputMimes sets the output MIME types for the OptionsBuilder.
697+
// SetOutputMime sets the output MIME types for the OptionsBuilder.
698698
// It accepts a variable number of string arguments representing the MIME types
699699
// and returns a pointer to the updated OptionsBuilder.
700700
//
701701
// Example usage:
702702
//
703703
// builder := &OptionsBuilder{}
704-
// builder.SetOutputMimes("application/json", "text/plain")
704+
// builder.SetOutputMime("application/json", "text/plain")
705705
//
706706
// Parameters:
707707
//
@@ -710,8 +710,8 @@ func (o *OptionsBuilder) SetEcho(echo bool) *OptionsBuilder {
710710
// Returns:
711711
//
712712
// *OptionsBuilder: A pointer to the updated OptionsBuilder instance.
713-
func (o *OptionsBuilder) SetOutputMimes(outputMimes string) *OptionsBuilder {
714-
o.options.values[OptionOutputMime] = outputMimes
713+
func (o *OptionsBuilder) SetOutputMime(outputMime string) *OptionsBuilder {
714+
o.options.values[OptionOutputMime] = outputMime
715715
return o
716716
}
717717

0 commit comments

Comments
 (0)