Skip to content

Commit

Permalink
Do not treat oneOf as anyOf by default (#71)
Browse files Browse the repository at this point in the history
Adds coerce_one_of option (false by default)

---------

Co-authored-by: Michał Moskal <[email protected]>
  • Loading branch information
hudson-ai and mmoskal authored Nov 26, 2024
1 parent 9deee46 commit e79e538
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 5 deletions.
16 changes: 13 additions & 3 deletions parser/src/json/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub struct JsonCompileOptions {
pub item_separator: String,
pub key_separator: String,
pub whitespace_flexible: bool,
pub coerce_one_of: bool,
}

fn json_dumps(target: &serde_json::Value) -> String {
Expand Down Expand Up @@ -98,6 +99,7 @@ impl Default for JsonCompileOptions {
item_separator: ",".to_string(),
key_separator: ":".to_string(),
whitespace_flexible: true,
coerce_one_of: false,
}
}
}
Expand Down Expand Up @@ -244,11 +246,19 @@ impl Compiler {
Ok(self.builder.string(if *value { "true" } else { "false" }))
}
Schema::AnyOf { options } => self.process_any_of(options.clone()),
Schema::OneOf { options } => self.process_any_of(options.clone()),
Schema::OneOf { options } => self.process_one_of(options.clone()),
Schema::Ref { uri, .. } => self.get_definition(uri),
}
}

fn process_one_of(&mut self, options: Vec<Schema>) -> Result<NodeRef> {
if self.options.coerce_one_of {
self.process_any_of(options)
} else {
Err(anyhow!("oneOf constraints are not supported. Enable 'coerce_one_of' option to approximate oneOf with anyOf"))
}
}

fn process_any_of(&mut self, options: Vec<Schema>) -> Result<NodeRef> {
let mut nodes = vec![];
let mut errors = vec![];
Expand All @@ -265,12 +275,12 @@ impl Compiler {
Ok(self.builder.select(&nodes))
} else if let Some(e) = errors.pop() {
Err(anyhow!(UnsatisfiableSchemaError {
message: format!("All options in anyOf/oneOf are unsatisfiable",),
message: format!("All options in anyOf are unsatisfiable",),
})
.context(e))
} else {
Err(anyhow!(UnsatisfiableSchemaError {
message: "No options in anyOf/oneOf".to_string(),
message: "No options in anyOf".to_string(),
}))
}
}
Expand Down
1 change: 1 addition & 0 deletions python/llguidance/_lib.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ class JsonCompiler:
cls,
separators: Optional[Tuple[str, str]] = None,
whitespace_flexible: bool = False,
coerce_one_of: bool = False,
) -> "JsonCompiler":
"""
Create a new JSON compiler.
Expand Down
7 changes: 5 additions & 2 deletions rust/src/py.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,14 @@ struct JsonCompiler {
item_separator: String,
key_separator: String,
whitespace_flexible: bool,
coerce_one_of: bool,
}

#[pymethods]
impl JsonCompiler {
#[new]
#[pyo3(signature = (separators = None, whitespace_flexible = false))]
fn py_new(separators: Option<(String, String)>, whitespace_flexible: bool) -> Self {
#[pyo3(signature = (separators = None, whitespace_flexible = false, coerce_one_of = false))]
fn py_new(separators: Option<(String, String)>, whitespace_flexible: bool, coerce_one_of: bool) -> Self {
let (item_separator, key_separator) = separators.unwrap_or_else(|| {
if whitespace_flexible {
(",".to_owned(), ":".to_owned())
Expand All @@ -267,6 +268,7 @@ impl JsonCompiler {
item_separator: item_separator,
key_separator: key_separator,
whitespace_flexible,
coerce_one_of,
}
}
fn compile(&self, schema: &str) -> PyResult<String> {
Expand All @@ -275,6 +277,7 @@ impl JsonCompiler {
item_separator: self.item_separator.clone(),
key_separator: self.key_separator.clone(),
whitespace_flexible: self.whitespace_flexible,
coerce_one_of: self.coerce_one_of,
};
let grammar = compile_options.json_to_llg(schema).map_err(val_error)?;
serde_json::to_string(&grammar).map_err(val_error)
Expand Down

0 comments on commit e79e538

Please sign in to comment.