427 lines
8.0 KiB
Go
427 lines
8.0 KiB
Go
package jsonschema_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"git.vaala.cloud/VaalaCat/go-openai/jsonschema"
|
|
)
|
|
|
|
func TestDefinition_MarshalJSON(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
def jsonschema.Definition
|
|
want string
|
|
}{
|
|
{
|
|
name: "Test with empty Definition",
|
|
def: jsonschema.Definition{},
|
|
want: `{}`,
|
|
},
|
|
{
|
|
name: "Test with Definition properties set",
|
|
def: jsonschema.Definition{
|
|
Type: jsonschema.String,
|
|
Description: "A string type",
|
|
Properties: map[string]jsonschema.Definition{
|
|
"name": {
|
|
Type: jsonschema.String,
|
|
},
|
|
},
|
|
},
|
|
want: `{
|
|
"type":"string",
|
|
"description":"A string type",
|
|
"properties":{
|
|
"name":{
|
|
"type":"string"
|
|
}
|
|
}
|
|
}`,
|
|
},
|
|
{
|
|
name: "Test with nested Definition properties",
|
|
def: jsonschema.Definition{
|
|
Type: jsonschema.Object,
|
|
Properties: map[string]jsonschema.Definition{
|
|
"user": {
|
|
Type: jsonschema.Object,
|
|
Properties: map[string]jsonschema.Definition{
|
|
"name": {
|
|
Type: jsonschema.String,
|
|
},
|
|
"age": {
|
|
Type: jsonschema.Integer,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: `{
|
|
"type":"object",
|
|
"properties":{
|
|
"user":{
|
|
"type":"object",
|
|
"properties":{
|
|
"name":{
|
|
"type":"string"
|
|
},
|
|
"age":{
|
|
"type":"integer"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}`,
|
|
},
|
|
{
|
|
name: "Test with complex nested Definition",
|
|
def: jsonschema.Definition{
|
|
Type: jsonschema.Object,
|
|
Properties: map[string]jsonschema.Definition{
|
|
"user": {
|
|
Type: jsonschema.Object,
|
|
Properties: map[string]jsonschema.Definition{
|
|
"name": {
|
|
Type: jsonschema.String,
|
|
},
|
|
"age": {
|
|
Type: jsonschema.Integer,
|
|
},
|
|
"address": {
|
|
Type: jsonschema.Object,
|
|
Properties: map[string]jsonschema.Definition{
|
|
"city": {
|
|
Type: jsonschema.String,
|
|
},
|
|
"country": {
|
|
Type: jsonschema.String,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: `{
|
|
"type":"object",
|
|
"properties":{
|
|
"user":{
|
|
"type":"object",
|
|
"properties":{
|
|
"name":{
|
|
"type":"string"
|
|
},
|
|
"age":{
|
|
"type":"integer"
|
|
},
|
|
"address":{
|
|
"type":"object",
|
|
"properties":{
|
|
"city":{
|
|
"type":"string"
|
|
},
|
|
"country":{
|
|
"type":"string"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}`,
|
|
},
|
|
{
|
|
name: "Test with Array type Definition",
|
|
def: jsonschema.Definition{
|
|
Type: jsonschema.Array,
|
|
Items: &jsonschema.Definition{
|
|
Type: jsonschema.String,
|
|
},
|
|
Properties: map[string]jsonschema.Definition{
|
|
"name": {
|
|
Type: jsonschema.String,
|
|
},
|
|
},
|
|
},
|
|
want: `{
|
|
"type":"array",
|
|
"items":{
|
|
"type":"string"
|
|
},
|
|
"properties":{
|
|
"name":{
|
|
"type":"string"
|
|
}
|
|
}
|
|
}`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
wantBytes := []byte(tt.want)
|
|
var want map[string]interface{}
|
|
err := json.Unmarshal(wantBytes, &want)
|
|
if err != nil {
|
|
t.Errorf("Failed to Unmarshal JSON: error = %v", err)
|
|
return
|
|
}
|
|
|
|
got := structToMap(t, tt.def)
|
|
gotPtr := structToMap(t, &tt.def)
|
|
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("MarshalJSON() got = %v, want %v", got, want)
|
|
}
|
|
if !reflect.DeepEqual(gotPtr, want) {
|
|
t.Errorf("MarshalJSON() gotPtr = %v, want %v", gotPtr, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStructToSchema(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in any
|
|
want string
|
|
}{
|
|
{
|
|
name: "Test with empty struct",
|
|
in: struct{}{},
|
|
want: `{
|
|
"type":"object",
|
|
"additionalProperties":false
|
|
}`,
|
|
},
|
|
{
|
|
name: "Test with struct containing many fields",
|
|
in: struct {
|
|
Name string `json:"name"`
|
|
Age int `json:"age"`
|
|
Active bool `json:"active"`
|
|
Height float64 `json:"height"`
|
|
Cities []struct {
|
|
Name string `json:"name"`
|
|
State string `json:"state"`
|
|
} `json:"cities"`
|
|
}{
|
|
Name: "John Doe",
|
|
Age: 30,
|
|
Cities: []struct {
|
|
Name string `json:"name"`
|
|
State string `json:"state"`
|
|
}{
|
|
{Name: "New York", State: "NY"},
|
|
{Name: "Los Angeles", State: "CA"},
|
|
},
|
|
},
|
|
want: `{
|
|
"type":"object",
|
|
"properties":{
|
|
"name":{
|
|
"type":"string"
|
|
},
|
|
"age":{
|
|
"type":"integer"
|
|
},
|
|
"active":{
|
|
"type":"boolean"
|
|
},
|
|
"height":{
|
|
"type":"number"
|
|
},
|
|
"cities":{
|
|
"type":"array",
|
|
"items":{
|
|
"additionalProperties":false,
|
|
"type":"object",
|
|
"properties":{
|
|
"name":{
|
|
"type":"string"
|
|
},
|
|
"state":{
|
|
"type":"string"
|
|
}
|
|
},
|
|
"required":["name","state"]
|
|
}
|
|
}
|
|
},
|
|
"required":["name","age","active","height","cities"],
|
|
"additionalProperties":false
|
|
}`,
|
|
},
|
|
{
|
|
name: "Test with description tag",
|
|
in: struct {
|
|
Name string `json:"name" description:"The name of the person"`
|
|
}{
|
|
Name: "John Doe",
|
|
},
|
|
want: `{
|
|
"type":"object",
|
|
"properties":{
|
|
"name":{
|
|
"type":"string",
|
|
"description":"The name of the person"
|
|
}
|
|
},
|
|
"required":["name"],
|
|
"additionalProperties":false
|
|
}`,
|
|
},
|
|
{
|
|
name: "Test with required tag",
|
|
in: struct {
|
|
Name string `json:"name" required:"false"`
|
|
}{
|
|
Name: "John Doe",
|
|
},
|
|
want: `{
|
|
"type":"object",
|
|
"properties":{
|
|
"name":{
|
|
"type":"string"
|
|
}
|
|
},
|
|
"additionalProperties":false
|
|
}`,
|
|
},
|
|
{
|
|
name: "Test with enum tag",
|
|
in: struct {
|
|
Color string `json:"color" enum:"red,green,blue"`
|
|
}{
|
|
Color: "red",
|
|
},
|
|
want: `{
|
|
"type":"object",
|
|
"properties":{
|
|
"color":{
|
|
"type":"string",
|
|
"enum":["red","green","blue"]
|
|
}
|
|
},
|
|
"required":["color"],
|
|
"additionalProperties":false
|
|
}`,
|
|
},
|
|
{
|
|
name: "Test with nullable tag",
|
|
in: struct {
|
|
Name *string `json:"name" nullable:"true"`
|
|
}{
|
|
Name: nil,
|
|
},
|
|
want: `{
|
|
|
|
"type":"object",
|
|
"properties":{
|
|
"name":{
|
|
"type":"string",
|
|
"nullable":true
|
|
}
|
|
},
|
|
"required":["name"],
|
|
"additionalProperties":false
|
|
}`,
|
|
},
|
|
{
|
|
name: "Test with exclude mark",
|
|
in: struct {
|
|
Name string `json:"-"`
|
|
}{
|
|
Name: "Name",
|
|
},
|
|
want: `{
|
|
"type":"object",
|
|
"additionalProperties":false
|
|
}`,
|
|
},
|
|
{
|
|
name: "Test with no json tag",
|
|
in: struct {
|
|
Name string
|
|
}{
|
|
Name: "",
|
|
},
|
|
want: `{
|
|
"type":"object",
|
|
"properties":{
|
|
"Name":{
|
|
"type":"string"
|
|
}
|
|
},
|
|
"required":["Name"],
|
|
"additionalProperties":false
|
|
}`,
|
|
},
|
|
{
|
|
name: "Test with omitempty tag",
|
|
in: struct {
|
|
Name string `json:"name,omitempty"`
|
|
}{
|
|
Name: "",
|
|
},
|
|
want: `{
|
|
"type":"object",
|
|
"properties":{
|
|
"name":{
|
|
"type":"string"
|
|
}
|
|
},
|
|
"additionalProperties":false
|
|
}`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
wantBytes := []byte(tt.want)
|
|
|
|
schema, err := jsonschema.GenerateSchemaForType(tt.in)
|
|
if err != nil {
|
|
t.Errorf("Failed to generate schema: error = %v", err)
|
|
return
|
|
}
|
|
|
|
var want map[string]interface{}
|
|
err = json.Unmarshal(wantBytes, &want)
|
|
if err != nil {
|
|
t.Errorf("Failed to Unmarshal JSON: error = %v", err)
|
|
return
|
|
}
|
|
|
|
got := structToMap(t, schema)
|
|
gotPtr := structToMap(t, &schema)
|
|
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("MarshalJSON() got = %v, want %v", got, want)
|
|
}
|
|
if !reflect.DeepEqual(gotPtr, want) {
|
|
t.Errorf("MarshalJSON() gotPtr = %v, want %v", gotPtr, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func structToMap(t *testing.T, v any) map[string]any {
|
|
t.Helper()
|
|
gotBytes, err := json.Marshal(v)
|
|
if err != nil {
|
|
t.Errorf("Failed to Marshal JSON: error = %v", err)
|
|
return nil
|
|
}
|
|
|
|
var got map[string]interface{}
|
|
err = json.Unmarshal(gotBytes, &got)
|
|
if err != nil {
|
|
t.Errorf("Failed to Unmarshal JSON: error = %v", err)
|
|
return nil
|
|
}
|
|
return got
|
|
}
|