1use core::ffi::{c_char, c_void};
4use std::collections::BTreeMap;
5use std::ffi::CString;
6use std::sync::mpsc;
7
8use serde_json::{json, Map, Value};
9
10use crate::content::{FromGeneratedContent, ToGeneratedContent};
11use crate::error::FMError;
12use crate::ffi;
13
14#[derive(Debug, Clone)]
16pub struct GenerationSchema {
17 json_schema: String,
18 bridge_request_json: Option<String>,
19}
20
21impl PartialEq for GenerationSchema {
22 fn eq(&self, other: &Self) -> bool {
23 self.json_schema == other.json_schema
24 }
25}
26
27impl Eq for GenerationSchema {}
28
29impl GenerationSchema {
30 pub fn from_json_schema(json_schema: impl Into<String>) -> Result<Self, FMError> {
36 let json_schema = json_schema.into();
37 let schema_c = CString::new(json_schema.as_str()).map_err(|error| {
38 FMError::InvalidArgument(format!(
39 "schema JSON contains an interior NUL byte: {error}"
40 ))
41 })?;
42 let mut error_ptr: *mut c_char = core::ptr::null_mut();
43 let status =
44 unsafe { ffi::fm_generation_schema_validate_json(schema_c.as_ptr(), &mut error_ptr) };
45 if status != ffi::status::OK {
46 return Err(crate::error::from_swift(status, error_ptr));
47 }
48 Ok(Self {
49 json_schema,
50 bridge_request_json: None,
51 })
52 }
53
54 pub fn from_dynamic(
60 root: DynamicGenerationSchema,
61 dependencies: impl IntoIterator<Item = DynamicGenerationSchema>,
62 ) -> Result<Self, FMError> {
63 let request = json!({
64 "root": root.to_json_value(),
65 "dependencies": dependencies
66 .into_iter()
67 .map(|schema| schema.to_json_value())
68 .collect::<Vec<_>>(),
69 });
70 let request_json = serde_json::to_string(&request).map_err(|error| {
71 FMError::InvalidArgument(format!(
72 "dynamic schema request is not JSON-serializable: {error}"
73 ))
74 })?;
75 let request_c = CString::new(request_json.as_str()).map_err(|error| {
76 FMError::InvalidArgument(format!("dynamic schema JSON contains NUL byte: {error}"))
77 })?;
78 let (tx, rx) = mpsc::channel();
79 let tx_box: Box<mpsc::Sender<Result<String, FMError>>> = Box::new(tx);
80 let context = Box::into_raw(tx_box).cast::<c_void>();
81 unsafe {
82 ffi::fm_generation_schema_compile_json(
83 request_c.as_ptr(),
84 context,
85 schema_callback_trampoline,
86 );
87 }
88 let json_schema = rx.recv().map_err(|_| FMError::Unknown {
89 code: ffi::status::UNKNOWN,
90 message: "Swift bridge dropped the schema callback channel".into(),
91 })??;
92 Ok(Self {
93 json_schema,
94 bridge_request_json: Some(request_json),
95 })
96 }
97
98 pub fn new(
107 description: Option<String>,
108 properties: impl IntoIterator<Item = (impl Into<String>, DynamicGenerationProperty)>,
109 ) -> Result<Self, FMError> {
110 Self::new_with_nil_repr(description, false, properties)
111 }
112
113 pub fn new_with_nil_repr(
121 description: Option<String>,
122 represent_nil_explicitly_in_generated_content: bool,
123 properties: impl IntoIterator<Item = (impl Into<String>, DynamicGenerationProperty)>,
124 ) -> Result<Self, FMError> {
125 let request = json!({
126 "description": description,
127 "representNilExplicitlyInGeneratedContent": represent_nil_explicitly_in_generated_content,
128 "properties": properties
129 .into_iter()
130 .map(|(name, property)| {
131 let DynamicGenerationProperty {
132 schema,
133 description,
134 optional,
135 } = property;
136 json!({
137 "name": name.into(),
138 "description": description,
139 "optional": optional,
140 "schema": schema.to_json_value(),
141 })
142 })
143 .collect::<Vec<_>>(),
144 });
145 let request_json = serde_json::to_string(&request).map_err(|error| {
146 FMError::InvalidArgument(format!(
147 "typed schema request is not JSON-serializable: {error}"
148 ))
149 })?;
150 let request_c = CString::new(request_json.as_str()).map_err(|error| {
151 FMError::InvalidArgument(format!("typed schema JSON contains NUL byte: {error}"))
152 })?;
153 let (tx, rx) = mpsc::channel();
154 let tx_box: Box<mpsc::Sender<Result<String, FMError>>> = Box::new(tx);
155 let context = Box::into_raw(tx_box).cast::<c_void>();
156 unsafe {
157 ffi::fm_generation_schema_create_typed_json(
158 request_c.as_ptr(),
159 context,
160 schema_callback_trampoline,
161 );
162 }
163 let json_schema = rx.recv().map_err(|_| FMError::Unknown {
164 code: ffi::status::UNKNOWN,
165 message: "Swift bridge dropped the typed schema callback channel".into(),
166 })??;
167 Ok(Self {
168 json_schema,
169 bridge_request_json: Some(request_json),
170 })
171 }
172
173 #[must_use]
175 pub fn json_schema(&self) -> &str {
176 &self.json_schema
177 }
178
179 #[must_use]
181 pub fn name(&self) -> Option<String> {
182 let value: Value = serde_json::from_str(&self.json_schema).ok()?;
183 value.get("title")?.as_str().map(ToOwned::to_owned)
184 }
185
186 pub(crate) fn bridge_request_json(&self) -> &str {
187 self.bridge_request_json
188 .as_deref()
189 .unwrap_or_else(|| self.json_schema())
190 }
191
192 pub(crate) fn effective_include_schema_in_prompt(&self, requested: bool) -> bool {
193 requested && !self.uses_explicit_null_representation()
194 }
195
196 fn uses_explicit_null_representation(&self) -> bool {
197 self.bridge_request_json
198 .as_ref()
199 .is_some_and(|json| json.contains("\"representNilExplicitlyInGeneratedContent\":true"))
200 || self.json_schema.contains("\"type\":\"null\"")
201 || self.json_schema.contains("\"type\": \"null\"")
202 }
203
204 #[must_use]
206 pub fn string() -> Self {
207 Self::from_json_schema_unchecked(r#"{"type":"string"}"#.into())
208 }
209
210 #[must_use]
212 pub fn integer() -> Self {
213 Self::from_json_schema_unchecked(r#"{"type":"integer"}"#.into())
214 }
215
216 #[must_use]
218 pub fn number() -> Self {
219 Self::from_json_schema_unchecked(r#"{"type":"number"}"#.into())
220 }
221
222 #[must_use]
224 pub fn boolean() -> Self {
225 Self::from_json_schema_unchecked(r#"{"type":"boolean"}"#.into())
226 }
227
228 #[must_use]
230 pub fn generated_content() -> Self {
231 Self::from_json_schema_unchecked(
232 r##"{"title":"GeneratedContent","description":"Any legal JSON","anyOf":[{"type":"object","additionalProperties":{"$ref":"#"}},{"type":"array","items":{"$ref":"#"}},{"type":"boolean"},{"type":"number"},{"type":"string"}]}"##.into(),
233 )
234 }
235
236 pub(crate) fn from_json_schema_unchecked(json_schema: String) -> Self {
237 Self {
238 json_schema,
239 bridge_request_json: None,
240 }
241 }
242}
243
244#[derive(Debug, Clone, PartialEq)]
246pub enum DynamicGenerationSchema {
247 Object {
248 name: String,
249 description: Option<String>,
250 represent_nil_explicitly_in_generated_content: bool,
251 properties: BTreeMap<String, DynamicGenerationProperty>,
252 },
253 Array {
254 item: Box<DynamicGenerationSchema>,
255 minimum_elements: Option<usize>,
256 maximum_elements: Option<usize>,
257 guides: Vec<GenerationGuide>,
258 },
259 AnyOf {
260 name: String,
261 description: Option<String>,
262 choices: Vec<DynamicGenerationSchema>,
263 },
264 AnyOfStrings {
265 name: String,
266 description: Option<String>,
267 choices: Vec<String>,
268 },
269 String {
270 description: Option<String>,
271 guides: Vec<GenerationGuide>,
272 },
273 Integer {
274 description: Option<String>,
275 guides: Vec<GenerationGuide>,
276 },
277 Float {
278 description: Option<String>,
279 guides: Vec<GenerationGuide>,
280 },
281 Number {
282 description: Option<String>,
283 guides: Vec<GenerationGuide>,
284 },
285 Decimal {
286 description: Option<String>,
287 guides: Vec<GenerationGuide>,
288 },
289 Boolean {
290 description: Option<String>,
291 },
292 GeneratedContent {
293 description: Option<String>,
294 },
295 Reference {
296 name: String,
297 },
298 Null,
299}
300
301impl DynamicGenerationSchema {
302 #[must_use]
304 pub fn object(name: impl Into<String>) -> Self {
305 Self::Object {
306 name: name.into(),
307 description: None,
308 represent_nil_explicitly_in_generated_content: false,
309 properties: BTreeMap::new(),
310 }
311 }
312
313 #[must_use]
315 pub fn new_with_nil_repr(
316 name: impl Into<String>,
317 description: Option<String>,
318 represent_nil_explicitly_in_generated_content: bool,
319 properties: impl IntoIterator<Item = (impl Into<String>, DynamicGenerationProperty)>,
320 ) -> Self {
321 Self::Object {
322 name: name.into(),
323 description,
324 represent_nil_explicitly_in_generated_content,
325 properties: properties
326 .into_iter()
327 .map(|(name, property)| (name.into(), property))
328 .collect(),
329 }
330 }
331
332 pub const NULL: Self = Self::Null;
334
335 #[must_use]
337 pub const fn null() -> Self {
338 Self::Null
339 }
340
341 #[must_use]
343 pub fn string() -> Self {
344 Self::String {
345 description: None,
346 guides: Vec::new(),
347 }
348 }
349
350 #[must_use]
352 pub fn integer() -> Self {
353 Self::Integer {
354 description: None,
355 guides: Vec::new(),
356 }
357 }
358
359 #[must_use]
361 pub fn float() -> Self {
362 Self::Float {
363 description: None,
364 guides: Vec::new(),
365 }
366 }
367
368 #[must_use]
370 pub fn number() -> Self {
371 Self::Number {
372 description: None,
373 guides: Vec::new(),
374 }
375 }
376
377 #[must_use]
379 pub fn decimal() -> Self {
380 Self::Decimal {
381 description: None,
382 guides: Vec::new(),
383 }
384 }
385
386 #[must_use]
388 pub fn boolean() -> Self {
389 Self::Boolean { description: None }
390 }
391
392 #[must_use]
394 pub fn generated_content() -> Self {
395 Self::GeneratedContent { description: None }
396 }
397
398 #[must_use]
400 pub fn array_of(item: Self) -> Self {
401 Self::Array {
402 item: Box::new(item),
403 minimum_elements: None,
404 maximum_elements: None,
405 guides: Vec::new(),
406 }
407 }
408
409 #[must_use]
411 pub fn reference(name: impl Into<String>) -> Self {
412 Self::Reference { name: name.into() }
413 }
414
415 #[must_use]
417 pub fn any_of(name: impl Into<String>, choices: Vec<Self>) -> Self {
418 Self::AnyOf {
419 name: name.into(),
420 description: None,
421 choices,
422 }
423 }
424
425 #[must_use]
427 pub fn any_of_strings(
428 name: impl Into<String>,
429 choices: impl IntoIterator<Item = impl Into<String>>,
430 ) -> Self {
431 Self::AnyOfStrings {
432 name: name.into(),
433 description: None,
434 choices: choices.into_iter().map(Into::into).collect(),
435 }
436 }
437
438 #[must_use]
440 pub fn with_description(mut self, description: impl Into<String>) -> Self {
441 match &mut self {
442 Self::Object {
443 description: slot, ..
444 }
445 | Self::AnyOf {
446 description: slot, ..
447 }
448 | Self::AnyOfStrings {
449 description: slot, ..
450 }
451 | Self::String {
452 description: slot, ..
453 }
454 | Self::Integer {
455 description: slot, ..
456 }
457 | Self::Float {
458 description: slot, ..
459 }
460 | Self::Number {
461 description: slot, ..
462 }
463 | Self::Decimal {
464 description: slot, ..
465 }
466 | Self::Boolean { description: slot }
467 | Self::GeneratedContent { description: slot } => *slot = Some(description.into()),
468 Self::Array { .. } | Self::Reference { .. } | Self::Null => {}
469 }
470 self
471 }
472
473 #[must_use]
475 pub fn with_property(
476 mut self,
477 name: impl Into<String>,
478 property: DynamicGenerationProperty,
479 ) -> Self {
480 if let Self::Object { properties, .. } = &mut self {
481 properties.insert(name.into(), property);
482 }
483 self
484 }
485
486 #[must_use]
488 pub fn with_element_bounds(mut self, minimum: Option<usize>, maximum: Option<usize>) -> Self {
489 if let Self::Array {
490 minimum_elements,
491 maximum_elements,
492 ..
493 } = &mut self
494 {
495 *minimum_elements = minimum;
496 *maximum_elements = maximum;
497 }
498 self
499 }
500
501 #[must_use]
503 pub fn with_guides(mut self, guides: impl IntoIterator<Item = GenerationGuide>) -> Self {
504 let guides: Vec<_> = guides.into_iter().collect();
505 match &mut self {
506 Self::String { guides: slot, .. }
507 | Self::Integer { guides: slot, .. }
508 | Self::Float { guides: slot, .. }
509 | Self::Number { guides: slot, .. }
510 | Self::Decimal { guides: slot, .. }
511 | Self::Array { guides: slot, .. } => *slot = guides,
512 Self::Object { .. }
513 | Self::AnyOf { .. }
514 | Self::AnyOfStrings { .. }
515 | Self::Boolean { .. }
516 | Self::GeneratedContent { .. }
517 | Self::Reference { .. }
518 | Self::Null => {}
519 }
520 self
521 }
522
523 fn to_json_value(&self) -> Value {
524 match self {
525 Self::Object {
526 name,
527 description,
528 represent_nil_explicitly_in_generated_content,
529 properties,
530 } => object_schema_json(
531 name,
532 description,
533 *represent_nil_explicitly_in_generated_content,
534 properties,
535 ),
536 Self::Array {
537 item,
538 minimum_elements,
539 maximum_elements,
540 guides,
541 } => array_schema_json(item, *minimum_elements, *maximum_elements, guides),
542 Self::AnyOf {
543 name,
544 description,
545 choices,
546 } => named_schema_json(
547 "any_of",
548 name,
549 description,
550 Value::Array(choices.iter().map(Self::to_json_value).collect()),
551 ),
552 Self::AnyOfStrings {
553 name,
554 description,
555 choices,
556 } => named_schema_json(
557 "any_of",
558 name,
559 description,
560 Value::Array(choices.iter().cloned().map(Value::String).collect()),
561 ),
562 Self::String {
563 description,
564 guides,
565 } => primitive_schema_json("string", description, guides),
566 Self::Integer {
567 description,
568 guides,
569 } => primitive_schema_json("integer", description, guides),
570 Self::Float {
571 description,
572 guides,
573 } => primitive_schema_json("float", description, guides),
574 Self::Number {
575 description,
576 guides,
577 } => primitive_schema_json("number", description, guides),
578 Self::Decimal {
579 description,
580 guides,
581 } => primitive_schema_json("decimal", description, guides),
582 Self::Boolean { description } => primitive_schema_json("boolean", description, &[]),
583 Self::GeneratedContent { description } => {
584 primitive_schema_json("generated_content", description, &[])
585 }
586 Self::Reference { name } => json!({ "$ref": name }),
587 Self::Null => json!({ "type": "null" }),
588 }
589 }
590}
591
592fn named_schema_json(
593 kind: &str,
594 name: &str,
595 description: &Option<String>,
596 choices: Value,
597) -> Value {
598 let mut map = Map::new();
599 map.insert("type".into(), Value::String(kind.into()));
600 map.insert("name".into(), Value::String(name.to_string()));
601 if let Some(description) = description {
602 map.insert("description".into(), Value::String(description.clone()));
603 }
604 map.insert("choices".into(), choices);
605 Value::Object(map)
606}
607
608fn object_schema_json(
609 name: &str,
610 description: &Option<String>,
611 represent_nil_explicitly_in_generated_content: bool,
612 properties: &BTreeMap<String, DynamicGenerationProperty>,
613) -> Value {
614 let property_map = properties
615 .iter()
616 .map(|(property_name, property)| (property_name.clone(), property.to_json_value()))
617 .collect::<Map<String, Value>>();
618 let mut map = Map::new();
619 map.insert("type".into(), Value::String("object".into()));
620 map.insert("name".into(), Value::String(name.to_string()));
621 if let Some(description) = description {
622 map.insert("description".into(), Value::String(description.clone()));
623 }
624 if represent_nil_explicitly_in_generated_content {
625 map.insert(
626 "representNilExplicitlyInGeneratedContent".into(),
627 Value::Bool(true),
628 );
629 }
630 map.insert("properties".into(), Value::Object(property_map));
631 Value::Object(map)
632}
633
634fn array_schema_json(
635 item: &DynamicGenerationSchema,
636 minimum_elements: Option<usize>,
637 maximum_elements: Option<usize>,
638 guides: &[GenerationGuide],
639) -> Value {
640 let mut map = Map::new();
641 map.insert("type".into(), Value::String("array".into()));
642 map.insert("items".into(), item.to_json_value());
643 if let Some(minimum_elements) = minimum_elements {
644 map.insert("min".into(), Value::from(minimum_elements));
645 }
646 if let Some(maximum_elements) = maximum_elements {
647 map.insert("max".into(), Value::from(maximum_elements));
648 }
649 if !guides.is_empty() {
650 map.insert(
651 "guides".into(),
652 Value::Array(guides.iter().map(GenerationGuide::to_json_value).collect()),
653 );
654 }
655 Value::Object(map)
656}
657
658#[derive(Debug, Clone, PartialEq)]
660pub struct DynamicGenerationProperty {
661 pub schema: DynamicGenerationSchema,
662 pub description: Option<String>,
663 pub optional: bool,
664}
665
666impl DynamicGenerationProperty {
667 #[must_use]
669 pub fn new(schema: DynamicGenerationSchema) -> Self {
670 Self {
671 schema,
672 description: None,
673 optional: false,
674 }
675 }
676
677 #[must_use]
679 pub const fn optional(mut self, optional: bool) -> Self {
680 self.optional = optional;
681 self
682 }
683
684 #[must_use]
686 pub fn with_description(mut self, description: impl Into<String>) -> Self {
687 self.description = Some(description.into());
688 self
689 }
690
691 fn to_json_value(&self) -> Value {
692 let mut value = self.schema.to_json_value();
693 if let Value::Object(map) = &mut value {
694 if let Some(description) = &self.description {
695 map.insert("description".into(), Value::String(description.clone()));
696 }
697 if self.optional {
698 map.insert("optional".into(), Value::Bool(true));
699 }
700 }
701 value
702 }
703}
704
705#[derive(Debug, Clone, PartialEq)]
707pub enum GenerationGuide {
708 StringConstant(String),
709 StringAnyOf(Vec<String>),
710 StringPattern(String),
711 MinimumI64(i64),
712 MaximumI64(i64),
713 RangeI64(i64, i64),
714 MinimumF32(f32),
715 MaximumF32(f32),
716 RangeF32(f32, f32),
717 MinimumF64(f64),
718 MaximumF64(f64),
719 RangeF64(f64, f64),
720 MinimumDecimal(String),
721 MaximumDecimal(String),
722 RangeDecimal(String, String),
723 MinimumCount(usize),
724 MaximumCount(usize),
725 CountRange(usize, usize),
726 CountExact(usize),
727 Element(Box<GenerationGuide>),
728}
729
730impl GenerationGuide {
731 #[must_use]
732 pub fn string_constant(value: impl Into<String>) -> Self {
733 Self::StringConstant(value.into())
734 }
735
736 #[must_use]
737 pub fn string_any_of(values: impl IntoIterator<Item = impl Into<String>>) -> Self {
738 Self::StringAnyOf(values.into_iter().map(Into::into).collect())
739 }
740
741 #[must_use]
742 pub fn string_pattern(pattern: impl Into<String>) -> Self {
743 Self::StringPattern(pattern.into())
744 }
745
746 #[must_use]
747 pub const fn minimum_i64(value: i64) -> Self {
748 Self::MinimumI64(value)
749 }
750
751 #[must_use]
752 pub const fn maximum_i64(value: i64) -> Self {
753 Self::MaximumI64(value)
754 }
755
756 #[must_use]
757 pub const fn range_i64(minimum: i64, maximum: i64) -> Self {
758 Self::RangeI64(minimum, maximum)
759 }
760
761 #[must_use]
762 pub const fn minimum_f32(value: f32) -> Self {
763 Self::MinimumF32(value)
764 }
765
766 #[must_use]
767 pub const fn maximum_f32(value: f32) -> Self {
768 Self::MaximumF32(value)
769 }
770
771 #[must_use]
772 pub const fn range_f32(minimum: f32, maximum: f32) -> Self {
773 Self::RangeF32(minimum, maximum)
774 }
775
776 #[must_use]
777 pub const fn minimum_f64(value: f64) -> Self {
778 Self::MinimumF64(value)
779 }
780
781 #[must_use]
782 pub const fn maximum_f64(value: f64) -> Self {
783 Self::MaximumF64(value)
784 }
785
786 #[must_use]
787 pub const fn range_f64(minimum: f64, maximum: f64) -> Self {
788 Self::RangeF64(minimum, maximum)
789 }
790
791 #[must_use]
792 pub fn minimum_decimal(value: impl Into<String>) -> Self {
793 Self::MinimumDecimal(value.into())
794 }
795
796 #[must_use]
797 pub fn maximum_decimal(value: impl Into<String>) -> Self {
798 Self::MaximumDecimal(value.into())
799 }
800
801 #[must_use]
802 pub fn range_decimal(minimum: impl Into<String>, maximum: impl Into<String>) -> Self {
803 Self::RangeDecimal(minimum.into(), maximum.into())
804 }
805
806 #[must_use]
807 pub const fn minimum_count(count: usize) -> Self {
808 Self::MinimumCount(count)
809 }
810
811 #[must_use]
812 pub const fn maximum_count(count: usize) -> Self {
813 Self::MaximumCount(count)
814 }
815
816 #[must_use]
817 pub const fn count_range(minimum: usize, maximum: usize) -> Self {
818 Self::CountRange(minimum, maximum)
819 }
820
821 #[must_use]
822 pub const fn count(count: usize) -> Self {
823 Self::CountExact(count)
824 }
825
826 #[must_use]
827 pub fn element(guide: GenerationGuide) -> Self {
828 Self::Element(Box::new(guide))
829 }
830
831 fn to_json_value(&self) -> Value {
832 match self {
833 Self::StringConstant(value) => json!({ "kind": "constant", "value": value }),
834 Self::StringAnyOf(values) => json!({ "kind": "any_of", "values": values }),
835 Self::StringPattern(pattern) => json!({ "kind": "pattern", "pattern": pattern }),
836 Self::MinimumI64(value) => json!({ "kind": "minimum", "value": value }),
837 Self::MaximumI64(value) => json!({ "kind": "maximum", "value": value }),
838 Self::RangeI64(minimum, maximum) => {
839 json!({ "kind": "range", "min": minimum, "max": maximum })
840 }
841 Self::MinimumF32(value) => json!({ "kind": "minimum", "value": value }),
842 Self::MaximumF32(value) => json!({ "kind": "maximum", "value": value }),
843 Self::RangeF32(minimum, maximum) => {
844 json!({ "kind": "range", "min": minimum, "max": maximum })
845 }
846 Self::MinimumF64(value) => json!({ "kind": "minimum", "value": value }),
847 Self::MaximumF64(value) => json!({ "kind": "maximum", "value": value }),
848 Self::RangeF64(minimum, maximum) => {
849 json!({ "kind": "range", "min": minimum, "max": maximum })
850 }
851 Self::MinimumDecimal(value) => json!({ "kind": "minimum", "value": value }),
852 Self::MaximumDecimal(value) => json!({ "kind": "maximum", "value": value }),
853 Self::RangeDecimal(minimum, maximum) => {
854 json!({ "kind": "range", "min": minimum, "max": maximum })
855 }
856 Self::MinimumCount(count) => json!({ "kind": "minimum_count", "value": count }),
857 Self::MaximumCount(count) => json!({ "kind": "maximum_count", "value": count }),
858 Self::CountRange(minimum, maximum) => {
859 json!({ "kind": "count", "min": minimum, "max": maximum })
860 }
861 Self::CountExact(count) => json!({ "kind": "count", "value": count }),
862 Self::Element(guide) => json!({ "kind": "element", "guide": guide.to_json_value() }),
863 }
864 }
865}
866
867fn primitive_schema_json(
868 kind: &str,
869 description: &Option<String>,
870 guides: &[GenerationGuide],
871) -> Value {
872 let mut map = Map::new();
873 map.insert("type".into(), Value::String(kind.into()));
874 if let Some(description) = description {
875 map.insert("description".into(), Value::String(description.clone()));
876 }
877 if !guides.is_empty() {
878 map.insert(
879 "guides".into(),
880 Value::Array(guides.iter().map(GenerationGuide::to_json_value).collect()),
881 );
882 }
883 Value::Object(map)
884}
885
886pub trait Generable: Sized + FromGeneratedContent + ToGeneratedContent {
888 fn generation_schema() -> Result<GenerationSchema, FMError>;
890}
891
892unsafe extern "C" fn schema_callback_trampoline(
897 context: *mut c_void,
898 response: *mut c_char,
899 error: *mut c_char,
900 status: i32,
901) {
902 let tx = Box::from_raw(context.cast::<mpsc::Sender<Result<String, FMError>>>());
903 let result = if status == ffi::status::OK && !response.is_null() {
904 let value = core::ffi::CStr::from_ptr(response)
905 .to_string_lossy()
906 .into_owned();
907 ffi::fm_string_free(response);
908 Ok(value)
909 } else {
910 Err(crate::error::from_swift(status, error))
911 };
912 let _ = tx.send(result);
913}