avro_rs/
lib.rs

1//! A library for working with [Apache Avro](https://avro.apache.org/) in Rust.
2//!
3//! Please check our [documentation](https://docs.rs/avro-rs) for examples, tutorials and API reference.
4//!
5//! **[Apache Avro](https://avro.apache.org/)** is a data serialization system which provides rich
6//! data structures and a compact, fast, binary data format.
7//!
8//! All data in Avro is schematized, as in the following example:
9//!
10//! ```text
11//! {
12//!     "type": "record",
13//!     "name": "test",
14//!     "fields": [
15//!         {"name": "a", "type": "long", "default": 42},
16//!         {"name": "b", "type": "string"}
17//!     ]
18//! }
19//! ```
20//!
21//! There are basically two ways of handling Avro data in Rust:
22//!
23//! * **as Avro-specialized data types** based on an Avro schema;
24//! * **as generic Rust serde-compatible types** implementing/deriving `Serialize` and
25//! `Deserialize`;
26//!
27//! **avro-rs** provides a way to read and write both these data representations easily and
28//! efficiently.
29//!
30//! # Installing the library
31//!
32//!
33//! Add to your `Cargo.toml`:
34//!
35//! ```toml
36//! [dependencies]
37//! avro-rs = "x.y"
38//! ```
39//!
40//! Or in case you want to leverage the **Snappy** codec:
41//!
42//! ```toml
43//! [dependencies.avro-rs]
44//! version = "x.y"
45//! features = ["snappy"]
46//! ```
47//!
48//! # Upgrading to a newer minor version
49//!
50//! The library is still in beta, so there might be backward-incompatible changes between minor
51//! versions. If you have troubles upgrading, check the [version upgrade guide](migration_guide.md).
52//!
53//! # Defining a schema
54//!
55//! An Avro data cannot exist without an Avro schema. Schemas **must** be used while writing and
56//! **can** be used while reading and they carry the information regarding the type of data we are
57//! handling. Avro schemas are used for both schema validation and resolution of Avro data.
58//!
59//! Avro schemas are defined in **JSON** format and can just be parsed out of a raw string:
60//!
61//! ```
62//! use avro_rs::Schema;
63//!
64//! let raw_schema = r#"
65//!     {
66//!         "type": "record",
67//!         "name": "test",
68//!         "fields": [
69//!             {"name": "a", "type": "long", "default": 42},
70//!             {"name": "b", "type": "string"}
71//!         ]
72//!     }
73//! "#;
74//!
75//! // if the schema is not valid, this function will return an error
76//! let schema = Schema::parse_str(raw_schema).unwrap();
77//!
78//! // schemas can be printed for debugging
79//! println!("{:?}", schema);
80//! ```
81//!
82//! Additionally, a list of of definitions (which may depend on each other) can be given and all of
83//! them will be parsed into the corresponding schemas.
84//!
85//! ```
86//! use avro_rs::Schema;
87//!
88//! let raw_schema_1 = r#"{
89//!         "name": "A",
90//!         "type": "record",
91//!         "fields": [
92//!             {"name": "field_one", "type": "float"}
93//!         ]
94//!     }"#;
95//!
96//! // This definition depends on the definition of A above
97//! let raw_schema_2 = r#"{
98//!         "name": "B",
99//!         "type": "record",
100//!         "fields": [
101//!             {"name": "field_one", "type": "A"}
102//!         ]
103//!     }"#;
104//!
105//! // if the schemas are not valid, this function will return an error
106//! let schemas = Schema::parse_list(&[raw_schema_1, raw_schema_2]).unwrap();
107//!
108//! // schemas can be printed for debugging
109//! println!("{:?}", schemas);
110//! ```
111//! *N.B.* It is important to note that the composition of schema definitions requires schemas with names.
112//! For this reason, only schemas of type Record, Enum, and Fixed should be input into this function.
113//!
114//! The library provides also a programmatic interface to define schemas without encoding them in
115//! JSON (for advanced use), but we highly recommend the JSON interface. Please read the API
116//! reference in case you are interested.
117//!
118//! For more information about schemas and what kind of information you can encapsulate in them,
119//! please refer to the appropriate section of the
120//! [Avro Specification](https://avro.apache.org/docs/current/spec.html#schemas).
121//!
122//! # Writing data
123//!
124//! Once we have defined a schema, we are ready to serialize data in Avro, validating them against
125//! the provided schema in the process. As mentioned before, there are two ways of handling Avro
126//! data in Rust.
127//!
128//! **NOTE:** The library also provides a low-level interface for encoding a single datum in Avro
129//! bytecode without generating markers and headers (for advanced use), but we highly recommend the
130//! `Writer` interface to be totally Avro-compatible. Please read the API reference in case you are
131//! interested.
132//!
133//! ## The avro way
134//!
135//! Given that the schema we defined above is that of an Avro *Record*, we are going to use the
136//! associated type provided by the library to specify the data we want to serialize:
137//!
138//! ```
139//! # use avro_rs::Schema;
140//! use avro_rs::types::Record;
141//! use avro_rs::Writer;
142//! #
143//! # let raw_schema = r#"
144//! #     {
145//! #         "type": "record",
146//! #         "name": "test",
147//! #         "fields": [
148//! #             {"name": "a", "type": "long", "default": 42},
149//! #             {"name": "b", "type": "string"}
150//! #         ]
151//! #     }
152//! # "#;
153//! # let schema = Schema::parse_str(raw_schema).unwrap();
154//! // a writer needs a schema and something to write to
155//! let mut writer = Writer::new(&schema, Vec::new());
156//!
157//! // the Record type models our Record schema
158//! let mut record = Record::new(writer.schema()).unwrap();
159//! record.put("a", 27i64);
160//! record.put("b", "foo");
161//!
162//! // schema validation happens here
163//! writer.append(record).unwrap();
164//!
165//! // this is how to get back the resulting avro bytecode
166//! // this performs a flush operation to make sure data has been written, so it can fail
167//! // you can also call `writer.flush()` yourself without consuming the writer
168//! let encoded = writer.into_inner().unwrap();
169//! ```
170//!
171//! The vast majority of the times, schemas tend to define a record as a top-level container
172//! encapsulating all the values to convert as fields and providing documentation for them, but in
173//! case we want to directly define an Avro value, the library offers that capability via the
174//! `Value` interface.
175//!
176//! ```
177//! use avro_rs::types::Value;
178//!
179//! let mut value = Value::String("foo".to_string());
180//! ```
181//!
182//! ## The serde way
183//!
184//! Given that the schema we defined above is an Avro *Record*, we can directly use a Rust struct
185//! deriving `Serialize` to model our data:
186//!
187//! ```
188//! # use avro_rs::Schema;
189//! # use serde::Serialize;
190//! use avro_rs::Writer;
191//!
192//! #[derive(Debug, Serialize)]
193//! struct Test {
194//!     a: i64,
195//!     b: String,
196//! }
197//!
198//! # let raw_schema = r#"
199//! #     {
200//! #         "type": "record",
201//! #         "name": "test",
202//! #         "fields": [
203//! #             {"name": "a", "type": "long", "default": 42},
204//! #             {"name": "b", "type": "string"}
205//! #         ]
206//! #     }
207//! # "#;
208//! # let schema = Schema::parse_str(raw_schema).unwrap();
209//! // a writer needs a schema and something to write to
210//! let mut writer = Writer::new(&schema, Vec::new());
211//!
212//! // the structure models our Record schema
213//! let test = Test {
214//!     a: 27,
215//!     b: "foo".to_owned(),
216//! };
217//!
218//! // schema validation happens here
219//! writer.append_ser(test).unwrap();
220//!
221//! // this is how to get back the resulting avro bytecode
222//! // this performs a flush operation to make sure data is written, so it can fail
223//! // you can also call `writer.flush()` yourself without consuming the writer
224//! let encoded = writer.into_inner();
225//! ```
226//!
227//! The vast majority of the times, schemas tend to define a record as a top-level container
228//! encapsulating all the values to convert as fields and providing documentation for them, but in
229//! case we want to directly define an Avro value, any type implementing `Serialize` should work.
230//!
231//! ```
232//! let mut value = "foo".to_string();
233//! ```
234//!
235//! ## Using codecs to compress data
236//!
237//! Avro supports three different compression codecs when encoding data:
238//!
239//! * **Null**: leaves data uncompressed;
240//! * **Deflate**: writes the data block using the deflate algorithm as specified in RFC 1951, and
241//! typically implemented using the zlib library. Note that this format (unlike the "zlib format" in
242//! RFC 1950) does not have a checksum.
243//! * **Snappy**: uses Google's [Snappy](http://google.github.io/snappy/) compression library. Each
244//! compressed block is followed by the 4-byte, big-endianCRC32 checksum of the uncompressed data in
245//! the block. You must enable the `snappy` feature to use this codec.
246//!
247//! To specify a codec to use to compress data, just specify it while creating a `Writer`:
248//! ```
249//! # use avro_rs::Schema;
250//! use avro_rs::Writer;
251//! use avro_rs::Codec;
252//! #
253//! # let raw_schema = r#"
254//! #     {
255//! #         "type": "record",
256//! #         "name": "test",
257//! #         "fields": [
258//! #             {"name": "a", "type": "long", "default": 42},
259//! #             {"name": "b", "type": "string"}
260//! #         ]
261//! #     }
262//! # "#;
263//! # let schema = Schema::parse_str(raw_schema).unwrap();
264//! let mut writer = Writer::with_codec(&schema, Vec::new(), Codec::Deflate);
265//! ```
266//!
267//! # Reading data
268//!
269//! As far as reading Avro encoded data goes, we can just use the schema encoded with the data to
270//! read them. The library will do it automatically for us, as it already does for the compression
271//! codec:
272//!
273//! ```
274//! use avro_rs::Reader;
275//! # use avro_rs::Schema;
276//! # use avro_rs::types::Record;
277//! # use avro_rs::Writer;
278//! #
279//! # let raw_schema = r#"
280//! #     {
281//! #         "type": "record",
282//! #         "name": "test",
283//! #         "fields": [
284//! #             {"name": "a", "type": "long", "default": 42},
285//! #             {"name": "b", "type": "string"}
286//! #         ]
287//! #     }
288//! # "#;
289//! # let schema = Schema::parse_str(raw_schema).unwrap();
290//! # let mut writer = Writer::new(&schema, Vec::new());
291//! # let mut record = Record::new(writer.schema()).unwrap();
292//! # record.put("a", 27i64);
293//! # record.put("b", "foo");
294//! # writer.append(record).unwrap();
295//! # let input = writer.into_inner().unwrap();
296//! // reader creation can fail in case the input to read from is not Avro-compatible or malformed
297//! let reader = Reader::new(&input[..]).unwrap();
298//! ```
299//!
300//! In case, instead, we want to specify a different (but compatible) reader schema from the schema
301//! the data has been written with, we can just do as the following:
302//! ```
303//! use avro_rs::Schema;
304//! use avro_rs::Reader;
305//! # use avro_rs::types::Record;
306//! # use avro_rs::Writer;
307//! #
308//! # let writer_raw_schema = r#"
309//! #     {
310//! #         "type": "record",
311//! #         "name": "test",
312//! #         "fields": [
313//! #             {"name": "a", "type": "long", "default": 42},
314//! #             {"name": "b", "type": "string"}
315//! #         ]
316//! #     }
317//! # "#;
318//! # let writer_schema = Schema::parse_str(writer_raw_schema).unwrap();
319//! # let mut writer = Writer::new(&writer_schema, Vec::new());
320//! # let mut record = Record::new(writer.schema()).unwrap();
321//! # record.put("a", 27i64);
322//! # record.put("b", "foo");
323//! # writer.append(record).unwrap();
324//! # let input = writer.into_inner().unwrap();
325//!
326//! let reader_raw_schema = r#"
327//!     {
328//!         "type": "record",
329//!         "name": "test",
330//!         "fields": [
331//!             {"name": "a", "type": "long", "default": 42},
332//!             {"name": "b", "type": "string"},
333//!             {"name": "c", "type": "long", "default": 43}
334//!         ]
335//!     }
336//! "#;
337//!
338//! let reader_schema = Schema::parse_str(reader_raw_schema).unwrap();
339//!
340//! // reader creation can fail in case the input to read from is not Avro-compatible or malformed
341//! let reader = Reader::with_schema(&reader_schema, &input[..]).unwrap();
342//! ```
343//!
344//! The library will also automatically perform schema resolution while reading the data.
345//!
346//! For more information about schema compatibility and resolution, please refer to the
347//! [Avro Specification](https://avro.apache.org/docs/current/spec.html#schemas).
348//!
349//! As usual, there are two ways to handle Avro data in Rust, as you can see below.
350//!
351//! **NOTE:** The library also provides a low-level interface for decoding a single datum in Avro
352//! bytecode without markers and header (for advanced use), but we highly recommend the `Reader`
353//! interface to leverage all Avro features. Please read the API reference in case you are
354//! interested.
355//!
356//!
357//! ## The avro way
358//!
359//! We can just read directly instances of `Value` out of the `Reader` iterator:
360//!
361//! ```
362//! # use avro_rs::Schema;
363//! # use avro_rs::types::Record;
364//! # use avro_rs::Writer;
365//! use avro_rs::Reader;
366//! #
367//! # let raw_schema = r#"
368//! #     {
369//! #         "type": "record",
370//! #         "name": "test",
371//! #         "fields": [
372//! #             {"name": "a", "type": "long", "default": 42},
373//! #             {"name": "b", "type": "string"}
374//! #         ]
375//! #     }
376//! # "#;
377//! # let schema = Schema::parse_str(raw_schema).unwrap();
378//! # let schema = Schema::parse_str(raw_schema).unwrap();
379//! # let mut writer = Writer::new(&schema, Vec::new());
380//! # let mut record = Record::new(writer.schema()).unwrap();
381//! # record.put("a", 27i64);
382//! # record.put("b", "foo");
383//! # writer.append(record).unwrap();
384//! # let input = writer.into_inner().unwrap();
385//! let reader = Reader::new(&input[..]).unwrap();
386//!
387//! // value is a Result  of an Avro Value in case the read operation fails
388//! for value in reader {
389//!     println!("{:?}", value.unwrap());
390//! }
391//!
392//! ```
393//!
394//! ## The serde way
395//!
396//! Alternatively, we can use a Rust type implementing `Deserialize` and representing our schema to
397//! read the data into:
398//!
399//! ```
400//! # use avro_rs::Schema;
401//! # use avro_rs::Writer;
402//! # use serde::{Deserialize, Serialize};
403//! use avro_rs::Reader;
404//! use avro_rs::from_value;
405//!
406//! # #[derive(Serialize)]
407//! #[derive(Debug, Deserialize)]
408//! struct Test {
409//!     a: i64,
410//!     b: String,
411//! }
412//!
413//! # let raw_schema = r#"
414//! #     {
415//! #         "type": "record",
416//! #         "name": "test",
417//! #         "fields": [
418//! #             {"name": "a", "type": "long", "default": 42},
419//! #             {"name": "b", "type": "string"}
420//! #         ]
421//! #     }
422//! # "#;
423//! # let schema = Schema::parse_str(raw_schema).unwrap();
424//! # let mut writer = Writer::new(&schema, Vec::new());
425//! # let test = Test {
426//! #     a: 27,
427//! #     b: "foo".to_owned(),
428//! # };
429//! # writer.append_ser(test).unwrap();
430//! # let input = writer.into_inner().unwrap();
431//! let reader = Reader::new(&input[..]).unwrap();
432//!
433//! // value is a Result in case the read operation fails
434//! for value in reader {
435//!     println!("{:?}", from_value::<Test>(&value.unwrap()));
436//! }
437//! ```
438//!
439//! # Putting everything together
440//!
441//! The following is an example of how to combine everything showed so far and it is meant to be a
442//! quick reference of the library interface:
443//!
444//! ```
445//! use avro_rs::{Codec, Reader, Schema, Writer, from_value, types::Record, Error};
446//! use serde::{Deserialize, Serialize};
447//!
448//! #[derive(Debug, Deserialize, Serialize)]
449//! struct Test {
450//!     a: i64,
451//!     b: String,
452//! }
453//!
454//! fn main() -> Result<(), Error> {
455//!     let raw_schema = r#"
456//!         {
457//!             "type": "record",
458//!             "name": "test",
459//!             "fields": [
460//!                 {"name": "a", "type": "long", "default": 42},
461//!                 {"name": "b", "type": "string"}
462//!             ]
463//!         }
464//!     "#;
465//!
466//!     let schema = Schema::parse_str(raw_schema)?;
467//!
468//!     println!("{:?}", schema);
469//!
470//!     let mut writer = Writer::with_codec(&schema, Vec::new(), Codec::Deflate);
471//!
472//!     let mut record = Record::new(writer.schema()).unwrap();
473//!     record.put("a", 27i64);
474//!     record.put("b", "foo");
475//!
476//!     writer.append(record)?;
477//!
478//!     let test = Test {
479//!         a: 27,
480//!         b: "foo".to_owned(),
481//!     };
482//!
483//!     writer.append_ser(test)?;
484//!
485//!     let input = writer.into_inner()?;
486//!     let reader = Reader::with_schema(&schema, &input[..])?;
487//!
488//!     for record in reader {
489//!         println!("{:?}", from_value::<Test>(&record?));
490//!     }
491//!     Ok(())
492//! }
493//! ```
494//!
495//! `avro-rs` also supports the logical types listed in the [Avro specification](https://avro.apache.org/docs/current/spec.html#Logical+Types):
496//!
497//! 1. `Decimal` using the [`num_bigint`](https://docs.rs/num-bigint/0.2.6/num_bigint) crate
498//! 1. UUID using the [`uuid`](https://docs.rs/uuid/0.8.1/uuid) crate
499//! 1. Date, Time (milli) as `i32` and Time (micro) as `i64`
500//! 1. Timestamp (milli and micro) as `i64`
501//! 1. Duration as a custom type with `months`, `days` and `millis` accessor methods each of which returns an `i32`
502//!
503//! Note that the on-disk representation is identical to the underlying primitive/complex type.
504//!
505//! ### Read and write logical types
506//!
507//! ```rust
508//! use avro_rs::{
509//!     types::Record, types::Value, Codec, Days, Decimal, Duration, Millis, Months, Reader, Schema,
510//!     Writer, Error,
511//! };
512//! use num_bigint::ToBigInt;
513//!
514//! fn main() -> Result<(), Error> {
515//!     let raw_schema = r#"
516//!     {
517//!       "type": "record",
518//!       "name": "test",
519//!       "fields": [
520//!         {
521//!           "name": "decimal_fixed",
522//!           "type": {
523//!             "type": "fixed",
524//!             "size": 2,
525//!             "name": "decimal"
526//!           },
527//!           "logicalType": "decimal",
528//!           "precision": 4,
529//!           "scale": 2
530//!         },
531//!         {
532//!           "name": "decimal_var",
533//!           "type": "bytes",
534//!           "logicalType": "decimal",
535//!           "precision": 10,
536//!           "scale": 3
537//!         },
538//!         {
539//!           "name": "uuid",
540//!           "type": "string",
541//!           "logicalType": "uuid"
542//!         },
543//!         {
544//!           "name": "date",
545//!           "type": "int",
546//!           "logicalType": "date"
547//!         },
548//!         {
549//!           "name": "time_millis",
550//!           "type": "int",
551//!           "logicalType": "time-millis"
552//!         },
553//!         {
554//!           "name": "time_micros",
555//!           "type": "long",
556//!           "logicalType": "time-micros"
557//!         },
558//!         {
559//!           "name": "timestamp_millis",
560//!           "type": "long",
561//!           "logicalType": "timestamp-millis"
562//!         },
563//!         {
564//!           "name": "timestamp_micros",
565//!           "type": "long",
566//!           "logicalType": "timestamp-micros"
567//!         },
568//!         {
569//!           "name": "duration",
570//!           "type": {
571//!             "type": "fixed",
572//!             "size": 12,
573//!             "name": "duration"
574//!           },
575//!           "logicalType": "duration"
576//!         }
577//!       ]
578//!     }
579//!     "#;
580//!
581//!     let schema = Schema::parse_str(raw_schema)?;
582//!
583//!     println!("{:?}", schema);
584//!
585//!     let mut writer = Writer::with_codec(&schema, Vec::new(), Codec::Deflate);
586//!
587//!     let mut record = Record::new(writer.schema()).unwrap();
588//!     record.put("decimal_fixed", Decimal::from(9936.to_bigint().unwrap().to_signed_bytes_be()));
589//!     record.put("decimal_var", Decimal::from((-32442.to_bigint().unwrap()).to_signed_bytes_be()));
590//!     record.put("uuid", uuid::Uuid::new_v4());
591//!     record.put("date", Value::Date(1));
592//!     record.put("time_millis", Value::TimeMillis(2));
593//!     record.put("time_micros", Value::TimeMicros(3));
594//!     record.put("timestamp_millis", Value::TimestampMillis(4));
595//!     record.put("timestamp_micros", Value::TimestampMicros(5));
596//!     record.put("duration", Duration::new(Months::new(6), Days::new(7), Millis::new(8)));
597//!
598//!     writer.append(record)?;
599//!
600//!     let input = writer.into_inner()?;
601//!     let reader = Reader::with_schema(&schema, &input[..])?;
602//!
603//!     for record in reader {
604//!         println!("{:?}", record?);
605//!     }
606//!     Ok(())
607//! }
608//! ```
609//!
610//! ## Calculate Avro schema fingerprint
611//!
612//! This library supports calculating the following fingerprints:
613//!
614//!  - SHA-256
615//!  - MD5
616//!  - Rabin
617//!
618//! An example of fingerprinting for the supported fingerprints:
619//!
620//! ```rust
621//! use avro_rs::rabin::Rabin;
622//! use avro_rs::{Schema, Error};
623//! use md5::Md5;
624//! use sha2::Sha256;
625//!
626//! fn main() -> Result<(), Error> {
627//!     let raw_schema = r#"
628//!         {
629//!             "type": "record",
630//!             "name": "test",
631//!             "fields": [
632//!                 {"name": "a", "type": "long", "default": 42},
633//!                 {"name": "b", "type": "string"}
634//!             ]
635//!         }
636//!     "#;
637//!     let schema = Schema::parse_str(raw_schema)?;
638//!     println!("{}", schema.fingerprint::<Sha256>());
639//!     println!("{}", schema.fingerprint::<Md5>());
640//!     println!("{}", schema.fingerprint::<Rabin>());
641//!     Ok(())
642//! }
643//! ```
644//!
645//! ## Ill-formed data
646//!
647//! In order to ease decoding, the Binary Encoding specification of Avro data
648//! requires some fields to have their length encoded alongside the data.
649//!
650//! If encoded data passed to a `Reader` has been ill-formed, it can happen that
651//! the bytes meant to contain the length of data are bogus and could result
652//! in extravagant memory allocation.
653//!
654//! To shield users from ill-formed data, `avro-rs` sets a limit (default: 512MB)
655//! to any allocation it will perform when decoding data.
656//!
657//! If you expect some of your data fields to be larger than this limit, be sure
658//! to make use of the `max_allocation_bytes` function before reading **any** data
659//! (we leverage Rust's [`std::sync::Once`](https://doc.rust-lang.org/std/sync/struct.Once.html)
660//! mechanism to initialize this value, if
661//! any call to decode is made before a call to `max_allocation_bytes`, the limit
662//! will be 512MB throughout the lifetime of the program).
663//!
664//!
665//! ```rust
666//! use avro_rs::max_allocation_bytes;
667//!
668//! max_allocation_bytes(2 * 1024 * 1024 * 1024);  // 2GB
669//!
670//! // ... happily decode large data
671//!
672//! ```
673//!
674//! ## Check schemas compatibility
675//!
676//! This library supports checking for schemas compatibility.
677//!
678//! Note: It does not yet support named schemas (more on
679//! https://github.com/flavray/avro-rs/pull/76).
680//!
681//! Examples of checking for compatibility:
682//!
683//! 1. Compatible schemas
684//!
685//! Explanation: an int array schema can be read by a long array schema- an int
686//! (32bit signed integer) fits into a long (64bit signed integer)
687//!
688//! ```rust
689//! use avro_rs::{Schema, schema_compatibility::SchemaCompatibility};
690//!
691//! let writers_schema = Schema::parse_str(r#"{"type": "array", "items":"int"}"#).unwrap();
692//! let readers_schema = Schema::parse_str(r#"{"type": "array", "items":"long"}"#).unwrap();
693//! assert_eq!(true, SchemaCompatibility::can_read(&writers_schema, &readers_schema));
694//! ```
695//!
696//! 2. Incompatible schemas (a long array schema cannot be read by an int array schema)
697//!
698//! Explanation: a long array schema cannot be read by an int array schema- a
699//! long (64bit signed integer) does not fit into an int (32bit signed integer)
700//!
701//! ```rust
702//! use avro_rs::{Schema, schema_compatibility::SchemaCompatibility};
703//!
704//! let writers_schema = Schema::parse_str(r#"{"type": "array", "items":"long"}"#).unwrap();
705//! let readers_schema = Schema::parse_str(r#"{"type": "array", "items":"int"}"#).unwrap();
706//! assert_eq!(false, SchemaCompatibility::can_read(&writers_schema, &readers_schema));
707//! ```
708
709mod codec;
710mod de;
711mod decimal;
712mod decode;
713mod duration;
714mod encode;
715mod error;
716mod reader;
717mod ser;
718mod util;
719mod writer;
720
721pub mod rabin;
722pub mod schema;
723pub mod schema_compatibility;
724pub mod types;
725
726pub use codec::Codec;
727pub use de::from_value;
728pub use decimal::Decimal;
729pub use duration::{Days, Duration, Millis, Months};
730pub use error::{Error, Error as DeError, Error as SerError};
731pub use reader::{from_avro_datum, Reader};
732pub use schema::Schema;
733pub use ser::to_value;
734pub use util::max_allocation_bytes;
735pub use writer::{to_avro_datum, Writer};
736
737/// A convenience type alias for `Result`s with `Error`s.
738pub type AvroResult<T> = Result<T, Error>;
739
740#[cfg(test)]
741mod tests {
742    use crate::{
743        from_avro_datum,
744        types::{Record, Value},
745        Codec, Reader, Schema, Writer,
746    };
747
748    //TODO: move where it fits better
749    #[test]
750    fn test_enum_default() {
751        let writer_raw_schema = r#"
752            {
753                "type": "record",
754                "name": "test",
755                "fields": [
756                    {"name": "a", "type": "long", "default": 42},
757                    {"name": "b", "type": "string"}
758                ]
759            }
760        "#;
761        let reader_raw_schema = r#"
762            {
763                "type": "record",
764                "name": "test",
765                "fields": [
766                    {"name": "a", "type": "long", "default": 42},
767                    {"name": "b", "type": "string"},
768                    {
769                        "name": "c",
770                        "type": {
771                            "type": "enum",
772                            "name": "suit",
773                            "symbols": ["diamonds", "spades", "clubs", "hearts"]
774                        },
775                        "default": "spades"
776                    }
777                ]
778            }
779        "#;
780        let writer_schema = Schema::parse_str(writer_raw_schema).unwrap();
781        let reader_schema = Schema::parse_str(reader_raw_schema).unwrap();
782        let mut writer = Writer::with_codec(&writer_schema, Vec::new(), Codec::Null);
783        let mut record = Record::new(writer.schema()).unwrap();
784        record.put("a", 27i64);
785        record.put("b", "foo");
786        writer.append(record).unwrap();
787        let input = writer.into_inner().unwrap();
788        let mut reader = Reader::with_schema(&reader_schema, &input[..]).unwrap();
789        assert_eq!(
790            reader.next().unwrap().unwrap(),
791            Value::Record(vec![
792                ("a".to_string(), Value::Long(27)),
793                ("b".to_string(), Value::String("foo".to_string())),
794                ("c".to_string(), Value::Enum(1, "spades".to_string())),
795            ])
796        );
797        assert!(reader.next().is_none());
798    }
799
800    //TODO: move where it fits better
801    #[test]
802    fn test_enum_string_value() {
803        let raw_schema = r#"
804            {
805                "type": "record",
806                "name": "test",
807                "fields": [
808                    {"name": "a", "type": "long", "default": 42},
809                    {"name": "b", "type": "string"},
810                    {
811                        "name": "c",
812                        "type": {
813                            "type": "enum",
814                            "name": "suit",
815                            "symbols": ["diamonds", "spades", "clubs", "hearts"]
816                        },
817                        "default": "spades"
818                    }
819                ]
820            }
821        "#;
822        let schema = Schema::parse_str(raw_schema).unwrap();
823        let mut writer = Writer::with_codec(&schema, Vec::new(), Codec::Null);
824        let mut record = Record::new(writer.schema()).unwrap();
825        record.put("a", 27i64);
826        record.put("b", "foo");
827        record.put("c", "clubs");
828        writer.append(record).unwrap();
829        let input = writer.into_inner().unwrap();
830        let mut reader = Reader::with_schema(&schema, &input[..]).unwrap();
831        assert_eq!(
832            reader.next().unwrap().unwrap(),
833            Value::Record(vec![
834                ("a".to_string(), Value::Long(27)),
835                ("b".to_string(), Value::String("foo".to_string())),
836                ("c".to_string(), Value::Enum(2, "clubs".to_string())),
837            ])
838        );
839        assert!(reader.next().is_none());
840    }
841
842    //TODO: move where it fits better
843    #[test]
844    fn test_enum_resolution() {
845        let writer_raw_schema = r#"
846            {
847                "type": "record",
848                "name": "test",
849                "fields": [
850                    {"name": "a", "type": "long", "default": 42},
851                    {"name": "b", "type": "string"},
852                    {
853                        "name": "c",
854                        "type": {
855                            "type": "enum",
856                            "name": "suit",
857                            "symbols": ["diamonds", "spades", "clubs", "hearts"]
858                        },
859                        "default": "spades"
860                    }
861                ]
862            }
863        "#;
864        let reader_raw_schema = r#"
865            {
866                "type": "record",
867                "name": "test",
868                "fields": [
869                    {"name": "a", "type": "long", "default": 42},
870                    {"name": "b", "type": "string"},
871                    {
872                        "name": "c",
873                        "type": {
874                            "type": "enum",
875                            "name": "suit",
876                            "symbols": ["diamonds", "spades", "ninja", "hearts"]
877                        },
878                        "default": "spades"
879                    }
880                ]
881            }
882        "#;
883        let writer_schema = Schema::parse_str(writer_raw_schema).unwrap();
884        let reader_schema = Schema::parse_str(reader_raw_schema).unwrap();
885        let mut writer = Writer::with_codec(&writer_schema, Vec::new(), Codec::Null);
886        let mut record = Record::new(writer.schema()).unwrap();
887        record.put("a", 27i64);
888        record.put("b", "foo");
889        record.put("c", "clubs");
890        writer.append(record).unwrap();
891        let input = writer.into_inner().unwrap();
892        let mut reader = Reader::with_schema(&reader_schema, &input[..]).unwrap();
893        assert!(reader.next().unwrap().is_err());
894        assert!(reader.next().is_none());
895    }
896
897    //TODO: move where it fits better
898    #[test]
899    fn test_enum_no_reader_schema() {
900        let writer_raw_schema = r#"
901            {
902                "type": "record",
903                "name": "test",
904                "fields": [
905                    {"name": "a", "type": "long", "default": 42},
906                    {"name": "b", "type": "string"},
907                    {
908                        "name": "c",
909                        "type": {
910                            "type": "enum",
911                            "name": "suit",
912                            "symbols": ["diamonds", "spades", "clubs", "hearts"]
913                        },
914                        "default": "spades"
915                    }
916                ]
917            }
918        "#;
919        let writer_schema = Schema::parse_str(writer_raw_schema).unwrap();
920        let mut writer = Writer::with_codec(&writer_schema, Vec::new(), Codec::Null);
921        let mut record = Record::new(writer.schema()).unwrap();
922        record.put("a", 27i64);
923        record.put("b", "foo");
924        record.put("c", "clubs");
925        writer.append(record).unwrap();
926        let input = writer.into_inner().unwrap();
927        let mut reader = Reader::new(&input[..]).unwrap();
928        assert_eq!(
929            reader.next().unwrap().unwrap(),
930            Value::Record(vec![
931                ("a".to_string(), Value::Long(27)),
932                ("b".to_string(), Value::String("foo".to_string())),
933                ("c".to_string(), Value::Enum(2, "clubs".to_string())),
934            ])
935        );
936    }
937
938    #[test]
939    fn test_illformed_length() {
940        let raw_schema = r#"
941            {
942                "type": "record",
943                "name": "test",
944                "fields": [
945                    {"name": "a", "type": "long", "default": 42},
946                    {"name": "b", "type": "string"}
947                ]
948            }
949        "#;
950
951        let schema = Schema::parse_str(raw_schema).unwrap();
952
953        // Would allocated 18446744073709551605 bytes
954        let illformed: &[u8] = &[0x3e, 0x15, 0xff, 0x1f, 0x15, 0xff];
955
956        let value = from_avro_datum(&schema, &mut &illformed[..], None);
957        assert!(value.is_err());
958    }
959}