#![cfg(feature = "schema")]
#![allow(missing_docs)]
use noyalib::{from_str, schema_for, schema_for_yaml, JsonSchema, Value};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, JsonSchema)]
#[allow(dead_code)]
struct ServerConfig {
port: u16,
host: String,
#[serde(default)]
tls: bool,
}
#[test]
fn schema_for_returns_object_shaped_value() {
let schema = schema_for::<ServerConfig>().unwrap();
assert_eq!(schema["type"].as_str(), Some("object"));
assert_eq!(schema["title"].as_str(), Some("ServerConfig"));
}
#[test]
fn schema_yaml_round_trips_through_value() {
let yaml = schema_for_yaml::<ServerConfig>().unwrap();
let parsed: Value = from_str(&yaml).unwrap();
let direct = schema_for::<ServerConfig>().unwrap();
assert_eq!(parsed, direct);
}
#[test]
fn schema_yaml_is_valid_yaml() {
let yaml = schema_for_yaml::<ServerConfig>().unwrap();
let _: Value = from_str(&yaml).unwrap();
}
#[test]
fn doc_strings_become_descriptions() {
let schema = schema_for::<ServerConfig>().unwrap();
assert_eq!(
schema["properties"]["port"]["description"].as_str(),
Some("Port the server binds on."),
);
assert_eq!(
schema["properties"]["host"]["description"].as_str(),
Some("Hostname or IP literal."),
);
}
#[test]
fn serde_default_drops_field_from_required() {
let schema = schema_for::<ServerConfig>().unwrap();
let required: Vec<&str> = match &schema["required"] {
Value::Sequence(s) => s.iter().filter_map(Value::as_str).collect(),
other => panic!("required must be a sequence, got {other:?}"),
};
assert!(required.contains(&"port"));
assert!(required.contains(&"host"));
assert!(!required.contains(&"tls"));
}
#[test]
fn integer_bounds_are_emitted_for_fixed_width_ints() {
let schema = schema_for::<ServerConfig>().unwrap();
let port = &schema["properties"]["port"];
assert_eq!(port["minimum"].as_i64(), Some(0));
assert_eq!(port["maximum"].as_i64(), Some(65_535));
}
#[derive(Serialize, Deserialize, JsonSchema)]
#[allow(dead_code)]
struct Database {
host: String,
port: u16,
}
#[derive(Serialize, Deserialize, JsonSchema)]
#[allow(dead_code)]
struct App {
name: String,
db: Database,
}
#[test]
fn nested_types_are_emitted_via_definitions() {
let schema = schema_for::<App>().unwrap();
let defs = &schema["$defs"];
let db = &defs["Database"];
assert_eq!(db["type"].as_str(), Some("object"));
assert!(matches!(db["properties"]["host"], Value::Mapping(_)));
}
#[derive(Serialize, Deserialize, JsonSchema)]
#[allow(dead_code)]
#[serde(rename_all = "lowercase")]
enum LogLevel {
Trace,
Debug,
Info,
Warn,
Error,
}
#[test]
fn json_schema_for_value_field() {
use noyalib::Value;
#[derive(Deserialize, JsonSchema)]
#[allow(dead_code)]
struct Envelope {
id: String,
payload: Value,
}
let schema = schema_for::<Envelope>().unwrap();
assert_eq!(schema["type"].as_str(), Some("object"));
assert!(matches!(schema["properties"]["payload"], Value::Mapping(_)));
let defs = &schema["$defs"];
let yaml_value = &defs["YamlValue"];
assert!(matches!(yaml_value["oneOf"], Value::Sequence(_)));
}
#[test]
fn unit_only_enum_emits_string_enum() {
let schema = schema_for::<LogLevel>().unwrap();
let variants: Vec<&str> = match &schema["enum"] {
Value::Sequence(s) => s.iter().filter_map(Value::as_str).collect(),
other => panic!("expected enum array, got {other:?}"),
};
assert!(variants.contains(&"trace"));
assert!(variants.contains(&"warn"));
assert!(variants.contains(&"error"));
}