Skip to content

Commit

Permalink
fear(x) : add MapToSlice
Browse files Browse the repository at this point in the history
  • Loading branch information
seefs001 committed Oct 16, 2024
1 parent e4c5fcb commit 92b3054
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 15 deletions.
2 changes: 1 addition & 1 deletion examples/demo/main.go → examples/xcli_example/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/seefs001/xox/xlog"
)

func main() {
func _main() {
app := xcli.NewApp("test", "test application", "1.0.0")

// Enable debug mode if needed
Expand Down
76 changes: 76 additions & 0 deletions x/x.go
Original file line number Diff line number Diff line change
Expand Up @@ -2039,3 +2039,79 @@ func JSONToURLValues(jsonStr string) (url.Values, error) {

return values, nil
}

// MapToSlice transforms a map into a slice based on a specific iteratee function.
// It uses three generic type parameters:
// K: the type of the map keys (must be comparable)
// V: the type of the map values
// R: the type of the resulting slice elements
//
// Parameters:
// - data: the input map to be transformed
// - iteratee: a function that takes a key-value pair from the map and returns a value of type R
//
// Returns:
// - A slice of type []R containing the transformed elements
// - An error if any issues occur during the transformation
//
// Example with type inference:
//
// data := map[string]int{"a": 1, "b": 2, "c": 3}
// result, err := MapToSlice(data, func(k string, v int) string {
// return fmt.Sprintf("%s:%d", k, v)
// })
// if err != nil {
// // handle error
// }
// fmt.Printf("%v\n", result) // Output: [a:1 b:2 c:3] (order may vary)
//
// Example with explicit type parameters:
//
// type Person struct {
// Name string
// Age int
// }
//
// data := map[string]map[string]interface{}{
// "person1": {"name": "Alice", "age": 30},
// "person2": {"name": "Bob", "age": 25},
// }
//
// result, err := MapToSlice[string, map[string]interface{}, Person](data, func(k string, v map[string]interface{}) Person {
// return Person{
// Name: v["name"].(string),
// Age: v["age"].(int),
// }
// })
// if err != nil {
// // handle error
// }
// fmt.Printf("%+v\n", result) // Output: [{Name:Alice Age:30} {Name:Bob Age:25}] (order may vary)
func MapToSlice[K comparable, V any, R any](data map[K]V, iteratee func(K, V) R) ([]R, error) {
var result []R

if iteratee == nil {
// Direct conversion using JSON marshaling/unmarshaling
for _, v := range data {
var item R
jsonData, err := json.Marshal(v)
if err != nil {
return nil, xerror.Wrap(err, "failed to marshal map value")
}

err = json.Unmarshal(jsonData, &item)
if err != nil {
return nil, xerror.Wrap(err, "failed to unmarshal into struct")
}

result = append(result, item)
}
} else {
// Custom transformation using the provided iteratee function
for k, v := range data {
result = append(result, iteratee(k, v))
}
}

return result, nil
}
116 changes: 116 additions & 0 deletions x/x_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1606,3 +1606,119 @@ func TestJSONToURLValues(t *testing.T) {
})
}
}

func TestMapToSlice(t *testing.T) {
t.Run("Direct conversion with struct", func(t *testing.T) {
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}

data := map[string]interface{}{
"person1": map[string]interface{}{"name": "Alice", "age": 30},
"person2": map[string]interface{}{"name": "Bob", "age": 25},
}

data2 := map[string]Person{
"person1": {Name: "Alice", Age: 30},
"person2": {Name: "Bob", Age: 25},
}

result, err := x.MapToSlice[string, interface{}, Person](data, nil)
assert.NoError(t, err)
assert.Len(t, result, 2)

result2, err := x.MapToSlice(data2, func(k string, v Person) Person {
return v
})
assert.NoError(t, err)
assert.Len(t, result2, 2)

expected := []Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
}
assert.ElementsMatch(t, expected, result)
})

t.Run("Custom transformation", func(t *testing.T) {
data := map[int]int64{1: 4, 2: 5, 3: 6}

result, err := x.MapToSlice(data, func(k int, v int64) string {
return fmt.Sprintf("%d_%d", k, v)
})

assert.NoError(t, err)
assert.Len(t, result, 3)
assert.ElementsMatch(t, []string{"1_4", "2_5", "3_6"}, result)
})

t.Run("Empty map", func(t *testing.T) {
data := map[string]int{}

result, err := x.MapToSlice(data, func(k string, v int) string {
return fmt.Sprintf("%s:%d", k, v)
})

assert.NoError(t, err)
assert.Empty(t, result)
})

t.Run("Nil map", func(t *testing.T) {
var data map[string]int

result, err := x.MapToSlice(data, func(k string, v int) string {
return fmt.Sprintf("%s:%d", k, v)
})

assert.NoError(t, err)
assert.Nil(t, result)
})

t.Run("Map with interface{} values", func(t *testing.T) {
data := map[string]interface{}{
"a": 1,
"b": "hello",
"c": true,
}

result, err := x.MapToSlice(data, func(k string, v interface{}) string {
return fmt.Sprintf("%s:%v", k, v)
})

assert.NoError(t, err)
assert.Len(t, result, 3)
assert.ElementsMatch(t, []string{"a:1", "b:hello", "c:true"}, result)
})

t.Run("Direct conversion with non-struct type", func(t *testing.T) {
data := map[string]int{"a": 1, "b": 2, "c": 3}

result, err := x.MapToSlice[string, int, int](data, nil)
assert.NoError(t, err)
assert.Len(t, result, 3)
assert.ElementsMatch(t, []int{1, 2, 3}, result)
})

t.Run("Direct conversion with unmarshalable data", func(t *testing.T) {
data := map[string]interface{}{
"a": make(chan int), // channels are not JSON marshalable
}

_, err := x.MapToSlice[string, interface{}, interface{}](data, nil)
assert.Error(t, err)
})

t.Run("Direct conversion with invalid JSON", func(t *testing.T) {
type InvalidStruct struct {
Field int `json:"field"`
}

data := map[string]interface{}{
"a": map[string]interface{}{"field": "not an int"},
}

_, err := x.MapToSlice[string, interface{}, InvalidStruct](data, nil)
assert.Error(t, err)
})
}
60 changes: 46 additions & 14 deletions xai/oai.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,53 @@ type Choice struct {
Logprobs interface{} `json:"logprobs"`
}

// Update CreateChatCompletionRequest to include tools and tool_choice
// Update CreateChatCompletionRequest to include response_format
type CreateChatCompletionRequest struct {
Model string `json:"model"`
Messages []ChatCompletionMessage `json:"messages"`
Temperature float32 `json:"temperature,omitempty"`
TopP float32 `json:"top_p,omitempty"`
N int `json:"n,omitempty"`
Stream bool `json:"stream,omitempty"`
Stop []string `json:"stop,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
Tools []Tool `json:"tools,omitempty"`
ToolChoice string `json:"tool_choice,omitempty"`
}

// Update CreateChatCompletionResponse to include usage details
Model string `json:"model"`
Messages []ChatCompletionMessage `json:"messages"`
Temperature float32 `json:"temperature,omitempty"`
TopP float32 `json:"top_p,omitempty"`
N int `json:"n,omitempty"`
Stream bool `json:"stream,omitempty"`
Stop []string `json:"stop,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
Tools []Tool `json:"tools,omitempty"`
ToolChoice string `json:"tool_choice,omitempty"`
ResponseFormat *ResponseFormat `json:"response_format,omitempty"`
}

// ResponseFormat specifies the format that the model must output.
// Compatible with GPT-4, GPT-4 Turbo, and GPT-3.5 Turbo models newer than gpt-3.5-turbo-1106.
type ResponseFormat struct {
// Type is the type of response format being defined.
// Possible values: "text", "json_object", "json_schema"
Type string `json:"type"`

// JSONSchema is used when Type is "json_schema".
// It enables Structured Outputs which ensures the model will match your supplied JSON schema.
JSONSchema *JSONSchemaFormat `json:"json_schema,omitempty"`
}

// JSONSchemaFormat defines the structure for JSON schema output.
type JSONSchemaFormat struct {
// Description of what the response format is for,
// used by the model to determine how to respond in the format.
Description string `json:"description,omitempty"`

// Name of the response format. Must be a-z, A-Z, 0-9,
// or contain underscores and dashes, with a maximum length of 64.
Name string `json:"name"`

// Schema for the response format, described as a JSON Schema object.
Schema json.RawMessage `json:"schema,omitempty"`

// Strict enables strict schema adherence when generating the output.
// If true, the model will always follow the exact schema defined in the Schema field.
// Only a subset of JSON Schema is supported when Strict is true.
Strict *bool `json:"strict,omitempty"`
}

// CreateChatCompletionResponse represents the response from the OpenAI API for chat completion requests.
type CreateChatCompletionResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Expand Down

0 comments on commit 92b3054

Please sign in to comment.