1use std::collections::HashMap;
4use std::sync::Arc;
5
6use superfluid_abi::Status;
7use llguidance::api::TopLevelGrammar;
8use llguidance::toktrie::{ApproximateTokEnv, SimpleVob, TokEnv, TokRxInfo, TokTrie};
9use llguidance::{Matcher, ParserFactory, TokenParser};
10use serde_json::Value;
11
12use crate::primitives::Vocabulary;
13
14struct Compiler {
15 factory: ParserFactory,
16 specials: Vec<(String, u32)>,
17 eos: Arc<Vec<u32>>,
18}
19
20impl Compiler {
21 fn new(vocab: Vocabulary) -> Result<Compiler, Status> {
22 let Some(&primary) = vocab.eos.first() else {
23 return Err(Status::Unsupported);
24 };
25 let info = TokRxInfo {
26 vocab_size: vocab.tokens.len() as u32,
27 tok_eos: primary,
28 tok_bos: None,
29 tok_pad: None,
30 tok_unk: None,
31 tok_end_of_turn: None,
32 };
33 let trie = TokTrie::from(&info, &vocab.tokens);
34 let env: TokEnv = Arc::new(ApproximateTokEnv::new(trie));
35 let mut factory = ParserFactory::new_simple(&env).map_err(|_| Status::Fatal)?;
36 factory.quiet();
37 let mut specials = vocab.specials;
38 specials.retain(|(s, _)| !s.is_empty());
39 specials.sort_by_key(|(s, _)| std::cmp::Reverse(s.len()));
40 Ok(Compiler { factory, specials, eos: Arc::new(vocab.eos) })
41 }
42
43 fn compile(&self, grammar: TopLevelGrammar) -> Result<TokenParser, Status> {
44 self.factory.create_parser(grammar).map_err(|_| Status::RejectBadStruct)
45 }
46}
47
48struct Entry {
49 proto: TokenParser,
50 open_ended: bool,
51 in_use: bool,
52 pending_free: bool,
53}
54
55#[derive(Default)]
56pub(crate) struct Grammars {
57 compiler: Option<Compiler>,
58 entries: HashMap<u32, Entry>,
59 next: u32,
60}
61
62impl Grammars {
63 fn compiler(&mut self, vocab: impl FnOnce() -> Option<Vocabulary>) -> Result<&Compiler, Status> {
64 if self.compiler.is_none() {
65 let v = vocab().ok_or(Status::Unsupported)?;
66 self.compiler = Some(Compiler::new(v)?);
67 }
68 Ok(self.compiler.as_ref().expect("built above"))
69 }
70
71 fn register(&mut self, proto: TokenParser, open_ended: bool) -> u32 {
72 self.next += 1;
73 let h = self.next;
74 self.entries.insert(h, Entry { proto, open_ended, in_use: false, pending_free: false });
75 h
76 }
77
78 pub(crate) fn create_schema(
79 &mut self,
80 vocab: impl FnOnce() -> Option<Vocabulary>,
81 json_schema: &str,
82 ) -> Result<u32, Status> {
83 let schema: Value = serde_json::from_str(json_schema).map_err(|_| Status::RejectBadStruct)?;
84 let proto = self.compiler(vocab)?.compile(TopLevelGrammar::from_json_schema(prepared(schema)))?;
85 Ok(self.register(proto, false))
86 }
87
88 pub(crate) fn create_structural(
89 &mut self,
90 vocab: impl FnOnce() -> Option<Vocabulary>,
91 tag_json: &str,
92 ) -> Result<u32, Status> {
93 let tag: Value = serde_json::from_str(tag_json).map_err(|_| Status::RejectBadStruct)?;
94 let c = self.compiler(vocab)?;
95 let lark = structural_to_lark(&tag, &c.specials, &c.eos)?;
96 let proto = c.compile(TopLevelGrammar::from_lark(lark))?;
97 Ok(self.register(proto, true))
98 }
99
100 pub(crate) fn free(&mut self, handle: u32) -> Result<(), Status> {
101 let e = self.entries.get_mut(&handle).ok_or(Status::UnknownHandle)?;
102 if e.pending_free {
103 return Err(Status::UnknownHandle);
104 }
105 if e.in_use {
106 e.pending_free = true;
107 } else {
108 self.entries.remove(&handle);
109 }
110 Ok(())
111 }
112
113 pub(crate) fn available(&self, handle: u32) -> bool {
114 self.entries.get(&handle).is_some_and(|e| !e.in_use && !e.pending_free)
115 }
116
117 pub(crate) fn cursor(&self, handle: u32, replay: &[u32]) -> Option<Cursor> {
118 let e = self.entries.get(&handle)?;
119 let mut matcher = Matcher::new(Ok(e.proto.deep_clone()));
120 for &t in replay {
121 matcher.consume_token(t).ok()?;
122 }
123 Some(Cursor(matcher))
124 }
125
126 pub(crate) fn borrow(&mut self, handle: u32, cursor: Cursor) -> Option<LaneGrammar> {
127 let eos = Arc::clone(&self.compiler.as_ref()?.eos);
128 let e = self.entries.get_mut(&handle)?;
129 e.in_use = true;
130 Some(LaneGrammar { handle, matcher: cursor.0, eos, open_ended: e.open_ended, next: None })
131 }
132
133 pub(crate) fn release(&mut self, handle: u32) {
134 if let Some(e) = self.entries.get_mut(&handle) {
135 e.in_use = false;
136 if e.pending_free {
137 self.entries.remove(&handle);
138 }
139 }
140 }
141}
142
143pub(crate) struct Cursor(Matcher);
144
145pub(crate) struct LaneGrammar {
146 pub(crate) handle: u32,
147 matcher: Matcher,
148 eos: Arc<Vec<u32>>,
149 open_ended: bool,
150 next: Option<SimpleVob>,
151}
152
153impl LaneGrammar {
154 fn allowed(&mut self) -> Result<SimpleVob, Status> {
155 if let Some(m) = self.next.take() {
156 return Ok(m);
157 }
158 let stopped = self.matcher.is_stopped();
159 let mut m = self.matcher.compute_mask_or_eos().map_err(|_| Status::Fatal)?;
160 if stopped || self.matcher.is_accepting().unwrap_or(false) {
161 for &id in self.eos.iter() {
162 if (id as usize) < m.len() {
163 m.allow_token(id);
164 }
165 }
166 }
167 Ok(m)
168 }
169
170 pub(crate) fn mask(&mut self, row: &mut [f32]) -> Result<(), Status> {
171 let allowed = self.allowed()?;
172 if allowed.is_zero() {
173 return Err(Status::Fatal);
174 }
175 let n = allowed.len().min(row.len());
176 allowed.iter_unset_entries(|i| {
177 if i < n {
178 row[i] = f32::NEG_INFINITY;
179 }
180 });
181 for v in row.iter_mut().skip(n) {
182 *v = f32::NEG_INFINITY;
183 }
184 Ok(())
185 }
186
187 pub(crate) fn accept(&mut self, token: u32) -> Result<bool, Status> {
188 if self.eos.contains(&token) {
189 return Ok(true);
190 }
191 self.matcher.consume_token(token).map_err(|_| Status::Fatal)?;
192 if self.matcher.is_stopped() && !self.open_ended {
193 return Ok(true);
194 }
195 let next = self.allowed()?;
196 let ending = self.eos.iter().filter(|&&id| (id as usize) < next.len() && next.is_allowed(id)).count();
197 let only_ending = next.num_set() == ending;
198 self.next = Some(next);
199 Ok(only_ending && !self.open_ended)
200 }
201}
202
203pub fn structural_to_lark(tag: &Value, specials: &[(String, u32)], eos: &[u32]) -> Result<String, Status> {
204 let mut t = Translator { specials, eos, ends: Vec::new(), rules: Vec::new() };
205 let format = match tag.get("type").and_then(Value::as_str) {
206 Some("structural_tag") => tag.get("format").ok_or(Status::RejectBadStruct)?,
207 _ => tag,
208 };
209 let start = t.format(format)?;
210 let mut out = format!("start: {start}\n");
211 for (name, body) in &t.rules {
212 out.push_str(&format!("{name}: {body}\n"));
213 }
214 Ok(out)
215}
216
217const WS: &str = r"(/[ \t\r\n]{1,4}/)";
218
219const JSON_WHITESPACE: &str = r"[\x20\x0A\x0D\x09]{1,64}";
220
221const ANNOTATIONS: [&str; 9] =
222 ["title", "default", "description", "examples", "deprecated", "readOnly", "writeOnly", "$comment", "$schema"];
223
224fn native_strict(schema: &mut Value) {
225 let Value::Object(o) = schema else { return };
226 for defs in ["$defs", "definitions"] {
227 if let Some(Value::Object(d)) = o.get_mut(defs) {
228 d.values_mut().for_each(native_strict);
229 }
230 }
231 if ["$ref", "const", "enum"].iter().any(|k| o.contains_key(*k)) {
232 return;
233 }
234 for key in ["anyOf", "oneOf"] {
235 if let Some(Value::Array(branches)) = o.get_mut(key) {
236 branches.iter_mut().for_each(native_strict);
237 return;
238 }
239 }
240 if let Some(Value::Array(branches)) = o.get_mut("allOf") {
241 if let [only] = branches.as_mut_slice() {
242 native_strict(only);
243 }
244 return;
245 }
246 if o.keys().all(|k| k == "type" || ANNOTATIONS.contains(&k.as_str())) {
247 return;
248 }
249 let (object, array) = {
250 let types: Vec<&str> = match o.get("type") {
251 Some(Value::String(t)) => vec![t.as_str()],
252 Some(Value::Array(ts)) => ts.iter().filter_map(Value::as_str).collect(),
253 _ => Vec::new(),
254 };
255 let untyped = !o.contains_key("type");
256 let has = |keys: &[&str]| keys.iter().any(|k| o.contains_key(*k));
257 let object = types.contains(&"object")
258 || (untyped && has(&["properties", "additionalProperties", "unevaluatedProperties"]));
259 let array = types.contains(&"array") || (untyped && !object && has(&["items", "prefixItems", "unevaluatedItems"]));
260 (object, array)
261 };
262 if object {
263 for key in ["properties", "patternProperties"] {
264 if let Some(Value::Object(props)) = o.get_mut(key) {
265 props.values_mut().for_each(native_strict);
266 }
267 }
268 for key in ["additionalProperties", "unevaluatedProperties"] {
269 if let Some(v @ Value::Object(_)) = o.get_mut(key) {
270 native_strict(v);
271 }
272 }
273 if !o.contains_key("additionalProperties") && !o.contains_key("unevaluatedProperties") {
274 o.insert("additionalProperties".to_string(), Value::Bool(false));
275 }
276 }
277 if array {
278 if let Some(Value::Array(prefix)) = o.get_mut("prefixItems") {
279 prefix.iter_mut().for_each(native_strict);
280 }
281 for key in ["items", "unevaluatedItems"] {
282 if let Some(v @ Value::Object(_)) = o.get_mut(key) {
283 native_strict(v);
284 }
285 }
286 if !o.contains_key("items") && !o.contains_key("unevaluatedItems") {
287 o.insert("items".to_string(), Value::Bool(false));
288 }
289 }
290}
291
292fn prepared(mut schema: Value) -> Value {
293 native_strict(&mut schema);
294 bounded_whitespace(schema)
295}
296
297fn bounded_whitespace(mut schema: Value) -> Value {
298 if let Value::Object(o) = &mut schema {
299 let guidance = o.entry("x-guidance").or_insert_with(|| Value::Object(Default::default()));
300 if let Value::Object(g) = guidance {
301 if !g.contains_key("whitespace_pattern") && !g.contains_key("whitespace_flexible") {
302 g.insert("whitespace_pattern".to_string(), Value::String(JSON_WHITESPACE.to_string()));
303 }
304 }
305 }
306 schema
307}
308
309struct Translator<'a> {
310 specials: &'a [(String, u32)],
311 eos: &'a [u32],
312 ends: Vec<u32>,
313 rules: Vec<(String, String)>,
314}
315
316impl Translator<'_> {
317 fn rule(&mut self, stem: &str, body: String) -> String {
318 let name = format!("{stem}_{}", self.rules.len());
319 self.rules.push((name.clone(), body));
320 name
321 }
322
323 fn literal(&self, s: &str) -> String {
324 let mut parts = Vec::new();
325 let mut text = String::new();
326 let mut rest = s;
327 while !rest.is_empty() {
328 if let Some((lit, id)) = self.specials.iter().find(|(lit, _)| rest.starts_with(lit.as_str())) {
329 if !text.is_empty() {
330 parts.push(serde_json::to_string(&text).expect("a string serializes"));
331 text.clear();
332 }
333 parts.push(format!("<[{id}]>"));
334 rest = &rest[lit.len()..];
335 } else {
336 let ch = rest.chars().next().expect("non-empty");
337 text.push(ch);
338 rest = &rest[ch.len_utf8()..];
339 }
340 }
341 if !text.is_empty() {
342 parts.push(serde_json::to_string(&text).expect("a string serializes"));
343 }
344 if parts.is_empty() {
345 "\"\"".to_string()
346 } else {
347 parts.join(" ")
348 }
349 }
350
351 fn token_id(&self, v: &Value) -> Result<u32, Status> {
352 match v {
353 Value::Number(n) => n.as_u64().and_then(|n| u32::try_from(n).ok()).ok_or(Status::RejectBadStruct),
354 Value::String(s) => {
355 self.specials.iter().find(|(lit, _)| lit == s).map(|(_, id)| *id).ok_or(Status::Unsupported)
356 }
357 _ => Err(Status::RejectBadStruct),
358 }
359 }
360
361 fn token_of(&self, v: &Value) -> Result<Option<u32>, Status> {
362 match v {
363 Value::Object(o) if o.get("type").and_then(Value::as_str) == Some("token") => {
364 self.token_id(o.get("token").ok_or(Status::RejectBadStruct)?).map(Some)
365 }
366 _ => Ok(None),
367 }
368 }
369
370 fn boundary(&self, v: &Value) -> Result<String, Status> {
371 if let Value::String(s) = v {
372 return Ok(self.literal(s));
373 }
374 let id = self.token_of(v)?.ok_or(Status::RejectBadStruct)?;
375 Ok(format!("<[{id}]>"))
376 }
377
378 fn elements(&mut self, v: &Value) -> Result<Vec<String>, Status> {
379 let els = v.get("elements").and_then(Value::as_array).ok_or(Status::RejectBadStruct)?;
380 els.iter().map(|e| self.format(e)).collect()
381 }
382
383 fn content(&mut self, v: &Value) -> Result<String, Status> {
384 self.format(v.get("content").ok_or(Status::RejectBadStruct)?)
385 }
386
387 fn tag(&mut self, v: &Value) -> Result<String, Status> {
388 let begin = self.boundary(v.get("begin").ok_or(Status::RejectBadStruct)?)?;
389 let end_v = v.get("end").ok_or(Status::RejectBadStruct)?;
390 let end = self.boundary(end_v)?;
391 let end_token = self.token_of(end_v)?;
392 self.ends.extend(end_token);
393 let content = self.content(v);
394 if end_token.is_some() {
395 self.ends.pop();
396 }
397 Ok(self.rule("tag", format!("{begin} {} {end}", content?)))
398 }
399
400 fn format(&mut self, v: &Value) -> Result<String, Status> {
401 let kind = v.get("type").and_then(Value::as_str).ok_or(Status::RejectBadStruct)?;
402 Ok(match kind {
403 "const_string" => self.literal(v.get("value").and_then(Value::as_str).ok_or(Status::RejectBadStruct)?),
404 "json_schema" => {
405 if v.get("style").and_then(Value::as_str).is_some_and(|s| s != "json") {
406 return Err(Status::Unsupported);
407 }
408 let schema = v.get("json_schema").ok_or(Status::RejectBadStruct)?;
409 let json = self.rule("json", format!("%json {}", prepared(schema.clone())));
410 format!("{WS}? {json} {WS}?")
411 }
412 "regex" => {
413 let p = v.get("pattern").and_then(Value::as_str).ok_or(Status::RejectBadStruct)?;
414 format!("/{}/", p.replace('/', "\\/"))
415 }
416 "sequence" => format!("({})", self.elements(v)?.join(" ")),
417 "or" => format!("({})", self.elements(v)?.join(" | ")),
418 "optional" => format!("({})?", self.content(v)?),
419 "plus" => format!("({})+", self.content(v)?),
420 "star" => format!("({})*", self.content(v)?),
421 "tag" => self.tag(v)?,
422 "triggered_tags" => self.triggered(v)?,
423 "token" => format!("<[{}]>", self.token_id(v.get("token").ok_or(Status::RejectBadStruct)?)?),
424 "any_tokens" => {
425 let mut excluded: Vec<u32> = self.eos.to_vec();
426 excluded.extend(self.ends.last().copied());
427 if let Some(list) = v.get("exclude_tokens") {
428 for t in list.as_array().ok_or(Status::RejectBadStruct)? {
429 excluded.push(self.token_id(t)?);
430 }
431 }
432 excluded.sort_unstable();
433 excluded.dedup();
434 let ids: Vec<String> = excluded.iter().map(u32::to_string).collect();
435 format!("<[^{}]>*", ids.join(","))
436 }
437 _ => return Err(Status::Unsupported),
438 })
439 }
440
441 fn triggered(&mut self, v: &Value) -> Result<String, Status> {
442 let triggers = v.get("triggers").and_then(Value::as_array).ok_or(Status::RejectBadStruct)?;
443 let mut excluded: Vec<u32> = self.eos.to_vec();
444 for trig in triggers {
445 let s = trig.as_str().ok_or(Status::RejectBadStruct)?;
446 let id = self
447 .specials
448 .iter()
449 .find(|(lit, _)| s.starts_with(lit.as_str()))
450 .map(|(_, id)| *id)
451 .ok_or(Status::Unsupported)?;
452 if !excluded.contains(&id) {
453 excluded.push(id);
454 }
455 }
456 let tags = v.get("tags").and_then(Value::as_array).ok_or(Status::RejectBadStruct)?;
457 if tags.is_empty() {
458 return Err(Status::RejectBadStruct);
459 }
460 let calls: Vec<String> = tags.iter().map(|t| self.tag(t)).collect::<Result<_, _>>()?;
461 let call = self.rule("call", calls.join(" | "));
462 let ids: Vec<String> = excluded.iter().map(u32::to_string).collect();
463 let text = self.rule("text", format!("<[^{}]>*", ids.join(",")));
464 let at_least_one = v.get("at_least_one").and_then(Value::as_bool).unwrap_or(false);
465 let stop_after_first = v.get("stop_after_first").and_then(Value::as_bool).unwrap_or(false);
466 Ok(match (at_least_one, stop_after_first) {
467 (false, false) => format!("{text} ({call} {text})*"),
468 (true, false) => format!("{call} ({text} {call})* {text}"),
469 (false, true) => format!("{text} {call}?"),
470 (true, true) => call,
471 })
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478
479 fn specials() -> Vec<(String, u32)> {
480 vec![("</tool_call>".to_string(), 101), ("<tool_call>".to_string(), 100)]
481 }
482
483 fn envelope_tag(at_least_one: bool) -> Value {
484 serde_json::json!({"type": "structural_tag", "format": {
485 "type": "triggered_tags",
486 "triggers": ["<tool_call>"],
487 "tags": [{"type": "tag", "begin": "<tool_call>", "content": {"type": "json_schema",
488 "json_schema": {"type": "object", "properties": {"name": {"const": "f"}}}}, "end": "</tool_call>"}],
489 "at_least_one": at_least_one,
490 "stop_after_first": false,
491 }})
492 }
493
494 #[test]
495 fn a_tool_call_tag_becomes_free_text_around_framed_calls() {
496 let lark = structural_to_lark(&envelope_tag(false), &specials(), &[7]).unwrap();
497 assert!(lark.starts_with("start: text_3 (call_2 text_3)*\n"), "{lark}");
498 assert!(
499 lark.contains(&format!("tag_1: <[100]> {WS}? json_0 {WS}? <[101]>")),
500 "the frame is the marker tokens: {lark}"
501 );
502 assert!(lark.contains("text_3: <[^7,100]>*"), "free text excludes the stop set and the trigger: {lark}");
503 }
504
505 #[test]
506 fn a_forced_call_starts_with_the_call() {
507 let lark = structural_to_lark(&envelope_tag(true), &specials(), &[7]).unwrap();
508 assert!(lark.starts_with("start: call_2 (text_3 call_2)* text_3\n"), "{lark}");
509 }
510
511 #[test]
512 fn text_between_markers_stays_text() {
513 let sp = specials();
514 let t = Translator { specials: &sp, eos: &[], ends: Vec::new(), rules: Vec::new() };
515 assert_eq!(t.literal("<tool_call>\n{\"name\": \"f\""), r#"<[100]> "\n{\"name\": \"f\"""#);
516 assert_eq!(t.literal("plain"), "\"plain\"");
517 assert_eq!(t.literal(""), "\"\"");
518 }
519
520 #[test]
521 fn a_trigger_that_is_not_a_marker_is_refused() {
522 let mut tag = envelope_tag(false);
523 tag["format"]["triggers"] = serde_json::json!(["<function="]);
524 assert_eq!(structural_to_lark(&tag, &specials(), &[7]).unwrap_err(), Status::Unsupported);
525 }
526
527 #[test]
528 fn an_xml_style_body_is_refused() {
529 let mut tag = envelope_tag(false);
530 tag["format"]["tags"][0]["content"]["style"] = serde_json::json!("qwen_xml");
531 assert_eq!(structural_to_lark(&tag, &specials(), &[7]).unwrap_err(), Status::Unsupported);
532 }
533
534 #[test]
535 fn markers_named_by_id_and_a_body_up_to_its_end_token() {
536 let tag = serde_json::json!({"type": "structural_tag", "format": {"type": "sequence", "elements": [
537 {"type": "optional", "content": {"type": "sequence", "elements": [
538 {"type": "token", "token": 200}, {"type": "const_string", "value": "analysis"},
539 {"type": "tag", "begin": {"type": "token", "token": 201},
540 "content": {"type": "any_tokens", "exclude_tokens": [200, 203]},
541 "end": {"type": "token", "token": 202}},
542 {"type": "token", "token": 203}, {"type": "const_string", "value": "assistant"}]}},
543 {"type": "sequence", "elements": [
544 {"type": "token", "token": 200}, {"type": "const_string", "value": "commentary to=functions.f"},
545 {"type": "token", "token": 201}, {"type": "json_schema", "json_schema": {"type": "object"}}]}]}});
546 let lark = structural_to_lark(&tag, &specials(), &[7]).unwrap();
547 assert!(lark.contains(r#"((<[200]> "analysis" tag_0 <[203]> "assistant"))?"#), "the analysis may be left out: {lark}");
548 assert!(
549 lark.contains("tag_0: <[201]> <[^7,200,202,203]>* <[202]>"),
550 "the body stops at its end token and at the stop set: {lark}"
551 );
552 assert!(lark.contains(r#"(<[200]> "commentary to=functions.f" <[201]> "#), "{lark}");
553
554 let named = serde_json::json!({"type": "token", "token": "<tool_call>"});
555 assert_eq!(structural_to_lark(&named, &specials(), &[7]).unwrap(), "start: <[100]>\n");
556 let unknown = serde_json::json!({"type": "token", "token": "<nope>"});
557 assert_eq!(structural_to_lark(&unknown, &specials(), &[7]).unwrap_err(), Status::Unsupported);
558 }
559
560 #[test]
561 fn composite_bodies_translate() {
562 let tag = serde_json::json!({"type": "triggered_tags", "triggers": ["<tool_call>"], "tags": [{
563 "begin": "<tool_call>call:f{", "end": "}</tool_call>",
564 "content": {"type": "sequence", "elements": [
565 {"type": "const_string", "value": "city:"},
566 {"type": "or", "elements": [{"type": "regex", "pattern": "[^,}]*"}, {"type": "const_string", "value": "x/y"}]},
567 ]}}], "at_least_one": true});
568 let lark = structural_to_lark(&tag, &specials(), &[7]).unwrap();
569 assert!(lark.contains(r#"<[100]> "call:f{" ("city:" (/[^,}]*/ | "x/y")) "}" <[101]>"#), "{lark}");
570 }
571
572 fn strict(v: Value) -> Value {
573 let mut v = v;
574 native_strict(&mut v);
575 v
576 }
577
578 #[test]
579 fn schemas_close_where_xgrammar_closes_them() {
580 use serde_json::json;
581 let cases = [
582 (json!({"type": "object", "properties": {"a": {}}}),
583 json!({"type": "object", "properties": {"a": {}}, "additionalProperties": false})),
584 (json!({"properties": {"a": {}}}), json!({"properties": {"a": {}}, "additionalProperties": false})),
585 (json!({"type": "object", "unevaluatedProperties": true}), json!({"type": "object", "unevaluatedProperties": true})),
586 (json!({"type": "object", "title": "t", "description": "d"}), json!({"type": "object", "title": "t", "description": "d"})),
587 (json!({"type": "object", "$defs": {}}), json!({"type": "object", "$defs": {}, "additionalProperties": false})),
588 (json!({"type": "array", "minItems": 0}), json!({"type": "array", "minItems": 0, "items": false})),
589 (json!({"prefixItems": [{"type": "object", "required": []}]}),
590 json!({"prefixItems": [{"type": "object", "required": [], "additionalProperties": false}], "items": false})),
591 (json!({"type": "array", "items": {"type": "object", "properties": {}}}),
592 json!({"type": "array", "items": {"type": "object", "properties": {}, "additionalProperties": false}})),
593 (json!({"type": ["object", "null"]}), json!({"type": ["object", "null"]})),
594 (json!({"type": ["object", "null"], "required": ["a"]}),
595 json!({"type": ["object", "null"], "required": ["a"], "additionalProperties": false})),
596 (json!({"$ref": "#/$defs/p", "properties": {}}), json!({"$ref": "#/$defs/p", "properties": {}})),
597 (json!({"const": {"a": 1}}), json!({"const": {"a": 1}})),
598 (json!({"enum": [{"a": 1}]}), json!({"enum": [{"a": 1}]})),
599 (json!({"anyOf": [{"type": "object", "required": []}, {"type": "null"}], "properties": {}}),
600 json!({"anyOf": [{"type": "object", "required": [], "additionalProperties": false}, {"type": "null"}], "properties": {}})),
601 (json!({"allOf": [{"type": "object", "required": []}]}),
602 json!({"allOf": [{"type": "object", "required": [], "additionalProperties": false}]})),
603 (json!({"allOf": [{"type": "object", "required": []}, {"required": []}]}),
604 json!({"allOf": [{"type": "object", "required": []}, {"required": []}]})),
605 (json!({"$defs": {"p": {"type": "object", "required": []}}, "$ref": "#/$defs/p"}),
606 json!({"$defs": {"p": {"type": "object", "required": [], "additionalProperties": false}}, "$ref": "#/$defs/p"})),
607 (json!({"type": "object", "additionalProperties": {"type": "object", "required": []}}),
608 json!({"type": "object", "additionalProperties": {"type": "object", "required": [], "additionalProperties": false}})),
609 (json!(true), json!(true)),
610 ];
611 for (input, want) in cases {
612 assert_eq!(strict(input.clone()), want, "{input}");
613 }
614 }
615}