pub mod error;
mod templates;
use std::collections::{HashMap, VecDeque};
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use avro_rs::{schema::RecordField, Schema};
use crate::error::{Error, Result};
use crate::templates::*;
pub enum Source<'a> {
Schema(&'a Schema),
SchemaStr(&'a str),
FilePath(&'a Path),
DirPath(&'a Path),
}
pub struct Generator {
templater: Templater,
}
impl Generator {
pub fn new() -> Result<Generator> {
GeneratorBuilder::new().build()
}
pub fn builder() -> GeneratorBuilder {
GeneratorBuilder::new()
}
pub fn gen(&self, source: &Source, output: &mut impl Write) -> Result<()> {
output.write_all(self.templater.str_serde()?.as_bytes())?;
if self.templater.nullable {
output.write_all(DESER_NULLABLE.as_bytes())?;
}
match source {
Source::Schema(schema) => self.gen_in_order(schema, output)?,
Source::SchemaStr(raw_schema) => {
let schema = Schema::parse_str(&raw_schema)?;
self.gen_in_order(&schema, output)?
}
Source::FilePath(schema_file) => {
let mut raw_schema = String::new();
File::open(&schema_file)?.read_to_string(&mut raw_schema)?;
let schema = Schema::parse_str(&raw_schema)?;
self.gen_in_order(&schema, output)?
}
Source::DirPath(schemas_dir) => {
for entry in std::fs::read_dir(schemas_dir)? {
let path = entry?.path();
if !path.is_dir() {
let mut raw_schema = String::new();
File::open(&path)?.read_to_string(&mut raw_schema)?;
let schema = Schema::parse_str(&raw_schema)?;
self.gen_in_order(&schema, output)?
}
}
}
}
Ok(())
}
fn gen_in_order(&self, schema: &Schema, output: &mut impl Write) -> Result<()> {
let mut gs = GenState::new();
let mut deps = deps_stack(schema);
while let Some(s) = deps.pop() {
match s {
Schema::Fixed { .. } => {
let code = &self.templater.str_fixed(&s)?;
output.write_all(code.as_bytes())?
}
Schema::Enum { .. } => {
let code = &self.templater.str_enum(&s)?;
output.write_all(code.as_bytes())?
}
Schema::Record { .. } => {
let code = &self.templater.str_record(&s, &gs)?;
output.write_all(code.as_bytes())?
}
Schema::Array(inner) => {
let type_str = array_type(inner, &gs)?;
gs.put_type(&s, type_str)
}
Schema::Map(inner) => {
let type_str = map_type(inner, &gs)?;
gs.put_type(&s, type_str)
}
Schema::Union(union) => {
if let [Schema::Null, inner] = union.variants() {
let type_str = option_type(inner, &gs)?;
gs.put_type(&s, type_str)
}
}
_ => Err(Error::Schema(format!("Not a valid root schema: {:?}", s)))?,
}
}
Ok(())
}
}
fn deps_stack(schema: &Schema) -> Vec<&Schema> {
fn push_unique<'a>(deps: &mut Vec<&'a Schema>, s: &'a Schema) {
if !deps.contains(&s) {
deps.push(s)
};
}
let mut deps = Vec::new();
let mut q = VecDeque::new();
q.push_back(schema);
while !q.is_empty() {
let s = q.pop_front().unwrap();
match s {
Schema::Fixed { .. } => push_unique(&mut deps, s),
Schema::Enum { .. } => push_unique(&mut deps, s),
Schema::Record { fields, .. } => {
push_unique(&mut deps, s);
let by_pos = fields
.iter()
.map(|f| (f.position, f))
.collect::<HashMap<_, _>>();
let mut i = 0;
while let Some(RecordField { schema: sr, .. }) = by_pos.get(&i) {
match sr {
Schema::Fixed { .. } => push_unique(&mut deps, sr),
Schema::Enum { .. } => push_unique(&mut deps, sr),
Schema::Record { .. } => q.push_back(sr),
Schema::Map(sc) | Schema::Array(sc) => match &**sc {
Schema::Fixed { .. }
| Schema::Enum { .. }
| Schema::Record { .. }
| Schema::Map(..)
| Schema::Array(..)
| Schema::Union(..) => q.push_back(&**sc),
_ => (),
},
Schema::Union(union) => {
if let [Schema::Null, sc] = union.variants() {
match sc {
Schema::Fixed { .. }
| Schema::Enum { .. }
| Schema::Record { .. }
| Schema::Map(..)
| Schema::Array(..)
| Schema::Union(..) => {
q.push_back(sc);
push_unique(&mut deps, sc);
}
_ => (),
}
}
}
_ => (),
};
i += 1;
}
}
Schema::Map(sc) | Schema::Array(sc) => match &**sc {
Schema::Fixed { .. }
| Schema::Enum { .. }
| Schema::Record { .. }
| Schema::Map(..)
| Schema::Array(..)
| Schema::Union(..) => q.push_back(&**sc),
_ => push_unique(&mut deps, s),
},
Schema::Union(union) => {
if let [Schema::Null, sc] = union.variants() {
match sc {
Schema::Fixed { .. }
| Schema::Enum { .. }
| Schema::Record { .. }
| Schema::Map(..)
| Schema::Array(..)
| Schema::Union(..) => q.push_back(sc),
_ => push_unique(&mut deps, s),
}
}
}
_ => (),
}
}
deps
}
pub struct GeneratorBuilder {
precision: usize,
nullable: bool,
}
impl GeneratorBuilder {
pub fn new() -> GeneratorBuilder {
GeneratorBuilder {
precision: 3,
nullable: false,
}
}
pub fn precision(mut self, precision: usize) -> GeneratorBuilder {
self.precision = precision;
self
}
pub fn nullable(mut self, nullable: bool) -> GeneratorBuilder {
self.nullable = nullable;
self
}
pub fn build(self) -> Result<Generator> {
let mut templater = Templater::new()?;
templater.precision = self.precision;
templater.nullable = self.nullable;
Ok(Generator { templater })
}
}
#[cfg(test)]
mod tests {
use avro_rs::schema::Name;
use serde::{Deserialize, Serialize};
use super::*;
macro_rules! assert_schema_gen (
($generator:expr, $expected:expr, $raw_schema:expr) => (
let schema = Schema::parse_str($raw_schema).unwrap();
let source = Source::Schema(&schema);
let mut buf = vec![];
$generator.gen(&source, &mut buf).unwrap();
let res = String::from_utf8(buf).unwrap();
assert_eq!($expected, &res);
);
);
#[test]
fn simple() {
let raw_schema = r#"
{
"type": "record",
"name": "test",
"fields": [
{"name": "a", "type": "long", "default": 42},
{"name": "b", "type": "string"}
]
}
"#;
let expected = "use serde::{Deserialize, Serialize};
#[serde(default)]
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
pub struct Test {
pub a: i64,
pub b: String,
}
impl Default for Test {
fn default() -> Test {
Test {
a: 42,
b: String::default(),
}
}
}
";
let g = Generator::new().unwrap();
assert_schema_gen!(g, expected, raw_schema);
}
#[test]
fn complex() {
let raw_schema = r#"
{
"type": "record",
"name": "User",
"doc": "Hi there.",
"fields": [
{"name": "name", "type": "string", "default": ""},
{"name": "favorite_number", "type": "int", "default": 7},
{"name": "likes_pizza", "type": "boolean", "default": false},
{"name": "oye", "type": "float", "default": 1.1},
{"name": "aa-i32",
"type": {"type": "array", "items": {"type": "array", "items": "int"}},
"default": [[0], [12, -1]]}
]
}
"#;
let expected = r#"use serde::{Deserialize, Serialize};
/// Hi there.
#[serde(default)]
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
pub struct User {
pub name: String,
pub favorite_number: i32,
pub likes_pizza: bool,
pub oye: f32,
#[serde(rename = "aa-i32")]
pub aa_i32: Vec<Vec<i32>>,
}
impl Default for User {
fn default() -> User {
User {
name: "".to_owned(),
favorite_number: 7,
likes_pizza: false,
oye: 1.100,
aa_i32: vec![vec![0], vec![12, -1]],
}
}
}
"#;
let g = Generator::new().unwrap();
assert_schema_gen!(g, expected, raw_schema);
}
#[test]
fn nullable_gen() {
let raw_schema = r#"
{
"type": "record",
"name": "test",
"fields": [
{"name": "a", "type": "long", "default": 42},
{"name": "b-b", "type": "string", "default": "na"},
{"name": "c", "type": ["null", "int"], "default": null}
]
}
"#;
let expected = r#"use serde::{Deserialize, Deserializer, Serialize};
macro_rules! deser(
($name:ident, $rtype:ty, $val:expr) => (
fn $name<'de, D>(deserializer: D) -> Result<$rtype, D::Error>
where
D: Deserializer<'de>,
{
let opt = Option::deserialize(deserializer)?;
Ok(opt.unwrap_or_else(|| $val))
}
);
);
#[serde(default)]
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
pub struct Test {
#[serde(deserialize_with = "nullable_test_a")]
pub a: i64,
#[serde(rename = "b-b", deserialize_with = "nullable_test_b_b")]
pub b_b: String,
pub c: Option<i32>,
}
deser!(nullable_test_a, i64, 42);
deser!(nullable_test_b_b, String, "na".to_owned());
impl Default for Test {
fn default() -> Test {
Test {
a: 42,
b_b: "na".to_owned(),
c: None,
}
}
}
"#;
let g = Generator::builder().nullable(true).build().unwrap();
assert_schema_gen!(g, expected, raw_schema);
}
#[test]
fn nullable_array() {
let raw_schema = r#"
{
"name": "Snmp",
"type": "record",
"fields": [ {
"name": "v1",
"type": [ "null", {
"name": "V1",
"type": "record",
"fields": [ {
"name": "pdu",
"type": [ "null", {
"name": "TrapV1",
"type": "record",
"fields": [ {
"name": "var",
"type": ["null", {
"type": "array",
"items": {
"name": "Variable",
"type": "record",
"fields": [ {
"name": "oid",
"type": ["null", {
"type":"array",
"items": "long"
} ],
"default": null
}, {
"name": "val",
"type": ["null", "string"],
"default": null
}],
"default": {}
}
} ],
"default": null
} ]
} ],
"default": null
} ]
} ],
"default": null
} ],
"default": {}
}
"#;
let expected = r#"use serde::{Deserialize, Serialize};
#[serde(default)]
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
pub struct Variable {
pub oid: Option<Vec<i64>>,
pub val: Option<String>,
}
impl Default for Variable {
fn default() -> Variable {
Variable {
oid: None,
val: None,
}
}
}
#[serde(default)]
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
pub struct TrapV1 {
pub var: Option<Vec<Variable>>,
}
impl Default for TrapV1 {
fn default() -> TrapV1 {
TrapV1 {
var: None,
}
}
}
#[serde(default)]
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
pub struct V1 {
pub pdu: Option<TrapV1>,
}
impl Default for V1 {
fn default() -> V1 {
V1 {
pdu: None,
}
}
}
#[serde(default)]
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
pub struct Snmp {
pub v1: Option<V1>,
}
impl Default for Snmp {
fn default() -> Snmp {
Snmp {
v1: None,
}
}
}
"#;
let g = Generator::new().unwrap();
assert_schema_gen!(g, expected, raw_schema);
}
#[test]
fn nullable_code() {
use serde::{Deserialize, Deserializer};
macro_rules! deser(
($name:ident, $rtype:ty, $val:expr) => (
fn $name<'de, D>(deserializer: D) -> std::result::Result<$rtype, D::Error>
where
D: Deserializer<'de>,
{
let opt = Option::deserialize(deserializer)?;
Ok(opt.unwrap_or_else(|| $val))
}
);
);
#[serde(default)]
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct Test {
#[serde(deserialize_with = "nullable_test_a")]
pub a: i64,
#[serde(rename = "b-b", deserialize_with = "nullable_test_b_b")]
pub b_b: String,
pub c: Option<i32>,
}
deser!(nullable_test_a, i64, 42);
deser!(nullable_test_b_b, String, "na".to_owned());
impl Default for Test {
fn default() -> Test {
Test {
a: 42,
b_b: "na".to_owned(),
c: None,
}
}
}
let json = r#"{"a": null, "b-b": null, "c": null}"#;
let res: Test = serde_json::from_str(json).unwrap();
assert_eq!(Test::default(), res);
}
#[test]
fn deps() {
use matches::assert_matches;
let raw_schema = r#"
{
"type": "record",
"name": "User",
"fields": [
{"name": "name", "type": "string", "default": "unknown"},
{"name": "address",
"type": {
"type": "record",
"name": "Address",
"fields": [
{"name": "city", "type": "string", "default": "unknown"},
{"name": "country",
"type": {"type": "enum", "name": "Country", "symbols": ["FR", "JP"]}
}
]
}
}
]
}
"#;
let schema = Schema::parse_str(&raw_schema).unwrap();
let mut deps = deps_stack(&schema);
let s = deps.pop().unwrap();
assert_matches!(s, Schema::Enum{ name: Name { ref name, ..}, ..} if name == "Country");
let s = deps.pop().unwrap();
assert_matches!(s, Schema::Record{ name: Name { ref name, ..}, ..} if name == "Address");
let s = deps.pop().unwrap();
assert_matches!(s, Schema::Record{ name: Name { ref name, ..}, ..} if name == "User");
let s = deps.pop();
assert_matches!(s, None);
}
}