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::{Result, RsgenError};
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 Box<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 Box<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(RsgenError::Schema(format!(
"Not a valid root schema: {:?}",
s
)))?,
}
}
Ok(())
}
}
fn deps_stack(schema: &Schema) -> Vec<&Schema> {
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 { .. } => deps.push(s),
Schema::Enum { .. } => deps.push(s),
Schema::Record { fields, .. } => {
deps.push(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 { .. } => deps.push(sr),
Schema::Enum { .. } => deps.push(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),
_ => (),
}
}
}
_ => (),
};
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),
_ => deps.push(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),
_ => deps.push(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 {
extern crate gag;
extern crate matches;
use std::io::stdout;
use self::gag::BufferRedirect;
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 = BufferRedirect::stdout().unwrap();
let mut out: Box<Write> = Box::new(stdout());
$generator.gen(&source, &mut out).unwrap();
let mut res = String::new();
buf.read_to_string(&mut res).unwrap();
buf.into_inner();
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_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 self::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);
}
}