diff --git a/src/bin/cli.rs b/src/bin/cli.rs index 72230df..22d5b8d 100644 --- a/src/bin/cli.rs +++ b/src/bin/cli.rs @@ -284,7 +284,7 @@ fn convert(args: ConvertArgs) -> Result<(), Box> { // Render the template. let rendered = match args.template { - Templates::JsonSchema => model.json_schema(args.root)?, + Templates::JsonSchema => model.json_schema(args.root, false)?, _ => render_jinja_template(&args.template, &mut model, None)?, }; @@ -350,7 +350,7 @@ fn render_all_json_schemes( fs::create_dir_all(outdir)?; // Render the JSON Schema for each entity - model.json_schema_all(outdir.to_path_buf())?; + model.json_schema_all(outdir.to_path_buf(), false)?; Ok(()) } diff --git a/src/bindings/wasm.rs b/src/bindings/wasm.rs index fa7f10c..8984282 100644 --- a/src/bindings/wasm.rs +++ b/src/bindings/wasm.rs @@ -82,6 +82,7 @@ pub fn convert_to(markdown_content: &str, template: Templates) -> Result Result) -> Result { +pub fn json_schema( + markdown_content: &str, + root: Option, + openai: bool, +) -> Result { let model = DataModel::from_markdown_string(markdown_content) .map_err(|e| JsValue::from_str(&format!("Error parsing markdown content: {}", e)))?; @@ -103,7 +108,7 @@ pub fn json_schema(markdown_content: &str, root: Option) -> Result) -> Result> { + pub fn json_schema( + &self, + obj_name: Option, + openai: bool, + ) -> Result> { if self.objects.is_empty() { panic!("No objects found in the markdown file"); } @@ -112,11 +116,14 @@ impl DataModel { if self.objects.iter().all(|o| o.name != name) { panic!("Object '{}' not found in the markdown file", name); } - Ok(serde_json::to_string_pretty(&to_json_schema(self, &name)?)?) + Ok(serde_json::to_string_pretty(&to_json_schema( + self, &name, openai, + )?)?) } None => Ok(serde_json::to_string_pretty(&to_json_schema( self, &self.objects[0].name, + openai, )?)?), } } @@ -125,6 +132,7 @@ impl DataModel { // and write them to a file // // * `path` - Path to the directory where the JSON schema files will be written + // * `openai` - Whether to remove options from the schema properties. OpenAI does not support options. // // # Panics // @@ -137,7 +145,7 @@ impl DataModel { // model.parse("path/to/file.md".to_string()); // model.json_schema_all("path/to/directory".to_string()); // ``` - pub fn json_schema_all(&self, path: PathBuf) -> Result<(), Box> { + pub fn json_schema_all(&self, path: PathBuf, openai: bool) -> Result<(), Box> { if self.objects.is_empty() { panic!("No objects found in the markdown file"); } @@ -149,7 +157,7 @@ impl DataModel { let base_path = path.to_str().ok_or("Failed to convert path to string")?; for object in &self.objects { - let schema = to_json_schema(self, &object.name)?; + let schema = to_json_schema(self, &object.name, openai)?; let file_name = format!("{}/{}.json", base_path, object.name); fs::write(file_name, serde_json::to_string_pretty(&schema)?) .expect("Could not write file"); diff --git a/src/json/export.rs b/src/json/export.rs index 0402417..3c97748 100644 --- a/src/json/export.rs +++ b/src/json/export.rs @@ -29,6 +29,7 @@ use std::{ use crate::{ attribute::Attribute, datamodel::DataModel, + markdown::frontmatter::FrontMatter, object::{Enumeration, Object}, validation::BASIC_TYPES, }; @@ -47,7 +48,11 @@ const SCHEMA: &str = "https://json-schema.org/draft/2020-12/schema"; /// # Returns /// /// A `Result` containing the `SchemaObject` or an error message. -pub fn to_json_schema(model: &DataModel, root: &str) -> Result { +pub fn to_json_schema( + model: &DataModel, + root: &str, + openai: bool, +) -> Result { let root_object = retrieve_object(model, root)?; let mut schema_object = schema::SchemaObject::try_from(root_object)?; @@ -62,10 +67,7 @@ pub fn to_json_schema(model: &DataModel, root: &str) -> Result) { for (_, property) in schema.properties.iter_mut() { if let Some(reference) = property.term.clone() { @@ -193,6 +201,49 @@ fn resolve_prefixes(schema: &mut schema::SchemaObject, prefixes: &HashMap for schema::SchemaType { type Error = String; diff --git a/src/llm/extraction.rs b/src/llm/extraction.rs index 078ef6f..bffa7f0 100644 --- a/src/llm/extraction.rs +++ b/src/llm/extraction.rs @@ -50,7 +50,7 @@ fn prepare_response_format( root: &str, multiple: bool, ) -> Result> { - let schema = to_json_schema(model, root)?; + let schema = to_json_schema(model, root, true)?; if multiple { Ok(json!( diff --git a/src/pipeline.rs b/src/pipeline.rs index 684a484..c656555 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -312,7 +312,7 @@ fn serialize_to_json_schema( match root { Some(root) => { - let schema = model.json_schema(Some(root))?; + let schema = model.json_schema(Some(root), false)?; save_to_file(out, &schema)?; print_render_msg(out, &Templates::JsonSchema); Ok(()) @@ -376,7 +376,7 @@ fn serialize_all_json_schemes( match merge_state { MergeState::Merge => { let model = build_models(specs)?; - model.json_schema_all(out.to_path_buf())?; + model.json_schema_all(out.to_path_buf(), false)?; print_render_msg(out, &Templates::JsonSchemaAll); Ok(()) } @@ -384,7 +384,7 @@ fn serialize_all_json_schemes( for spec in specs { let model = DataModel::from_markdown(spec)?; let path = out.join(get_file_name(spec)); - model.json_schema_all(path.to_path_buf())?; + model.json_schema_all(path.to_path_buf(), false)?; print_render_msg(&path, &Templates::JsonSchemaAll); } Ok(()) diff --git a/tests/data/expected_json_schema.json b/tests/data/expected_json_schema.json index b4ecfcf..fabefc2 100644 --- a/tests/data/expected_json_schema.json +++ b/tests/data/expected_json_schema.json @@ -75,7 +75,7 @@ "names": { "title": "names", "type": "array", - "$term": "schema:hello", + "$term": "http://schema.org/hello", "items": { "type": "string" } @@ -83,7 +83,7 @@ "number": { "title": "number", "type": "number", - "$term": "schema:one", + "$term": "http://schema.org/one", "minimum": 0.0 } }, diff --git a/tests/data/expected_json_schema_openai.json b/tests/data/expected_json_schema_openai.json new file mode 100644 index 0000000..0195764 --- /dev/null +++ b/tests/data/expected_json_schema_openai.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.github.com/my/repo/", + "title": "Test", + "type": "object", + "properties": { + "array_valued": { + "title": "array_valued", + "type": "array", + "$term": "http://schema.org/something", + "items": { + "$ref": "#/$defs/Test2" + } + }, + "multiple_types": { + "title": "multiple_types", + "oneOf": [ + { + "type": "number" + }, + { + "$ref": "#/$defs/Test2" + } + ] + }, + "multiple_types_array": { + "title": "multiple_types_array", + "type": "array", + "items": { + "oneOf": [ + { + "type": "number" + }, + { + "$ref": "#/$defs/Test2" + } + ] + } + }, + "name": { + "title": "name", + "type": "string", + "description": "A test description", + "$term": "http://schema.org/hello" + }, + "number": { + "title": "number", + "type": "number", + "$term": "http://schema.org/one" + }, + "ontology": { + "title": "ontology", + "$ref": "#/$defs/Ontology" + }, + "single_valued": { + "title": "single_valued", + "type": "object", + "$ref": "#/$defs/Test2" + } + }, + "$defs": { + "Ontology": { + "title": "Ontology", + "type": "string", + "enum": [ + "https://www.evidenceontology.org/term/", + "https://amigo.geneontology.org/amigo/term/", + "http://semanticscience.org/resource/" + ] + }, + "Test2": { + "title": "Test2", + "type": "object", + "properties": { + "names": { + "title": "names", + "type": "array", + "$term": "http://schema.org/hello", + "items": { + "type": "string" + } + }, + "number": { + "title": "number", + "type": "number", + "$term": "http://schema.org/one" + } + }, + "required": [], + "additionalProperties": false + } + }, + "required": [ + "name" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 75cb7a5..108b7a9 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -139,7 +139,7 @@ mod tests { // Act model - .json_schema(Some("Test".to_string())) + .json_schema(Some("Test".to_string()), false) .expect("Could not generate JSON schema"); } @@ -151,7 +151,7 @@ mod tests { // Act let schema = model - .json_schema(None) + .json_schema(None, false) .expect("Could not generate JSON schema"); // Assert @@ -160,6 +160,24 @@ mod tests { assert_eq!(schema, expected); } + #[test] + fn test_json_schema_openai() { + // Arrange + let path = Path::new("tests/data/model_json_schema.md"); + let model = DataModel::from_markdown(path).expect("Could not parse markdown"); + + // Act + let schema = model + .json_schema(None, true) + .expect("Could not generate JSON schema"); + + // Assert + let expected = + std::fs::read_to_string("tests/data/expected_json_schema_openai.json").unwrap(); + + assert_eq!(schema, expected); + } + #[test] #[should_panic] fn test_json_schema_object_not_found() { @@ -169,7 +187,7 @@ mod tests { // Act model - .json_schema(Some("Test3".to_string())) + .json_schema(Some("Test3".to_string()), false) .expect("Could not generate JSON schema"); }