metrale_grammar/schema/
api.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//
3// Public API for the JSON-schema -> EBNF converter — port of the
4// `JSONSchemaToEBNF` / `*XMLToolCallingToEBNF` free functions from
5// `cpp/json_schema_converter.{h,cc}`.
6
7use super::converter::JsonSchemaConverter;
8use super::error::{SchemaError, SchemaResult};
9use super::json_value::JsonValue;
10use super::options::{JsonFormat, SchemaConverterOptions};
11use super::parser::SchemaParser;
12use crate::grammar::{GrammarData, parse_ebnf_default};
13
14/// Convert a JSON Schema document (already parsed into a [`JsonValue`])
15/// into an EBNF grammar string. Port of the `picojson::value`
16/// overload of `JSONSchemaToEBNF`.
17pub fn json_value_to_ebnf(
18    schema: &JsonValue,
19    options: &SchemaConverterOptions,
20) -> SchemaResult<String> {
21    let mut parser = SchemaParser::new(schema.clone(), options.strict_mode);
22    let spec = parser.parse(schema, "root", None)?;
23    let mut converter = JsonSchemaConverter::new(options, &mut parser);
24    converter.convert(&spec)
25}
26
27/// Convert a JSON Schema *string* into an EBNF grammar string. Port
28/// of the `std::string` overload of `JSONSchemaToEBNF`.
29pub fn json_schema_to_ebnf(schema: &str, options: &SchemaConverterOptions) -> SchemaResult<String> {
30    let value = JsonValue::parse(schema)
31        .map_err(|e| SchemaError::invalid(format!("Failed to parse JSON schema: {e}")))?;
32    json_value_to_ebnf(&value, options)
33}
34
35/// Convert a JSON Schema string into a parsed [`GrammarData`] AST by
36/// generating the EBNF and feeding it through the EBNF parser.
37pub fn json_schema_to_grammar(
38    schema: &str,
39    options: &SchemaConverterOptions,
40) -> SchemaResult<GrammarData> {
41    let ebnf = json_schema_to_ebnf(schema, options)?;
42    parse_ebnf_default(&ebnf)
43        .map_err(|e| SchemaError::invalid(format!("generated EBNF failed to parse: {e}")))
44}
45
46/// Validate that `schema` is a tool-calling object schema and convert
47/// it with the given XML `format`. Shared by the three XML helpers.
48fn xml_tool_calling_to_ebnf(schema: &str, format: JsonFormat) -> SchemaResult<String> {
49    let value = JsonValue::parse(schema)
50        .map_err(|e| SchemaError::invalid(format!("Failed to parse JSON schema: {e}")))?;
51    if value.as_bool().is_some() {
52        return Err(SchemaError::invalid(
53            "Expected JSON schema object, got boolean",
54        ));
55    }
56    match value.get("type").and_then(JsonValue::as_str) {
57        Some("object") => {}
58        _ => {
59            return Err(SchemaError::invalid(
60                "Function calling must have a 'type' field of 'object'",
61            ));
62        }
63    }
64    let options = SchemaConverterOptions {
65        json_format: format,
66        ..SchemaConverterOptions::default()
67    };
68    json_value_to_ebnf(&value, &options)
69}
70
71/// Convert a function-call parameter schema into a Qwen-XML-style
72/// EBNF grammar. Port of `QwenXMLToolCallingToEBNF`.
73pub fn qwen_xml_tool_calling_to_ebnf(schema: &str) -> SchemaResult<String> {
74    xml_tool_calling_to_ebnf(schema, JsonFormat::QwenXml)
75}
76
77/// Convert a function-call parameter schema into a MiniMax-XML-style
78/// EBNF grammar. Port of `MiniMaxXMLToolCallingToEBNF`.
79pub fn minimax_xml_tool_calling_to_ebnf(schema: &str) -> SchemaResult<String> {
80    xml_tool_calling_to_ebnf(schema, JsonFormat::MiniMaxXml)
81}
82
83/// Convert a function-call parameter schema into a DeepSeek-XML-style
84/// EBNF grammar. Port of `DeepSeekXMLToolCallingToEBNF`.
85pub fn deepseek_xml_tool_calling_to_ebnf(schema: &str) -> SchemaResult<String> {
86    xml_tool_calling_to_ebnf(schema, JsonFormat::DeepSeekXml)
87}
88
89/// Build the EBNF grammar for the "any JSON value" schema (`{}`).
90///
91/// This is the schema-converter analogue of `compile_builtin_json
92/// grammar`: an empty schema accepts any JSON document, so its
93/// generated grammar is the builtin-JSON grammar surface for this
94/// subsystem.
95pub fn builtin_json_grammar_ebnf() -> String {
96    // `{}` is always a valid schema and never fails to convert.
97    json_schema_to_ebnf("{}", &SchemaConverterOptions::default()).unwrap_or_default()
98}
99
100/// Build the parsed [`GrammarData`] for the builtin-JSON grammar.
101pub fn builtin_json_grammar() -> SchemaResult<GrammarData> {
102    json_schema_to_grammar("{}", &SchemaConverterOptions::default())
103}