llguidance 1.7.4

Super-fast Structured Outputs
Documentation
use lazy_static::lazy_static;
use rstest::*;
use serde_json::{json, Value};

use llg_test_utils::{json_err_test, json_schema_check};

lazy_static! {
    static ref SINGLE_PROPERTY_SCHEMA: Value =
        json!({"type":"object", "properties": {"a": {"type":"integer"}}, "required": ["a"]});
}

#[rstest]
#[case(&json!({"a":123}))]
#[case(&json!({"a":0}))]
fn single_property(#[case] obj: &Value) {
    json_schema_check(&SINGLE_PROPERTY_SCHEMA, obj, true);
}

#[rstest]
#[case(&json!({"a":"Hello"}))]
#[case(&json!({"b":0}))]
fn single_property_failures(#[case] obj: &Value) {
    json_schema_check(&SINGLE_PROPERTY_SCHEMA, obj, false);
}

lazy_static! {
    static ref MULTIPLE_PROPERTY_SCHEMA: Value = json!({"type":"object", "properties": {
            "a": {"type":"integer"},
            "b": {"type":"string"}
        }, "required": ["a", "b"]});
}

#[rstest]
#[case(&json!({"a":123, "b": "Hello"}))]
#[case(&json!({"a":0, "b": "World"}))]
fn multiple_properties(#[case] obj: &Value) {
    json_schema_check(&MULTIPLE_PROPERTY_SCHEMA, obj, true);
}

#[rstest]
#[case(&json!({"a":123}))]
#[case(&json!({"b": "Hello"}))]
#[case(&json!({"c": 1}))]
fn multiple_properties_failures(#[case] obj: &Value) {
    json_schema_check(&MULTIPLE_PROPERTY_SCHEMA, obj, false);
}

lazy_static! {
    static ref NESTED_SCHEMA: Value = json!({
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "info": {
                    "type": "object",
                "properties": {
                    "a": {"type": "integer"},
                    "b": {"type": "integer"}
                },
                "required": ["a", "b"]
            }
        },
        "required": ["name", "info"]
    });
}

#[rstest]
#[case(&json!({"name": "Test", "info": {"a": 123, "b": 456}}))]
fn nested(#[case] obj: &Value) {
    json_schema_check(&NESTED_SCHEMA, obj, true);
}

#[rstest]
#[case(&json!({"name": "Test", "info": {"a": 123}}))]
#[case(&json!({"name": "Test", "info": {"a": "123", "b":20}}))]
#[case(&json!({"name": "Test", "info": {"a": 123, "b": "456"}}))]
#[case(&json!({"name": "Test", "info": {"b": 456}}))]
#[case(&json!({"name": "Test", "info": {"c": 1}}))]
fn nested_failures(#[case] obj: &Value) {
    json_schema_check(&NESTED_SCHEMA, obj, false);
}

lazy_static! {
    static ref OBJECT_WITH_ARRAY: Value = json!({"type":"object", "properties": {
            "name" : {"type": "string"},
            "values": {
                "type": "array",
                "items": {"type": "integer"}
            }
        },
        "required": ["name", "values"]
    });
}

#[rstest]
#[case(&json!({"name": "Test", "values": [1, 2, 3]}))]
fn object_with_array(#[case] obj: &Value) {
    json_schema_check(&OBJECT_WITH_ARRAY, obj, true);
}

#[rstest]
#[case(&json!({"name": "Test", "values": [1, 2, "Hello"]}))]
#[case(&json!({"name": "Test", "values": [1.0, 2.0]}))]
#[case(&json!({"name": "Test"}))]
#[case(&json!({"values": [1, 2, 3]}))]
fn object_with_array_failures(#[case] obj: &Value) {
    json_schema_check(&OBJECT_WITH_ARRAY, obj, false);
}

lazy_static! {
    static ref FALSE_PROPERTY: Value = json!({
        "type": "object",
        "properties": {"a": {"type": "integer"}, "b": false},
        "additionalProperties": false,
    });
}

#[rstest]
#[case(&json!({"a": 42}))]
fn object_false_property(#[case] obj: &Value) {
    json_schema_check(&FALSE_PROPERTY, obj, true);
}

#[rstest]
#[case(&json!({"a": 42, "b": 43}))]
fn object_false_property_failures(#[case] obj: &Value) {
    json_schema_check(&FALSE_PROPERTY, obj, false);
}

#[rstest]
#[case(&json!({
            "type": "object",
            "properties": {"a": {"type": "integer"}, "b": false},
            "required": ["b"],
            "additionalProperties": false,
        }))]
#[case(&json!({
            "type": "object",
            "properties": {"a": {"type": "integer"}},
            "required": ["a", "b"],
            "additionalProperties": false,
        }))]
fn object_unsatisfiable_schema(#[case] schema: &Value) {
    json_err_test(schema, "Unsatisfiable schema");
}

lazy_static! {
    static ref LINKED_LIST: Value = json!({
        "$defs": {
            "A": {
                "properties": {
                    "my_str": {
                        "default": "me",
                        "title": "My Str",
                        "type": "string"
                    },
                    "next": {
                        "anyOf": [
                            {
                                "$ref": "#/$defs/A"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    }
                },
                "required": ["my_str", "next"],
                "type": "object"
            }
        },
        "type": "object",
        "properties": {
            "my_list": {
                "anyOf": [
                    {
                        "$ref": "#/$defs/A"
                    },
                    {
                        "type": "null"
                    }
                ]
            }
        },
        "required": ["my_list"]
    });
}

#[rstest]
#[case::null(&json!({"my_list": null}))]
#[case::single_node(&json!({"my_list": {"my_str": "first", "next": null}}))]
#[case::two_nodes(&json!({"my_list": {"my_str": "first", "next": {"my_str": "second", "next": null}}}))]
#[case::three_nodes(&json!({"my_list": {"my_str": "first", "next": {"my_str": "second", "next": {"my_str": "third", "next": null}}}}))]
fn linked_list(#[case] obj: &Value) {
    json_schema_check(&LINKED_LIST, obj, true);
}

#[rstest]
#[case::invalid_type(&json!({"my_list": {"my_str": 1}}))]
#[case::invalid_next_type(&json!({"my_list": {"my_str": "first", "next": "second"}}))]
fn linked_list_failures(#[case] obj: &Value) {
    json_schema_check(&LINKED_LIST, obj, false);
}