Files
crank/crates/crank-schema/src/model.rs
T
github-ops ba29ac7b94
Deploy / deploy (push) Successful in 2m44s
CI / Rust Checks (push) Successful in 5m31s
CI / UI Checks (push) Successful in 5s
CI / Deployment Manifests (push) Successful in 2s
CI / Frontend E2E (push) Successful in 4m24s
chore: publish clean community baseline
2026-06-19 16:45:51 +00:00

227 lines
6.5 KiB
Rust

use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum XmlNodeKind {
Element,
Attribute,
Text,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct XmlQualifiedName {
pub local_name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub namespace_uri: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prefix: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct XmlSchemaBinding {
pub node_kind: XmlNodeKind,
pub name: XmlQualifiedName,
#[serde(default)]
pub repeated: bool,
#[serde(default)]
pub nillable: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SchemaKind {
Object,
Array,
String,
Integer,
Number,
Boolean,
Enum,
#[serde(rename = "null")]
Null,
Oneof,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Schema {
#[serde(rename = "type")]
pub kind: SchemaKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default)]
pub required: bool,
#[serde(default)]
pub nullable: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_value: Option<Value>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub fields: BTreeMap<String, Schema>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub items: Option<Box<Schema>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub enum_values: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub variants: Vec<Schema>,
}
impl Schema {
pub fn is_object(&self) -> bool {
self.kind == SchemaKind::Object
}
pub fn field(&self, name: &str) -> Option<&Schema> {
self.fields.get(name)
}
pub fn has_required_fields(&self) -> bool {
self.fields.values().any(|field| field.required)
}
pub fn expected_type_name(&self) -> &'static str {
match self.kind {
SchemaKind::Object => "object",
SchemaKind::Array => "array",
SchemaKind::String => "string",
SchemaKind::Integer => "integer",
SchemaKind::Number => "number",
SchemaKind::Boolean => "boolean",
SchemaKind::Enum => "enum",
SchemaKind::Null => "null",
SchemaKind::Oneof => "oneof",
}
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::{Schema, SchemaKind, XmlNodeKind, XmlQualifiedName, XmlSchemaBinding};
#[test]
fn object_schema_exposes_fields() {
let mut fields = BTreeMap::new();
fields.insert(
"email".to_owned(),
Schema {
kind: SchemaKind::String,
description: None,
required: true,
nullable: false,
default_value: None,
fields: BTreeMap::new(),
items: None,
enum_values: Vec::new(),
variants: Vec::new(),
},
);
let schema = Schema {
kind: SchemaKind::Object,
description: Some("User input".to_owned()),
required: true,
nullable: false,
default_value: None,
fields,
items: None,
enum_values: Vec::new(),
variants: Vec::new(),
};
assert!(schema.is_object());
assert!(schema.has_required_fields());
assert_eq!(
schema.field("email").map(|field| field.required),
Some(true)
);
}
#[test]
fn schema_serializes_type_field() {
let schema = Schema {
kind: SchemaKind::Array,
description: None,
required: false,
nullable: false,
default_value: None,
fields: BTreeMap::new(),
items: Some(Box::new(Schema {
kind: SchemaKind::String,
description: None,
required: false,
nullable: false,
default_value: None,
fields: BTreeMap::new(),
items: None,
enum_values: Vec::new(),
variants: Vec::new(),
})),
enum_values: Vec::new(),
variants: Vec::new(),
};
let value = serde_json::to_value(schema).unwrap();
assert_eq!(value["type"], "array");
assert_eq!(value["items"]["type"], "string");
}
#[test]
fn schema_roundtrips_through_yaml() {
let schema = Schema {
kind: SchemaKind::Object,
description: Some("Lead output".to_owned()),
required: true,
nullable: false,
default_value: None,
fields: BTreeMap::from([(
"id".to_owned(),
Schema {
kind: SchemaKind::String,
description: None,
required: true,
nullable: false,
default_value: None,
fields: BTreeMap::new(),
items: None,
enum_values: Vec::new(),
variants: Vec::new(),
},
)]),
items: None,
enum_values: Vec::new(),
variants: Vec::new(),
};
let yaml = serde_yaml::to_string(&schema).unwrap();
let restored: Schema = serde_yaml::from_str(&yaml).unwrap();
assert!(yaml.contains("type: object"));
assert_eq!(restored, schema);
}
#[test]
fn xml_schema_binding_roundtrips() {
let binding = XmlSchemaBinding {
node_kind: XmlNodeKind::Element,
name: XmlQualifiedName {
local_name: "CreateLeadRequest".to_owned(),
namespace_uri: Some("urn:crm".to_owned()),
prefix: Some("crm".to_owned()),
},
repeated: false,
nillable: true,
};
let value = serde_json::to_value(&binding).unwrap();
let restored: XmlSchemaBinding = serde_json::from_value(value.clone()).unwrap();
assert_eq!(value["node_kind"], "element");
assert_eq!(value["name"]["local_name"], "CreateLeadRequest");
assert_eq!(restored, binding);
}
}