transcode-pb 0.1.0

translates encoding between json and protobuf
Documentation
use std::rc::Rc;

use crate::json::*;

use super::*;

macro_rules! write_num_field {
    ($e: expr, $tag: expr, $s: expr, $ty: ty, $z: expr) => {
        $s.parse::<$ty>()
            .map(|v| {
                if v != 0 as $ty {
                    if $z {
                        $e.emit_zigzag($tag, v as i64);
                    } else {
                        $e.emit_varint($tag, v as u64);
                    }
                }
            })
            .map_err(|e| Error::Wrap(e.into()))
    };
    ($e: expr, $tag: expr, $s: expr, $ty: ty) => {
        $s.parse::<$ty>()
            .map(|v| {
                if v != 0 as $ty {
                    $e.write_varint(proto_key($tag, if ::std::mem::size_of::<$ty>() == 64 {
                        WIRE_64BIT
                    } else {
                        WIRE_32BIT
                    }));
                    $e.write_slice(&v.to_le_bytes()[..]);
                }
            })
            .map_err(|e| Error::Wrap(e.into()))
    };
}

macro_rules! write_elem_fn {
    ($enc: expr, bool) => {
        |_, tok| match tok {
            Token::True | Token::False => {
                $enc.write_varint(matches!(tok, Token::True) as u64);
                Ok(())
            }
            _ => Err(Error::UnexpectedToken),
        }
    };
    ($enc: expr, $ty: ty) => {
        |_, tok| match tok {
            Token::Number(s) => ::std::str::from_utf8(s)
                .map_err(|e| Error::Wrap(e.into()))
                .and_then(|s| {
                    s.parse::<$ty>()
                        .map(|v| $enc.write_slice(&v.to_le_bytes()[..]))
                        .map_err(|e| Error::Wrap(e.into()))
                }),
            _ => Err(Error::UnexpectedToken),
        }
    };
    ($enc: expr, $ty: ty, $z: expr) => {
        |_, tok| match tok {
            Token::Number(s) => ::std::str::from_utf8(s)
                .map_err(|e| Error::Wrap(e.into()))
                .and_then(|s| {
                    s.parse::<$ty>()
                        .map(|v| {
                            if $z {
                                $enc.write_zigzag(v as i64);
                            } else {
                                $enc.write_varint(v as u64);
                            }
                        })
                        .map_err(|e| Error::Wrap(e.into()))
                }),
            _ => Err(Error::UnexpectedToken),
        }
    };
}

fn skip_value(it: &mut Iter, tok: Token) -> Result<()> {
    match tok {
        Token::Null | Token::False | Token::True | Token::Number(_) | Token::String(_) => Ok(()),
        Token::Object => {
            while let Some(tok) = it.next() {
                match tok {
                    Token::ObjectClose => return Ok(()),
                    Token::Colon | Token::Comma => continue,
                    _ => skip_value(it, tok)?,
                }
            }
            Err(Error::UnexpectedEof)
        }
        Token::Array => {
            while let Some(tok) = it.next() {
                match tok {
                    Token::ArrayClose => return Ok(()),
                    Token::Comma => continue,
                    _ => skip_value(it, tok)?,
                }
            }
            Err(Error::UnexpectedEof)
        }
        _ => Err(Error::UnexpectedToken),
    }
}

#[allow(clippy::float_cmp)]
fn trans_numeric(enc: &mut Encoder, ty: &Type, tag: u32, s: &[u8]) -> Result<()> {
    ::std::str::from_utf8(s)
        .map_err(|e| Error::Wrap(e.into()))
        .and_then(|s| match ty {
            Type::Double => write_num_field!(enc, tag, s, f64),
            Type::Float => write_num_field!(enc, tag, s, f32),
            Type::Int32 => write_num_field!(enc, tag, s, i32, false),
            Type::Int64 => write_num_field!(enc, tag, s, i64, false),
            Type::Uint32 => write_num_field!(enc, tag, s, u32, false),
            Type::Uint64 => write_num_field!(enc, tag, s, u64, false),
            Type::Sint32 => write_num_field!(enc, tag, s, i32, true),
            Type::Sint64 => write_num_field!(enc, tag, s, i64, true),
            Type::Fixed32 => write_num_field!(enc, tag, s, u32),
            Type::Fixed64 => write_num_field!(enc, tag, s, u64),
            Type::Sfixed32 => write_num_field!(enc, tag, s, i32),
            Type::Sfixed64 => write_num_field!(enc, tag, s, i64),
            _ => Err(Error::TypeMismatch),
        })
}

fn trans_string(enc: &mut Encoder, s: &[u8], tag: u32) -> Result<()> {
    let mut z = Vec::with_capacity(s.len() - 2);
    unescape_string(&s[1..s.len() - 1], &mut z).map_err(|e| Error::Wrap(e.into()))?;
    if !z.is_empty() {
        enc.emit_len_delim(tag, &z);
    }
    Ok(())
}

fn trans_bytes(enc: &mut Encoder, s: &[u8], tag: u32) -> Result<()> {
    let mut z = Vec::with_capacity(s.len() * 4 / 3);
    base64::decode_config_buf(&s[1..s.len() - 1], base64::STANDARD, &mut z)
        .map_err(|e| Error::Wrap(e.into()))?;
    if !z.is_empty() {
        enc.emit_len_delim(tag, &z);
    }
    Ok(())
}

fn trans_map(enc: &mut Encoder, it: &mut Iter, tag: u32, kty: &Type, vty: &Type) -> Result<()> {
    let mut entry = Encoder::new();
    let mut key: Option<Token> = None;
    while let Some(tok) = it.next() {
        match tok {
            Token::ObjectClose if key.is_none() => return Ok(()),
            Token::Comma | Token::Colon => continue,
            _ => {
                if let Some(k) = key {
                    entry.clear();
                    trans_field(&mut entry, it, 1, k, kty)?;
                    trans_field(&mut entry, it, 2, tok, vty)?;
                    let data = entry.as_bytes();
                    if !data.is_empty() {
                        enc.emit_len_delim(tag, data);
                    }
                    key = None;
                } else if matches!(tok, Token::String(_)) {
                    key = Some(tok);
                } else {
                    return Err(Error::UnexpectedToken);
                }
            }
        }
    }
    Err(Error::UnexpectedEof)
}

fn trans_repeated_impl<F>(it: &mut Iter, mut f: F) -> Result<()>
where
    F: FnMut(&mut Iter, Token) -> Result<()>,
{
    while let Some(tok) = it.next() {
        match tok {
            Token::Comma => continue,
            Token::ArrayClose => return Ok(()),
            _ => f(it, tok)?,
        }
    }
    Err(Error::UnexpectedEof)
}

fn trans_repeated(enc: &mut Encoder, it: &mut Iter, tag: u32, elem: &Rc<Type>) -> Result<()> {
    match elem.as_ref() {
        Type::Message(msg) => {
            let mut z = Encoder::new();
            trans_repeated_impl(it, |it, tok| match tok {
                Token::Object => {
                    z.clear();
                    trans_message(&mut z, it, msg)?;
                    enc.emit_len_delim(tag, z.as_bytes());
                    Ok(())
                }
                _ => Err(Error::UnexpectedToken),
            })
        }
        Type::String => {
            let mut z = Vec::new();
            trans_repeated_impl(it, |_, tok| match tok {
                Token::String(s) => {
                    z.clear();
                    unescape_string(&s[1..s.len() - 1], &mut z)
                        .map_err(|e| Error::Wrap(e.into()))?;
                    enc.emit_len_delim(tag, &z);
                    Ok(())
                }
                _ => Err(Error::UnexpectedToken),
            })
        }
        Type::Bytes => {
            let mut z = Vec::new();
            trans_repeated_impl(it, |_, tok| match tok {
                Token::String(s) => {
                    z.clear();
                    base64::decode_config_buf(&s[1..s.len() - 1], base64::STANDARD, &mut z)
                        .map_err(|e| Error::Wrap(e.into()))?;
                    enc.emit_len_delim(tag, &z);
                    Ok(())
                }
                _ => Err(Error::UnexpectedToken),
            })
        }
        _ => {
            let mut packed = Encoder::new();
            match elem.as_ref() {
                Type::Bool => trans_repeated_impl(it, write_elem_fn!(packed, bool)),
                Type::Double => trans_repeated_impl(it, write_elem_fn!(packed, f64)),
                Type::Float => trans_repeated_impl(it, write_elem_fn!(packed, f32)),
                Type::Int32 => trans_repeated_impl(it, write_elem_fn!(packed, i32, false)),
                Type::Int64 => trans_repeated_impl(it, write_elem_fn!(packed, i64, false)),
                Type::Uint32 => trans_repeated_impl(it, write_elem_fn!(packed, u32, false)),
                Type::Uint64 => trans_repeated_impl(it, write_elem_fn!(packed, u64, false)),
                Type::Sint32 => trans_repeated_impl(it, write_elem_fn!(packed, i32, true)),
                Type::Sint64 => trans_repeated_impl(it, write_elem_fn!(packed, i64, true)),
                Type::Fixed32 => trans_repeated_impl(it, write_elem_fn!(packed, u32)),
                Type::Fixed64 => trans_repeated_impl(it, write_elem_fn!(packed, u64)),
                Type::Sfixed32 => trans_repeated_impl(it, write_elem_fn!(packed, i32)),
                Type::Sfixed64 => trans_repeated_impl(it, write_elem_fn!(packed, i64)),
                _ => return Err(Error::TypeMismatch),
            }?;
            if !packed.is_empty() {
                enc.emit_len_delim(tag, packed.as_bytes());
            }
            Ok(())
        }
    }
}

fn trans_embedded_message(enc: &mut Encoder, it: &mut Iter, tag: u32, msg: &Message) -> Result<()> {
    let mut embedded = Encoder::new();
    trans_message(&mut embedded, it, msg)?;
    enc.emit_len_delim(tag, embedded.as_bytes());
    Ok(())
}

fn trans_field(enc: &mut Encoder, it: &mut Iter, tag: u32, lead: Token, ty: &Type) -> Result<()> {
    match lead {
        Token::String(s) => match ty {
            Type::String => trans_string(enc, s, tag),
            Type::Bytes => trans_bytes(enc, s, tag),
            _ => Err(Error::TypeMismatch),
        },
        Token::Number(n) => trans_numeric(enc, ty, tag, n),
        Token::True | Token::False => match ty {
            Type::Bool => {
                if matches!(lead, Token::True) {
                    enc.emit_varint(tag, 1);
                }
                Ok(())
            }
            _ => Err(Error::TypeMismatch),
        },
        Token::Null => match ty {
            Type::Bytes | Type::Message(_) | Type::Array(_) | Type::Map(_, _) => Ok(()),
            _ => Err(Error::TypeMismatch),
        },
        Token::Object => match ty {
            Type::Message(msg) => trans_embedded_message(enc, it, tag, msg),
            Type::Map(kty, vty) => trans_map(enc, it, tag, kty, vty),
            _ => Err(Error::TypeMismatch),
        },
        Token::Array => match ty {
            Type::Array(elem) => trans_repeated(enc, it, tag, elem),
            _ => Err(Error::TypeMismatch),
        },
        _ => Err(Error::UnexpectedToken),
    }
}

fn trans_message(enc: &mut Encoder, it: &mut Iter, msg: &Message) -> Result<()> {
    let mut key: Option<&[u8]> = None;
    while let Some(tok) = it.next() {
        match tok {
            Token::ObjectClose if key.is_none() => return Ok(()),
            Token::Comma | Token::Colon => continue,
            _ => {
                if let Some(k) = key {
                    let name = ::std::str::from_utf8(&k[1..k.len() - 1])
                        .map_err(|e| Error::Wrap(e.into()))?;
                    if let Some(field) = msg.get_by_name(name) {
                        trans_field(enc, it, field.tag, tok, &field.ty)?;
                    } else {
                        skip_value(it, tok)?;
                    }
                    key = None;
                } else if let Token::String(k) = tok {
                    key = Some(k);
                } else {
                    return Err(Error::UnexpectedToken);
                }
            }
        }
    }
    Err(Error::UnexpectedEof)
}

pub fn trans_json_to_proto(enc: &mut Encoder, it: &mut Iter, ty: &Type) -> Result<()> {
    match ty {
        Type::Message(msg) => match it.next() {
            Some(Token::Object) => trans_message(enc, it, msg),
            None => Err(Error::UnexpectedEof),
            _ => Err(Error::UnexpectedToken),
        },
        _ => Err(Error::TypeMismatch),
    }
}

#[cfg(test)]
mod tests {
    use super::super::tests::*;
    use super::*;

    fn test_trans_json_to_proto(s: &str) {
        println!("input: {}", s);
        let mut enc = Encoder::new();
        let mut it = Iter::new(s.as_bytes());
        let r = trans_json_to_proto(&mut enc, &mut it, &get_msg_foo_type());
        if let Err(ref e) = r {
            println!("err: {}", e);
        }
        assert!(r.is_ok());
        println!("output: {}", printable(enc.as_bytes()));
    }

    #[test]
    fn test_trans_json_to_proto_case0() {
        let s = r#"{}"#;
        test_trans_json_to_proto(s);
    }

    #[test]
    fn test_trans_json_to_proto_case1() {
        let s = r#"{"a":"a","b":true,"c":1,"d":{"a":2,"b":"b"},"e":[3,4,5],"f":["f0","f1","f2"],"g":[{"a":6,"s":"s0"},{"a":7,"s":"s1"}]}"#;
        test_trans_json_to_proto(s);
    }

    #[test]
    fn test_trans_json_to_proto_case2() {
        let s = r#"{"a":"","b":false,"c":0,"d":{"a":0,"b":""},"e":[0,0,0],"f":["","",""],"g":[{"a":0,"s":""},{"a":0,"s":""},{"a":0,"s":""},{"a":0,"s":""},{"a":0,"s":""},{"a":0,"s":""},{"a":0,"s":""},{"a":0,"s":""}]}"#;
        test_trans_json_to_proto(s);
    }
}