Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Go] test picoschema with yaml test cases #528

Merged
merged 2 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
34 changes: 23 additions & 11 deletions go/plugins/dotprompt/dotprompt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ func TestPrompts(t *testing.T) {
"type": "object",
"required": [
"food"
]
],
"additionalProperties": false
}`,
output: `{
"properties": {
Expand Down Expand Up @@ -72,11 +73,13 @@ func TestPrompts(t *testing.T) {
"required": [
"name",
"quantity"
]
],
"additionalProperties": false
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"ingredients",
Expand Down Expand Up @@ -150,20 +153,29 @@ func cmpSchema(t *testing.T, got *jsonschema.Schema, want string) string {
return ""
}

// JSON sorts maps but not slices.
// jsonschema slices are not sorted consistently.
sortSchemaSlices(got)

data, err := json.Marshal(got)
jsonGot, err := convertSchema(got)
if err != nil {
t.Fatal(err)
}
var jsonGot, jsonWant any
if err := json.Unmarshal(data, &jsonGot); err != nil {
t.Fatal(err)
}
var jsonWant any
if err := json.Unmarshal([]byte(want), &jsonWant); err != nil {
t.Fatalf("unmarshaling %q failed: %v", want, err)
}
return cmp.Diff(jsonWant, jsonGot)
}

// convertSchema marshals s to JSON, then unmarshals the result.
func convertSchema(s *jsonschema.Schema) (any, error) {
// JSON sorts maps but not slices.
// jsonschema slices are not sorted consistently.
sortSchemaSlices(s)
data, err := json.Marshal(s)
if err != nil {
return nil, err
}
var a any
if err := json.Unmarshal(data, &a); err != nil {
return nil, err
}
return a, nil
}
106 changes: 62 additions & 44 deletions go/plugins/dotprompt/picoschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
"strings"

"github.com/invopop/jsonschema"
"github.com/wk8/go-ordered-map/v2"
orderedmap "github.com/wk8/go-ordered-map/v2"
)

// picoschemaToJSONSchema turns picoschema input into a JSONSchema.
Expand Down Expand Up @@ -57,71 +57,89 @@ func picoschemaToJSONSchema(val any) (*jsonschema.Schema, error) {

// parsePico parses picoschema from the result of the YAML parser.
func parsePico(val any) (*jsonschema.Schema, error) {
if str, ok := val.(string); ok {
typ, desc, found := strings.Cut(str, ",")
switch val := val.(type) {
default:
return nil, fmt.Errorf("picoschema: value %v of type %[1]T is not an object, slice or string", val)

case string:
typ, desc, found := strings.Cut(val, ",")
switch typ {
case "string", "boolean", "null", "number", "integer":
case "string", "boolean", "null", "number", "integer", "any":
default:
return nil, fmt.Errorf("picoschema: unsupported scalar type %q", typ)
}
if typ == "any" {
typ = ""
}
ret := &jsonschema.Schema{
Type: typ,
}
if found {
ret.Description = strings.TrimSpace(desc)
}
return ret, nil
}

m, ok := val.(map[string]any)
if !ok {
return nil, fmt.Errorf("picoschema: value %v of type %T is not an object or a string", val, val)
}
case []any: // assume enum
return &jsonschema.Schema{Enum: val}, nil

ret := &jsonschema.Schema{
Type: "object",
Properties: orderedmap.New[string, *jsonschema.Schema](),
}
for k, v := range m {
name, typ, found := strings.Cut(k, "(")
propertyName, isOptional := strings.CutSuffix(name, "?")
if !isOptional {
ret.Required = append(ret.Required, propertyName)
case map[string]any:
ret := &jsonschema.Schema{
Type: "object",
Properties: orderedmap.New[string, *jsonschema.Schema](),
AdditionalProperties: jsonschema.FalseSchema,
}
for k, v := range val {
name, typ, found := strings.Cut(k, "(")
propertyName, isOptional := strings.CutSuffix(name, "?")
if name != "" && !isOptional {
ret.Required = append(ret.Required, propertyName)
}

property, err := parsePico(v)
if err != nil {
return nil, err
}
property, err := parsePico(v)
if err != nil {
return nil, err
}

if !found {
ret.Properties.Set(propertyName, property)
continue
}
if !found {
ret.Properties.Set(propertyName, property)
continue
}

typ = strings.TrimSuffix(typ, ")")
typ, desc, found := strings.Cut(strings.TrimSuffix(typ, ")"), ",")
switch typ {
case "array":
property = &jsonschema.Schema{
Type: "array",
Items: property,
}
case "object":
// Use property unchanged.
case "enum":
if property.Enum == nil {
return nil, fmt.Errorf("picoschema: enum value %v is not an array", property)
}
if isOptional {
property.Enum = append(property.Enum, nil)
}

case "*":
ret.AdditionalProperties = property
continue
default:
return nil, fmt.Errorf("picoschema: parenthetical type %q is none of %q", typ,
[]string{"object", "array", "enum", "*"})

typ = strings.TrimSuffix(typ, ")")
typ, desc, found := strings.Cut(strings.TrimSuffix(typ, ")"), ",")
switch typ {
case "array":
property = &jsonschema.Schema{
Type: "array",
Items: property,
}
case "object":
// Use property unchanged.
default:
return nil, fmt.Errorf("picoschema: parenthetical type %q is neither %q nor %q", typ, "object", "array")

}
if found {
property.Description = strings.TrimSpace(desc)
}

if found {
property.Description = strings.TrimSpace(desc)
ret.Properties.Set(propertyName, property)
}

ret.Properties.Set(propertyName, property)
return ret, nil
}

return ret, nil
}

// mapToJSONSchema converts a YAML value to a JSONSchema.
Expand Down
Loading
Loading