Repository: oxidecomputer/typify (Rust)

Issue:
Arrays wrongly require `items`
Given
```json
{
  "title": "hello",
  "type": "array",
  "minItems": 1,
  "uniqueItems": true
}
```
cargo-typify 0.0.13 errors:
```
% cargo typify other.json
Error:
   0: Failed to convert JSON Schema to Rust code
   1: Schema conversion failed
   2: schema invalid: unhandled array validation ArrayValidation {
          items: None,
          additional_items: None,
          max_items: None,
          min_items: Some(
              1,
          ),
          unique_items: Some(
              true,
          ),
          contains: None,
      }
...
```

In the latest JSON Schema spec ([2020-12 Core](https://json-schema.org/draft/2020-12/json-schema-core.html#section-10.3.1.2-4)),
> Omitting [`items`] has the same assertion behavior as an empty schema.

typify _does_ do something reasonable when adding an `"items": {}` to the schema: it generates a struct like
```rust
pub struct Hello(pub Vec<serde_json::Value>);
```

(arguably the minimum reasonable data structure would be something like a `Vec<serde_json::Value>`, but not sure if typify should try to be that clever)


Discussion:
Thanks for the bug report; this is a pretty straightforward fix (and similar to fixes applied recently to other schema archetypes).

Basically, change
```rust
    fn convert_array<'a>(
        &mut self,
        type_name: Name,
        metadata: &'a Option<Box<Metadata>>,
        validation: &ArrayValidation,
    ) -> Result<(TypeEntry, &'a Option<Box<Metadata>>)> {
```
... to take an `Option<ArrayValidation>` instead...

PRs welcome if I don't get to it in the over the next couple of weekends.



Patch:
diff --git a/typify-impl/src/convert.rs b/typify-impl/src/convert.rs
index 0812e481..ca1598ac 100644
--- a/typify-impl/src/convert.rs
+++ b/typify-impl/src/convert.rs
@@ -1415,6 +1415,25 @@ impl TypeSpace {
                 }
             }
 
+            // Arrays and sets with no specified items.
+            ArrayValidation {
+                items: None,
+                additional_items: _, // By spec: ignored for missing items
+                max_items: _,        // TODO enforce size limitations
+                min_items: _,        // TODO enforce size limitations
+                unique_items,
+                contains: None,
+            } => {
+                self.uses_serde_json = true;
+                let type_id = self.assign_type(TypeEntryDetails::JsonValue.into());
+
+                // If items are unique, this is a Set; otherwise it's an Array.
+                match unique_items {
+                    Some(true) => Ok((TypeEntryDetails::Set(type_id).into(), metadata)),
+                    _ => Ok((TypeEntryDetails::Vec(type_id).into(), metadata)),
+                }
+            }
+
             _ => Err(Error::InvalidSchema {
                 type_name: type_name.into_option(),
                 reason: format!("unhandled array validation {:#?}", validation),


Test patch:
diff --git a/typify/tests/schemas/arrays-and-tuples.json b/typify/tests/schemas/arrays-and-tuples.json
index 5a9b620a..5d040148 100644
--- a/typify/tests/schemas/arrays-and-tuples.json
+++ b/typify/tests/schemas/arrays-and-tuples.json
@@ -59,6 +59,11 @@
         "$comment": "ignored",
         "type": "string"
       }
+    },
+    "array-sans-items": {
+      "type": "array",
+      "minItems": 1,
+      "uniqueItems": true
     }
   }
 }
\ No newline at end of file
diff --git a/typify/tests/schemas/arrays-and-tuples.rs b/typify/tests/schemas/arrays-and-tuples.rs
index 29819f42..3071e2f6 100644
--- a/typify/tests/schemas/arrays-and-tuples.rs
+++ b/typify/tests/schemas/arrays-and-tuples.rs
@@ -1,6 +1,29 @@
 #[allow(unused_imports)]
 use serde::{Deserialize, Serialize};
 #[derive(Clone, Debug, Deserialize, Serialize)]
+pub struct ArraySansItems(pub Vec<serde_json::Value>);
+impl std::ops::Deref for ArraySansItems {
+    type Target = Vec<serde_json::Value>;
+    fn deref(&self) -> &Vec<serde_json::Value> {
+        &self.0
+    }
+}
+impl From<ArraySansItems> for Vec<serde_json::Value> {
+    fn from(value: ArraySansItems) -> Self {
+        value.0
+    }
+}
+impl From<&ArraySansItems> for ArraySansItems {
+    fn from(value: &ArraySansItems) -> Self {
+        value.clone()
+    }
+}
+impl From<Vec<serde_json::Value>> for ArraySansItems {
+    fn from(value: Vec<serde_json::Value>) -> Self {
+        Self(value)
+    }
+}
+#[derive(Clone, Debug, Deserialize, Serialize)]
 pub struct LessSimpleTwoTuple(pub (String, String));
 impl std::ops::Deref for LessSimpleTwoTuple {
     type Target = (String, String);