Skip to main content

llama_cpp_bindings/
json_schema_to_grammar.rs

1use std::ffi::{CStr, CString, c_char};
2
3use crate::error::JsonSchemaToGrammarError;
4use crate::ffi_error_reader::read_and_free_cpp_error;
5
6/// # Errors
7///
8/// Returns [`JsonSchemaToGrammarError`] if the schema string contains a NUL byte,
9/// the wrapper reports any non-OK status, or the returned grammar is not valid UTF-8.
10pub fn json_schema_to_grammar(schema_json: &str) -> Result<String, JsonSchemaToGrammarError> {
11    let schema_cstr = CString::new(schema_json)?;
12    let mut out: *mut c_char = std::ptr::null_mut();
13    let mut error_ptr: *mut c_char = std::ptr::null_mut();
14
15    let status = unsafe {
16        llama_cpp_bindings_sys::llama_rs_json_schema_to_grammar(
17            schema_cstr.as_ptr(),
18            false,
19            &raw mut out,
20            &raw mut error_ptr,
21        )
22    };
23
24    match status {
25        llama_cpp_bindings_sys::LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_OK => {
26            let grammar_bytes = unsafe { CStr::from_ptr(out) }.to_bytes().to_vec();
27            unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out) };
28            Ok(String::from_utf8(grammar_bytes)?)
29        }
30        llama_cpp_bindings_sys::LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_ERROR_STRING_ALLOCATION_FAILED => {
31            Err(JsonSchemaToGrammarError::NotEnoughMemory)
32        }
33        llama_cpp_bindings_sys::LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_INVALID_SCHEMA => {
34            let message = unsafe { read_and_free_cpp_error(error_ptr) };
35            Err(JsonSchemaToGrammarError::InvalidSchema { message })
36        }
37        llama_cpp_bindings_sys::LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_VENDORED_THREW_CXX_EXCEPTION => {
38            let message = unsafe { read_and_free_cpp_error(error_ptr) };
39            Err(JsonSchemaToGrammarError::Reported { message })
40        }
41        other => {
42            unreachable!("llama_rs_json_schema_to_grammar returned unrecognized status {other}")
43        }
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::json_schema_to_grammar;
50    use crate::error::JsonSchemaToGrammarError;
51
52    #[test]
53    fn simple_object() {
54        let schema = r#"{"type": "object", "properties": {"name": {"type": "string"}}}"#;
55        let grammar = json_schema_to_grammar(schema).expect("schema converts to grammar");
56
57        assert!(!grammar.is_empty());
58    }
59
60    #[test]
61    fn null_byte_returns_schema_contains_nul_byte_error() {
62        use std::ffi::CString;
63
64        let schema = "{\x00}";
65        let err = json_schema_to_grammar(schema).unwrap_err();
66        let representative = JsonSchemaToGrammarError::SchemaContainsNulByte(
67            CString::new(b"a\0b".to_vec()).unwrap_err(),
68        );
69
70        assert_eq!(
71            std::mem::discriminant(&err),
72            std::mem::discriminant(&representative)
73        );
74    }
75
76    #[test]
77    fn simple_string() {
78        let schema = r#"{"type": "string"}"#;
79        let grammar = json_schema_to_grammar(schema).expect("schema converts to grammar");
80
81        assert!(!grammar.is_empty());
82    }
83
84    #[test]
85    fn invalid_json_returns_reported() {
86        let schema = "not valid json at all";
87        let err = json_schema_to_grammar(schema).unwrap_err();
88        let representative = JsonSchemaToGrammarError::Reported {
89            message: String::new(),
90        };
91
92        assert_eq!(
93            std::mem::discriminant(&err),
94            std::mem::discriminant(&representative)
95        );
96    }
97
98    #[test]
99    fn unresolved_ref_returns_invalid_schema() {
100        let schema = r##"{"$ref": "#/$defs/Missing"}"##;
101        let err = json_schema_to_grammar(schema).unwrap_err();
102        let representative = JsonSchemaToGrammarError::InvalidSchema {
103            message: String::new(),
104        };
105
106        assert_eq!(
107            std::mem::discriminant(&err),
108            std::mem::discriminant(&representative)
109        );
110    }
111}