|
1 | 1 | use crate::prelude::*; |
2 | | - |
3 | 2 | use crate::utils::immutable::RefList; |
| 3 | +use indexmap::IndexMap; |
4 | 4 | use schemars::schema::{ |
5 | 5 | ArrayValidation, InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec, |
6 | 6 | SubschemaValidation, |
@@ -74,6 +74,9 @@ impl JsonSchemaBuilder { |
74 | 74 | schema::BasicValueType::Str => { |
75 | 75 | schema.instance_type = Some(SingleOrVec::Single(Box::new(InstanceType::String))); |
76 | 76 | } |
| 77 | + schema::BasicValueType::Enum => { |
| 78 | + schema.instance_type = Some(SingleOrVec::Single(Box::new(InstanceType::String))); |
| 79 | + } |
77 | 80 | schema::BasicValueType::Bytes => { |
78 | 81 | schema.instance_type = Some(SingleOrVec::Single(Box::new(InstanceType::String))); |
79 | 82 | } |
@@ -245,15 +248,34 @@ impl JsonSchemaBuilder { |
245 | 248 | field_path.prepend(&f.name), |
246 | 249 | ); |
247 | 250 | if self.options.fields_always_required && f.value_type.nullable { |
248 | | - if let Some(instance_type) = &mut field_schema.instance_type { |
249 | | - let mut types = match instance_type { |
250 | | - SingleOrVec::Single(t) => vec![**t], |
251 | | - SingleOrVec::Vec(t) => std::mem::take(t), |
| 251 | + if field_schema.enum_values.is_some() { |
| 252 | + // Keep the enum as-is and support null via oneOf |
| 253 | + let non_null = Schema::Object(field_schema); |
| 254 | + let null_branch = Schema::Object(SchemaObject { |
| 255 | + instance_type: Some(SingleOrVec::Single(Box::new( |
| 256 | + InstanceType::Null, |
| 257 | + ))), |
| 258 | + ..Default::default() |
| 259 | + }); |
| 260 | + field_schema = SchemaObject { |
| 261 | + subschemas: Some(Box::new(SubschemaValidation { |
| 262 | + one_of: Some(vec![non_null, null_branch]), |
| 263 | + ..Default::default() |
| 264 | + })), |
| 265 | + ..Default::default() |
252 | 266 | }; |
253 | | - types.push(InstanceType::Null); |
254 | | - *instance_type = SingleOrVec::Vec(types); |
| 267 | + } else { |
| 268 | + if let Some(instance_type) = &mut field_schema.instance_type { |
| 269 | + let mut types = match instance_type { |
| 270 | + SingleOrVec::Single(t) => vec![**t], |
| 271 | + SingleOrVec::Vec(t) => std::mem::take(t), |
| 272 | + }; |
| 273 | + types.push(InstanceType::Null); |
| 274 | + *instance_type = SingleOrVec::Vec(types); |
| 275 | + } |
255 | 276 | } |
256 | 277 | } |
| 278 | + |
257 | 279 | (f.name.to_string(), field_schema.into()) |
258 | 280 | }) |
259 | 281 | .collect(), |
@@ -298,9 +320,26 @@ impl JsonSchemaBuilder { |
298 | 320 | enriched_value_type: &schema::EnrichedValueType, |
299 | 321 | field_path: RefList<'_, &'_ spec::FieldName>, |
300 | 322 | ) -> SchemaObject { |
301 | | - self.for_value_type(schema_base, &enriched_value_type.typ, field_path) |
302 | | - } |
| 323 | + let mut out = self.for_value_type(schema_base, &enriched_value_type.typ, field_path); |
| 324 | + |
| 325 | + if let schema::ValueType::Basic(schema::BasicValueType::Enum) = &enriched_value_type.typ { |
| 326 | + if let Some(variants) = enriched_value_type.attrs.get("variants") { |
| 327 | + if let Some(arr) = variants.as_array() { |
| 328 | + let enum_values: Vec<serde_json::Value> = arr |
| 329 | + .iter() |
| 330 | + .filter_map(|v| { |
| 331 | + v.as_str().map(|s| serde_json::Value::String(s.to_string())) |
| 332 | + }) |
| 333 | + .collect(); |
| 334 | + if !enum_values.is_empty() { |
| 335 | + out.enum_values = Some(enum_values); |
| 336 | + } |
| 337 | + } |
| 338 | + } |
| 339 | + } |
303 | 340 |
|
| 341 | + out |
| 342 | + } |
304 | 343 | fn build_extra_instructions(&self) -> Result<Option<String>> { |
305 | 344 | if self.extra_instructions_per_field.is_empty() { |
306 | 345 | return Ok(None); |
@@ -458,6 +497,53 @@ mod tests { |
458 | 497 | .assert_eq(&serde_json::to_string_pretty(&json_schema).unwrap()); |
459 | 498 | } |
460 | 499 |
|
| 500 | + #[test] |
| 501 | + fn test_basic_types_enum_without_variants() { |
| 502 | + let value_type = EnrichedValueType { |
| 503 | + typ: ValueType::Basic(BasicValueType::Enum), |
| 504 | + nullable: false, |
| 505 | + attrs: Arc::new(BTreeMap::new()), |
| 506 | + }; |
| 507 | + let options = create_test_options(); |
| 508 | + let result = build_json_schema(value_type, options).unwrap(); |
| 509 | + let json_schema = schema_to_json(&result.schema); |
| 510 | + |
| 511 | + expect![[r#" |
| 512 | + { |
| 513 | + "type": "string" |
| 514 | + }"#]] |
| 515 | + .assert_eq(&serde_json::to_string_pretty(&json_schema).unwrap()); |
| 516 | + } |
| 517 | + |
| 518 | + #[test] |
| 519 | + fn test_basic_types_enum_with_variants() { |
| 520 | + let mut attrs = BTreeMap::new(); |
| 521 | + attrs.insert( |
| 522 | + "variants".to_string(), |
| 523 | + serde_json::json!(["red", "green", "blue"]), |
| 524 | + ); |
| 525 | + |
| 526 | + let value_type = EnrichedValueType { |
| 527 | + typ: ValueType::Basic(BasicValueType::Enum), |
| 528 | + nullable: false, |
| 529 | + attrs: Arc::new(attrs), |
| 530 | + }; |
| 531 | + let options = create_test_options(); |
| 532 | + let result = build_json_schema(value_type, options).unwrap(); |
| 533 | + let json_schema = schema_to_json(&result.schema); |
| 534 | + |
| 535 | + expect![[r#" |
| 536 | + { |
| 537 | + "enum": [ |
| 538 | + "red", |
| 539 | + "green", |
| 540 | + "blue" |
| 541 | + ], |
| 542 | + "type": "string" |
| 543 | + }"#]] |
| 544 | + .assert_eq(&serde_json::to_string_pretty(&json_schema).unwrap()); |
| 545 | + } |
| 546 | + |
461 | 547 | #[test] |
462 | 548 | fn test_basic_types_bool() { |
463 | 549 | let value_type = EnrichedValueType { |
|
0 commit comments