1#![doc = include_str!("../README.md")]
2mod error;
44#[cfg(test)]
45mod idempotency_test;
46#[cfg(test)]
47mod other_variant_test;
48mod parser;
49mod schema_error;
50mod schema_gen;
51mod schema_meta;
52mod schema_types;
53mod schema_validate;
54mod serializer;
55#[cfg(test)]
56mod tag_events_test;
57mod tracing_macros;
58#[cfg(test)]
59mod value_expr_test;
60
61pub use error::{RenderError, StyxError, StyxErrorKind};
62pub use facet_format::DeserializeError;
63pub use facet_format::SerializeError;
64pub use parser::StyxParser;
65pub use schema_error::{ValidationError, ValidationErrorKind, ValidationResult, ValidationWarning};
66pub use schema_gen::{GenerateSchema, schema_file_from_type, schema_from_type};
67pub use schema_meta::META_SCHEMA_SOURCE;
68pub use schema_types::*;
69pub use schema_validate::{Validator, validate, validate_as};
70pub use serializer::{
71 SerializeOptions, StyxSerializeError, StyxSerializer, peek_to_string, peek_to_string_expr,
72 peek_to_string_with_options, to_string, to_string_compact, to_string_with_options,
73};
74
75pub fn from_str<T>(input: &str) -> Result<T, DeserializeError<StyxError>>
97where
98 T: facet_core::Facet<'static>,
99{
100 use facet_format::FormatDeserializer;
101 let parser = StyxParser::new(input);
102 let mut de = FormatDeserializer::new_owned(parser);
103 de.deserialize_root()
104}
105
106pub fn from_str_borrowed<'input, 'facet, T>(
129 input: &'input str,
130) -> Result<T, DeserializeError<StyxError>>
131where
132 T: facet_core::Facet<'facet>,
133 'input: 'facet,
134{
135 use facet_format::FormatDeserializer;
136 let parser = StyxParser::new(input);
137 let mut de = FormatDeserializer::new(parser);
138 de.deserialize_root()
139}
140
141pub fn from_str_expr<T>(input: &str) -> Result<T, DeserializeError<StyxError>>
165where
166 T: facet_core::Facet<'static>,
167{
168 use facet_format::FormatDeserializer;
169 let parser = StyxParser::new_expr(input);
170 let mut de = FormatDeserializer::new_owned(parser);
171 de.deserialize_root()
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use facet::Facet;
178 use facet_testhelpers::test;
179
180 #[derive(Facet, Debug, PartialEq)]
181 struct Simple {
182 name: String,
183 value: i32,
184 }
185
186 #[derive(Facet, Debug, PartialEq)]
187 struct WithOptional {
188 required: String,
189 optional: Option<i32>,
190 }
191
192 #[derive(Facet, Debug, PartialEq)]
193 struct Nested {
194 inner: Simple,
195 }
196
197 #[test]
198 fn test_simple_struct() {
199 let input = "name hello\nvalue 42";
200 let result: Simple = from_str(input).unwrap();
201 assert_eq!(result.name, "hello");
202 assert_eq!(result.value, 42);
203 }
204
205 #[test]
206 fn test_quoted_string() {
207 let input = r#"name "hello world"
208value 123"#;
209 let result: Simple = from_str(input).unwrap();
210 assert_eq!(result.name, "hello world");
211 assert_eq!(result.value, 123);
212 }
213
214 #[test]
215 fn test_optional_present() {
216 let input = "required hello\noptional 42";
217 let result: WithOptional = from_str(input).unwrap();
218 assert_eq!(result.required, "hello");
219 assert_eq!(result.optional, Some(42));
220 }
221
222 #[test]
223 fn test_optional_absent() {
224 let input = "required hello";
225 let result: WithOptional = from_str(input).unwrap();
226 assert_eq!(result.required, "hello");
227 assert_eq!(result.optional, None);
228 }
229
230 #[test]
231 fn test_bool_values() {
232 #[derive(Facet, Debug, PartialEq)]
233 struct Flags {
234 enabled: bool,
235 debug: bool,
236 }
237
238 let input = "enabled true\ndebug false";
239 let result: Flags = from_str(input).unwrap();
240 assert!(result.enabled);
241 assert!(!result.debug);
242 }
243
244 #[test]
245 fn test_vec() {
246 #[derive(Facet, Debug, PartialEq)]
247 struct WithVec {
248 items: Vec<i32>,
249 }
250
251 let input = "items (1 2 3)";
252 let result: WithVec = from_str(input).unwrap();
253 assert_eq!(result.items, vec![1, 2, 3]);
254 }
255
256 #[test]
257 fn test_schema_directive_skipped() {
258 #[derive(Facet, Debug, PartialEq)]
261 struct Config {
262 name: String,
263 port: u16,
264 }
265
266 let input = r#"@schema {source crate:test@1, cli test}
267
268name myapp
269port 8080"#;
270 let result: Config = from_str(input).unwrap();
271 assert_eq!(result.name, "myapp");
272 assert_eq!(result.port, 8080);
273 }
274
275 #[test]
280 fn test_from_str_expr_scalar() {
281 let num: i32 = from_str_expr("42").unwrap();
282 assert_eq!(num, 42);
283
284 let s: String = from_str_expr("hello").unwrap();
285 assert_eq!(s, "hello");
286
287 let b: bool = from_str_expr("true").unwrap();
288 assert!(b);
289 }
290
291 #[test]
292 fn test_from_str_expr_object() {
293 #[derive(Facet, Debug, PartialEq)]
294 struct Point {
295 x: i32,
296 y: i32,
297 }
298
299 let point: Point = from_str_expr("{x 10, y 20}").unwrap();
300 assert_eq!(point.x, 10);
301 assert_eq!(point.y, 20);
302 }
303
304 #[test]
305 fn test_from_str_expr_sequence() {
306 let items: Vec<i32> = from_str_expr("(1 2 3)").unwrap();
307 assert_eq!(items, vec![1, 2, 3]);
308 }
309
310 #[test]
311 fn test_expr_roundtrip() {
312 #[derive(Facet, Debug, PartialEq)]
314 struct Config {
315 name: String,
316 port: u16,
317 }
318
319 let original = Config {
320 name: "test".into(),
321 port: 8080,
322 };
323
324 let serialized = to_string_compact(&original).unwrap();
326 assert!(serialized.starts_with('{'));
327
328 let parsed: Config = from_str_expr(&serialized).unwrap();
330 assert_eq!(original, parsed);
331 }
332
333 #[test]
338 fn test_documented_basic() {
339 let shape = <Documented<String>>::SHAPE;
341 assert!(shape.is_metadata_container());
342 }
343
344 #[test]
345 fn test_documented_helper_methods() {
346 let doc = Documented::new(42);
347 assert_eq!(*doc.value(), 42);
348 assert!(doc.doc().is_none());
349
350 let doc = Documented::with_doc(42, vec!["The answer".into()]);
351 assert_eq!(*doc.value(), 42);
352 assert_eq!(doc.doc(), Some(&["The answer".to_string()][..]));
353
354 let doc = Documented::with_doc_line(42, "The answer");
355 assert_eq!(doc.doc(), Some(&["The answer".to_string()][..]));
356 }
357
358 #[test]
359 fn test_documented_deref() {
360 let doc = Documented::new("hello".to_string());
361 assert_eq!(doc.len(), 5);
363 assert!(doc.starts_with("hel"));
364 }
365
366 #[test]
367 fn test_documented_from() {
368 let doc: Documented<i32> = 42.into();
369 assert_eq!(*doc.value(), 42);
370 assert!(doc.doc().is_none());
371 }
372
373 #[test]
374 fn test_documented_map() {
375 let doc = Documented::with_doc_line(42, "The answer");
376 let mapped = doc.map(|x| x.to_string());
377 assert_eq!(*mapped.value(), "42");
378 assert_eq!(mapped.doc(), Some(&["The answer".to_string()][..]));
379 }
380
381 #[test]
382 fn test_unit_field_followed_by_another_field() {
383 use std::collections::HashMap;
386
387 #[derive(Facet, Debug, PartialEq)]
388 struct Fields {
389 #[facet(flatten)]
390 fields: HashMap<String, Option<String>>,
391 }
392
393 let input = "foo\nbar baz";
394 let result: Fields = from_str(input).unwrap();
395
396 assert_eq!(result.fields.len(), 2);
397 assert_eq!(result.fields.get("foo"), Some(&None));
398 assert_eq!(result.fields.get("bar"), Some(&Some("baz".to_string())));
399 }
400
401 #[test]
402 fn test_map_schema_spacing() {
403 use crate::schema_types::{Documented, EnumSchema, MapSchema, Schema};
407 use std::collections::HashMap;
408
409 let mut enum_variants = HashMap::new();
410 enum_variants.insert(Documented::new("a".to_string()), Schema::Unit);
411 enum_variants.insert(Documented::new("b".to_string()), Schema::Unit);
412
413 let map_schema = Schema::Map(MapSchema(vec![
414 Documented::new(Schema::String(None)), Documented::new(Schema::Enum(EnumSchema(enum_variants))), ]));
417
418 let output = to_string(&map_schema).unwrap();
419
420 assert!(
422 output.contains("@string @enum"),
423 "Expected space between @string and @enum, got: {}",
424 output
425 );
426 }
427}