#![allow(dead_code, deprecated)]
use schematic::*;
use serde::Serialize;
use std::collections::BTreeMap;
#[derive(Config)]
pub struct SomeConfig {
foo: String,
bar: usize,
}
#[derive(Config)]
enum AllUnit {
Foo,
#[setting(default)]
Bar,
Baz,
}
#[derive(Config)]
enum AllUnnamed {
Foo(String),
Bar(bool),
Baz(usize),
}
#[derive(Config)]
enum OfBothTypes {
#[setting(null)]
Foo,
#[setting(default)]
Bar(bool, usize),
}
fn merge_tuple<C>(
prev: (String, usize),
next: (String, usize),
_: &C,
) -> MergeResult<(String, usize)> {
Ok(Some((format!("{}-{}", prev.0, next.0), (prev.1 + next.1))))
}
fn validate_tuple<T, C>(_: (&String, &usize), _: &T, _: &C, _: bool) -> ValidateResult {
Ok(())
}
fn validate_nested<T, C>(_: &PartialSomeConfig, _: &T, _: &C, _: bool) -> ValidateResult {
Ok(())
}
#[derive(Config)]
enum Collections {
#[setting(merge = merge::append_vec, validate = validate::min_length(1))]
List(Vec<String>),
#[setting(merge = merge::merge_btreemap, validate = validate::min_length(1))]
Map(BTreeMap<String, String>),
#[setting(merge = merge_tuple, validate = validate_tuple)]
Tuple(String, usize),
}
#[derive(Config)]
enum NestedConfigs {
String(String),
#[setting(default, nested, validate = validate_nested)]
Object(SomeConfig),
#[setting(nested)]
Objects(SomeConfig, SomeConfig),
}
#[allow(unused_parens)]
fn validate_string<T, C>(_: (&Option<String>), _: &T, _: &C, _: bool) -> Result<(), ValidateError> {
Ok(())
}
#[derive(Config)]
enum ValidateConfigs {
Normal(String),
#[setting(validate = validate_string)]
Optional(Option<String>),
#[setting(validate = validate_string, required)]
Required(Option<String>),
#[setting(required)]
RequiredMany(Option<String>, Option<String>),
}
#[derive(Config, Serialize)]
#[serde(untagged, expecting = "something")]
enum WithSerde {
#[serde(rename = "fooooo")]
Foo(String),
#[serde(alias = "barrrrr")]
Bar(bool),
#[setting(rename = "bazzzzz")]
Baz(usize),
}
#[derive(Config)]
enum WithComments {
Foo,
Bar,
Baz,
}
#[derive(Config)]
#[config(serde(untagged, expecting = "something"))]
enum Untagged {
Unit,
OneTuple(bool),
#[setting(rename = "bazzer")]
TwoTuple(usize, String),
#[setting(nested)]
TupleOfStruct(SomeConfig),
}
#[derive(Config)]
enum ExternalTagged {
Foo,
Bar(bool),
#[setting(rename = "bazzer")]
Baz(usize),
#[setting(nested)]
Qux(SomeConfig),
}
#[derive(Config)]
#[config(serde(tag = "type"))]
enum InternalTagged {
Foo,
Bar(bool),
#[setting(rename = "bazzer")]
Baz(usize),
#[setting(nested)]
Qux(SomeConfig),
}
#[derive(Config)]
#[config(serde(tag = "type", content = "content"))]
enum AdjacentTagged {
Foo,
Bar(bool),
#[setting(rename = "bazzer")]
Baz(usize),
#[setting(nested)]
Qux(SomeConfig),
}
fn create_gen() -> schema::SchemaGenerator {
let mut generator = schema::SchemaGenerator::default();
generator.add::<AllUnit>();
generator.add::<AllUnnamed>();
generator.add::<OfBothTypes>();
generator.add::<NestedConfigs>();
generator.add::<WithSerde>();
generator.add::<WithComments>();
generator.add::<Untagged>();
generator.add::<ExternalTagged>();
generator.add::<InternalTagged>();
generator.add::<AdjacentTagged>();
generator.add::<PartialAllUnit>();
generator.add::<PartialAllUnnamed>();
generator.add::<PartialOfBothTypes>();
generator.add::<PartialNestedConfigs>();
generator.add::<PartialWithSerde>();
generator.add::<PartialWithComments>();
generator.add::<PartialUntagged>();
generator.add::<PartialExternalTagged>();
generator.add::<PartialInternalTagged>();
generator.add::<PartialAdjacentTagged>();
generator
}
#[cfg(feature = "renderer_json_schema")]
#[test]
fn generates_json_schema() {
use starbase_sandbox::{assert_snapshot, create_empty_sandbox};
let sandbox = create_empty_sandbox();
let file = sandbox.path().join("schema.json");
let generator = create_gen();
generator
.generate(&file, schema::json_schema::JsonSchemaRenderer::default())
.unwrap();
assert!(file.exists());
assert_snapshot!(std::fs::read_to_string(file).unwrap());
}
#[cfg(feature = "renderer_typescript")]
#[test]
fn generates_typescript() {
use starbase_sandbox::{assert_snapshot, create_empty_sandbox};
let sandbox = create_empty_sandbox();
let file = sandbox.path().join("config.ts");
let generator = create_gen();
generator
.generate(&file, schema::typescript::TypeScriptRenderer::default())
.unwrap();
assert!(file.exists());
assert_snapshot!(std::fs::read_to_string(file).unwrap());
}
#[test]
fn untagged_enum_deserialize_error_shows_all_variants() {
let json = r#""invalid_string_value""#;
let result: Result<PartialUntagged, _> = serde_json::from_str(json);
assert!(result.is_err());
let error = result.unwrap_err().to_string();
assert!(
error.contains("failed to parse as any variant of PartialUntagged"),
"Error should contain 'failed to parse as any variant of PartialUntagged', got: {}",
error
);
assert!(
error.contains("unit"),
"Error should mention 'unit' variant, got: {}",
error
);
assert!(
error.contains("one-tuple"),
"Error should mention 'one-tuple' variant, got: {}",
error
);
assert!(
error.contains("bazzer"),
"Error should mention 'bazzer' variant (renamed), got: {}",
error
);
assert!(
error.contains("tuple-of-struct"),
"Error should mention 'tuple-of-struct' variant, got: {}",
error
);
}
#[test]
fn untagged_enum_deserialize_success() {
let json = r#"true"#;
let result: PartialUntagged = serde_json::from_str(json).unwrap();
assert!(matches!(result, PartialUntagged::OneTuple(true)));
let json = r#"[42, "hello"]"#;
let result: PartialUntagged = serde_json::from_str(json).unwrap();
assert!(matches!(result, PartialUntagged::TwoTuple(42, ref s) if s == "hello"));
let json = r#"{"foo": "test", "bar": 123}"#;
let result: PartialUntagged = serde_json::from_str(json).unwrap();
if let PartialUntagged::TupleOfStruct(config) = result {
assert_eq!(config.foo, Some("test".to_string()));
assert_eq!(config.bar, Some(123));
} else {
panic!("Expected Qux variant");
}
let json = r#"null"#;
let result: PartialUntagged = serde_json::from_str(json).unwrap();
assert!(matches!(result, PartialUntagged::Unit));
}