Skip to content
Merged
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
94 changes: 94 additions & 0 deletions internal/tool/examples_nested_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package tool_test

import (
"encoding/json"
"reflect"
"testing"

itool "trpc.group/trpc-go/trpc-agent-go/internal/tool"
)

// Pet defines the user's fury friend.
type Pet struct {
// Name of the animal.
Name string `json:"name" jsonschema:"title=Name"`
// Animal type, e.g., dog, cat, hamster.
AnimalType AnimalType `json:"animal_type" jsonschema:"title=Animal Type"`
}

type AnimalType string

// Pets is a collection of Pet objects.
type Pets []*Pet

// NamedPets is a map of animal names to pets.
type NamedPets map[string]*Pet

type (
// Plant represents the plants the user might have and serves as a test
// of structs inside a `type` set.
Plant struct {
Variant string `json:"variant" jsonschema:"title=Variant"` // This comment will be used
// Multicellular is true if the plant is multicellular
Multicellular bool `json:"multicellular,omitempty" jsonschema:"title=Multicellular"` // This comment will be ignored
}
)
type User struct {
// Unique sequential identifier.
ID int `json:"id" jsonschema:"required"`
// This comment will be ignored
Name string `json:"name" jsonschema:"required,minLength=1,maxLength=20,pattern=.*,description=this is a property,title=the name,example=joe,example=lucy,default=alex"`
Friends []int `json:"friends,omitempty" jsonschema_description:"list of IDs, omitted when empty"`
Tags map[string]any `json:"tags,omitempty"`

// An array of pets the user cares for.
Pets Pets `json:"pets"`

// Set of animal names to pets
NamedPets NamedPets `json:"named_pets"`

// Set of plants that the user likes
Plants []*Plant `json:"plants" jsonschema:"title=Plants"`
}

func Test_GenerateJSONSchema_User(t *testing.T) {
s := itool.GenerateJSONSchema(reflect.TypeOf(&User{}))
data, err := json.MarshalIndent(s, "", " ")
if err != nil {
panic(err.Error())
}
t.Log(string(data))
}

func Test_GenerateJSONSchema_TreeNode(t *testing.T) {
s := itool.GenerateJSONSchema(reflect.TypeOf(&TreeNode{}))
data, err := json.MarshalIndent(s, "", " ")
if err != nil {
panic(err.Error())
}
t.Log(string(data))
}

func Test_GenerateJSONSchema_LinkedListNode(t *testing.T) {
s := itool.GenerateJSONSchema(reflect.TypeOf(&LinkedListNode{}))
data, err := json.MarshalIndent(s, "", " ")
if err != nil {
panic(err.Error())
}
t.Log(string(data))
}

type TreeLinkListNode struct {
Name string `json:"name"`
TreeNode *TreeNode `json:"tree_node,omitempty"`
LinkListNode *LinkedListNode `json:"link_list_node,omitempty"`
}

func Test_GenerateJSONSchema_TreeLinkListNode(t *testing.T) {
s := itool.GenerateJSONSchema(reflect.TypeOf(&TreeLinkListNode{}))
data, err := json.MarshalIndent(s, "", " ")
if err != nil {
panic(err.Error())
}
t.Log(string(data))
}
152 changes: 152 additions & 0 deletions internal/tool/recursive_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//
// Tencent is pleased to support the open source community by making trpc-agent-go available.
//
// Copyright (C) 2025 Tencent. All rights reserved.
//
// trpc-agent-go is licensed under the Apache License Version 2.0.
//
//

package tool_test

import (
"encoding/json"
"reflect"
"testing"

itool "trpc.group/trpc-go/trpc-agent-go/internal/tool"
)

// TreeNode represents a recursive tree structure
type TreeNode struct {
Name string `json:"name"`
Children []*TreeNode `json:"children,omitempty"`
}

// LinkedListNode represents a recursive linked list structure
type LinkedListNode struct {
Value int `json:"value"`
Next *LinkedListNode `json:"next,omitempty"`
}

// MutuallyRecursiveA and MutuallyRecursiveB represent mutually recursive structures
type MutuallyRecursiveA struct {
Name string `json:"name"`
B *MutuallyRecursiveB `json:"b,omitempty"`
}

type MutuallyRecursiveB struct {
Value int `json:"value"`
A *MutuallyRecursiveA `json:"a,omitempty"`
}

func TestGenerateJSONSchema_RecursiveStructure(t *testing.T) {
t.Run("tree node recursive structure", func(t *testing.T) {
// This should not panic and should generate a schema with $ref and $defs
result := itool.GenerateJSONSchema(reflect.TypeOf(TreeNode{}))
resultJson, _ := json.MarshalIndent(result, "", " ")
t.Logf("%s", resultJson)

if result.Type != "object" {
t.Errorf("expected object type, got %s", result.Type)
}

// Check that we have properties
if result.Properties == nil {
t.Fatal("expected properties to be set")
}

// Check name property
if result.Properties["name"] == nil || result.Properties["name"].Type != "string" {
t.Errorf("expected name property of type string")
}

// Check children property
if result.Properties["children"] == nil || result.Properties["children"].Type != "array" {
t.Errorf("expected children property of type array")
}

// The children items should use $ref to avoid infinite recursion
if result.Properties["children"].Items == nil {
t.Fatal("expected children items to be defined")
}

// Check if we have $defs defined for recursive types
if result.Defs == nil {
t.Errorf("expected $defs to be defined for recursive structure")
}

// Check that children items use $ref
if result.Properties["children"].Items.Ref != "#/$defs/treenode" {
t.Errorf("expected children items to reference #/$defs/treenode, got %s", result.Properties["children"].Items.Ref)
}

// Check the definition in $defs
treeDef := result.Defs["treenode"]
if treeDef == nil {
t.Fatal("expected treenode definition in $defs")
}

if treeDef.Type != "object" {
t.Errorf("expected treenode definition type to be object, got %s", treeDef.Type)
}

// Check that the definition also uses $ref for children
if treeDef.Properties["children"].Items == nil || treeDef.Properties["children"].Items.Ref != "#/$defs/treenode" {
t.Errorf("expected treenode definition children items to reference #/$defs/treenode")
}
})

t.Run("linked list recursive structure", func(t *testing.T) {
// This should not panic and should generate a schema with $ref and $defs
result := itool.GenerateJSONSchema(reflect.TypeOf(LinkedListNode{}))
resultJson, _ := json.MarshalIndent(result, "", " ")
t.Logf("%s", resultJson)

if result.Type != "object" {
t.Errorf("expected object type, got %s", result.Type)
}

// Check that we have properties
if result.Properties == nil {
t.Fatal("expected properties to be set")
}

// Check value property
if result.Properties["value"] == nil || result.Properties["value"].Type != "integer" {
t.Errorf("expected value property of type integer")
}

// Check next property - should use $ref to avoid infinite recursion
if result.Properties["next"] == nil {
t.Fatal("expected next property to be defined")
}

// Check if we have $defs defined for recursive types
if result.Defs == nil {
t.Errorf("expected $defs to be defined for recursive structure")
}
})

t.Run("mutually recursive structures", func(t *testing.T) {
// This should not panic and should generate a schema with $ref and $defs
result := itool.GenerateJSONSchema(reflect.TypeOf(MutuallyRecursiveA{}))
resultJson, _ := json.MarshalIndent(result, "", " ")
t.Logf("%s", resultJson)

if result.Type != "object" {
t.Errorf("expected object type, got %s", result.Type)
}

// Check that we have $defs for both types
if result.Defs == nil {
t.Fatal("expected $defs to be defined for mutually recursive structures")
}

// Should have definitions for both types
expectedDefs := 2 // MutuallyRecursiveA and MutuallyRecursiveB
if len(result.Defs) < expectedDefs {
t.Errorf("expected at least %d definitions in $defs, got %d", expectedDefs, len(result.Defs))
}
})
}
Loading
Loading