/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::io::Cursor;
use std::u8;

use bytes::Buf;
use bytes::Bytes;

use super::BOOL_VALUES;
use super::BYTE_VALUES;
use super::DOUBLE_VALUES;
use super::FLOAT_VALUES;
use super::INT16_VALUES;
use super::INT32_VALUES;
use super::INT64_VALUES;
use crate::thrift_protocol::MessageType;
use crate::ttype::TType;
use crate::CompactProtocol;
use crate::Protocol;
use crate::ProtocolReader;
use crate::ProtocolWriter;

#[test]
fn read_write_bool_list() {
    let thetype = TType::Bool;
    let thelen = BOOL_VALUES.len();

    // Write
    let buf = serialize!(CompactProtocol, |p| {
        p.write_list_begin(thetype, thelen);

        for v in &BOOL_VALUES {
            p.write_bool(*v);
        }

        p.write_list_end();
    });

    // Read
    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(buf));
    {
        let (thetype2, thelen2) = deserializer
            .read_list_begin()
            .expect("failed to read header");

        assert_eq!(thetype, thetype2);
        assert_eq!(Some(thelen), thelen2);

        for v in &BOOL_VALUES {
            assert_eq!(*v, deserializer.read_bool().expect("failed to read bool"));
        }
        deserializer
            .read_list_end()
            .expect("failed to read list end");
    }
}

#[test]
fn read_write_string_list() {
    let string_values = vec![
        String::from(""),
        String::from("a"),
        String::from("st[uf]f"),
        String::from("st,u:ff with spaces"),
        String::from("stuff\twith\nescape\\characters'...\"lots{of}fun</xml>"),
    ];
    let thetype = TType::String;
    let thelen = string_values.len();

    let buf = serialize!(CompactProtocol, |p| {
        p.write_list_begin(thetype, thelen);

        for v in &string_values {
            p.write_string(v);
        }
        p.write_list_end();
    });

    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(buf));
    {
        let (thetype2, thelen2) = deserializer
            .read_list_begin()
            .expect("failed to read header");

        assert_eq!(thetype, thetype2);
        assert_eq!(Some(thelen), thelen2);

        for v in &string_values {
            assert_eq!(
                *v,
                deserializer.read_string().expect("failed to read string")
            );
        }
        deserializer
            .read_list_end()
            .expect("failed to read list end");
    }
}

#[test]
fn read_write_byte_list() {
    let thetype = TType::Byte;
    let thelen = BYTE_VALUES.len();

    let buf = serialize!(CompactProtocol, |p| {
        p.write_list_begin(thetype, thelen);

        for v in &BYTE_VALUES {
            p.write_byte(*v);
        }
        p.write_list_end();
    });

    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(buf));
    {
        let (thetype2, thelen2) = deserializer
            .read_list_begin()
            .expect("failed to read header");

        assert_eq!(thetype, thetype2);
        assert_eq!(Some(thelen), thelen2);

        for v in &BYTE_VALUES {
            assert_eq!(*v, deserializer.read_byte().expect("failed to read byte"));
        }
        deserializer
            .read_list_end()
            .expect("failed to read list end");
    }
}

#[test]
fn read_write_i16_list() {
    let thetype = TType::I16;
    let thelen = INT16_VALUES.len();

    let buf = serialize!(CompactProtocol, |p| {
        p.write_list_begin(thetype, thelen);

        for v in &INT16_VALUES {
            p.write_i16(*v);
        }
        p.write_list_end();
    });

    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(buf));
    {
        let (thetype2, thelen2) = deserializer
            .read_list_begin()
            .expect("failed to read header");

        assert_eq!(thetype, thetype2);
        assert_eq!(Some(thelen), thelen2);

        for v in &INT16_VALUES {
            assert_eq!(*v, deserializer.read_i16().expect("failed to read i16"));
        }
        deserializer
            .read_list_end()
            .expect("failed to read list end");
    }
}

#[test]
fn read_write_i32_list() {
    let thetype = TType::I32;
    let thelen = INT32_VALUES.len();

    let buf = serialize!(CompactProtocol, |p| {
        p.write_list_begin(thetype, thelen);

        for v in &INT32_VALUES {
            p.write_i32(*v);
        }
        p.write_list_end();
    });

    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(buf));
    {
        let (thetype2, thelen2) = deserializer
            .read_list_begin()
            .expect("failed to read header");

        assert_eq!(thetype, thetype2);
        assert_eq!(Some(thelen), thelen2);

        for v in &INT32_VALUES {
            assert_eq!(*v, deserializer.read_i32().expect("failed to read i32"));
        }
        deserializer
            .read_list_end()
            .expect("failed to read list end");
    }
}

#[test]
fn read_write_i64_list() {
    let thetype = TType::I64;
    let thelen = INT64_VALUES.len();

    let buf = serialize!(CompactProtocol, |p| {
        p.write_list_begin(thetype, thelen);

        for v in &INT64_VALUES {
            p.write_i64(*v);
        }
        p.write_list_end();
    });

    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(buf));
    {
        let (thetype2, thelen2) = deserializer
            .read_list_begin()
            .expect("failed to read header");

        assert_eq!(thetype, thetype2);
        assert_eq!(Some(thelen), thelen2);

        for v in &INT64_VALUES {
            assert_eq!(*v, deserializer.read_i64().expect("failed to read i64"));
        }
        deserializer
            .read_list_end()
            .expect("failed to read list end");
    }
}

#[test]
fn read_write_f32_list() {
    let thetype = TType::Float;
    let thelen = FLOAT_VALUES.len();

    let buf = serialize!(CompactProtocol, |p| {
        p.write_list_begin(thetype, thelen);

        for v in &FLOAT_VALUES {
            p.write_float(*v);
        }
        p.write_list_end();
    });

    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(buf));
    {
        let (thetype2, thelen2) = deserializer
            .read_list_begin()
            .expect("failed to read header");

        assert_eq!(thetype, thetype2);
        assert_eq!(Some(thelen), thelen2);

        for v in &FLOAT_VALUES {
            let val = deserializer.read_float().expect("failed to read f32");
            // In Rust, NaN != NaN
            if v.is_nan() {
                assert!(val.is_nan());
            } else {
                assert_eq!(*v, val);
            }
        }
        deserializer
            .read_list_end()
            .expect("failed to read list end");
    }
}

#[test]
fn read_write_f64_list() {
    let thetype = TType::Double;
    let thelen = DOUBLE_VALUES.len();

    let buf = serialize!(CompactProtocol, |p| {
        p.write_list_begin(thetype, thelen);

        for v in &DOUBLE_VALUES {
            p.write_double(*v);
        }
        p.write_list_end();
    });

    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(buf));
    {
        let (thetype2, thelen2) = deserializer
            .read_list_begin()
            .expect("failed to read header");

        assert_eq!(thetype, thetype2);
        assert_eq!(Some(thelen), thelen2);

        for v in &DOUBLE_VALUES {
            let val = deserializer.read_double().expect("failed to read f64");
            // In Rust, NaN != NaN :)
            if v.is_nan() {
                assert!(val.is_nan());
            } else {
                assert_eq!(*v, val);
            }
        }
        deserializer
            .read_list_end()
            .expect("failed to read list end");
    }
}

#[test]
fn read_write_f64_set() {
    let thetype = TType::Double;
    let thelen = DOUBLE_VALUES.len();

    let buf = serialize!(CompactProtocol, |p| {
        p.write_set_begin(thetype, thelen);

        for v in &DOUBLE_VALUES {
            p.write_double(*v);
        }
        p.write_list_end();
    });

    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(buf));
    {
        let (thetype2, thelen2) = deserializer
            .read_set_begin()
            .expect("failed to read header");

        assert_eq!(thetype, thetype2);
        assert_eq!(Some(thelen), thelen2);

        for v in &DOUBLE_VALUES {
            let val = deserializer.read_double().expect("failed to read f64");
            // In Rust, NaN != NaN :)
            if v.is_nan() {
                assert!(val.is_nan());
            } else {
                assert_eq!(*v, val);
            }
        }
        assert!(deserializer.read_set_end().is_ok());
    }
}

#[test]
fn read_write_string_i64_map() {
    let key_type = TType::String;
    let value_type = TType::I64;
    let thelen = INT64_VALUES.len();
    let string_keys: Vec<String> = INT64_VALUES.iter().map(|&v| v.to_string()).collect();

    let buf = serialize!(CompactProtocol, |p| {
        p.write_map_begin(key_type, value_type, thelen);

        for (k, v) in string_keys.iter().zip(INT64_VALUES.iter()) {
            p.write_string(k);
            p.write_i64(*v);
        }
        p.write_map_end();
    });

    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(buf));
    {
        let (key_type2, value_type2, thelen2) = deserializer
            .read_map_begin()
            .expect("failed to read header");

        assert_eq!(key_type, key_type2);
        assert_eq!(value_type, value_type2);
        assert_eq!(Some(thelen), thelen2);

        for (k, v) in string_keys.iter().zip(INT64_VALUES.iter()) {
            assert_eq!(
                *k,
                deserializer.read_string().expect("failed to read string")
            );
            assert_eq!(*v, deserializer.read_i64().expect("failed to read i64"));
        }
        assert!(deserializer.read_map_end().is_ok());
    }
}

#[test]
fn read_write_message() {
    let msg_name = String::from("hello_message");
    let msg_type = MessageType::Call;
    let seq_id = 1;

    let buf = serialize!(CompactProtocol, |p| {
        p.write_message_begin(&msg_name, msg_type, seq_id);
        p.write_message_end();
    });

    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(buf));
    {
        let (msg_name2, msg_type2, seq_id2) = deserializer
            .read_message_begin(|msg| String::from_utf8(msg.to_vec()).expect("bad msg"))
            .expect("failed to read message");
        assert_eq!(msg_name, msg_name2);
        assert_eq!(msg_type, msg_type2);
        assert_eq!(seq_id, seq_id2);

        assert!(deserializer.read_message_end().is_ok());
    }
}

#[test]
fn write_message_begin() {
    let buf = serialize!(CompactProtocol, |serializer| {
        // Write the following information:
        //  1) Version bitwise or'd with MessageType: (0x80001000 | 0x00000001) = [128, 1, 0, 1]
        //  2) String name prefixed by i32 string length: [0, 0, 0, 4, 116, 101, 115, 116]
        //  3) i32 seqid: [0, 0, 0, 1]
        let _ = serializer.write_message_begin(
            "test",
            MessageType::try_from(1).expect("try_from failed"),
            1,
        );
        let _ = serializer.write_message_end();
    });

    let vec = vec![0x82, 2 | (1 << 5), 1, 4, b't', b'e', b's', b't'];

    assert_eq!(vec, buf);
}

#[test]
fn serializer_overflow() {
    let vec: Vec<u8> = (0..u8::MAX).collect();
    let buf = serialize!(CompactProtocol, |p| for i in 0..u8::MAX {
        let _ = p.write_byte(i as i8);
    });

    let buf = buf;

    assert_eq!(vec, buf);
}

#[test]
fn read_message_begin() {
    // Data in vec:
    let msg = vec![
        0x82,            // protocol id
        0x02 | (1 << 5), // version and MessageType
        1,               // seqid
        4,               // length
        b't',
        b'e',
        b's',
        b't',
    ];
    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(Bytes::from(msg)));

    let rmb =
        deserializer.read_message_begin(|name| String::from_utf8(name.to_vec()).expect("bad msg"));
    deserializer
        .read_message_end()
        .expect("read_message_end failed");

    match rmb {
        Ok((name, mty, sid)) => {
            assert_eq!(name, "test");
            assert_eq!(mty, MessageType::try_from(1).expect("try_from failed"));
            assert_eq!(sid, 1);
        }
        Err(bad) => panic!("unexpected error {:?}", bad),
    }
}

#[test]
fn deserializer_underflow() {
    use crate::errors::ProtocolError;

    let vec = vec![1 << 1];
    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(Bytes::from(vec)));

    let last_val = deserializer.read_i32();

    match last_val {
        Ok(i) => assert_eq!(i, 1i32, "read incorrect value for last_val"),
        Err(_) => panic!("Error encoutered reading final i32 from vec"),
    };

    match deserializer.read_i32() {
        Ok(v) => panic!("got unexpected value {}", v),
        Err(err) => match err.downcast_ref::<ProtocolError>() {
            Some(ProtocolError::EOF) => {}
            _ => panic!("got unexpected err {:?}", err),
        },
    }
}

#[test]
fn read_binary_from_chained_buffer() {
    use crate::compact_protocol::CompactProtocolDeserializer;

    let buf1 = Cursor::new(b"\x05hello\x06 ");
    let buf2 = Cursor::new(b"world");
    let joined = buf1.chain(buf2);

    let mut deserializer = CompactProtocolDeserializer::new(joined);

    let result: Vec<u8> = deserializer
        .read_binary()
        .expect("read \"hello\" from the buffer");
    assert_eq!(result.as_slice(), b"hello");

    let result: Vec<u8> = deserializer
        .read_binary()
        .expect("read \" world\" from the buffer");
    assert_eq!(result.as_slice(), b" world");
}

// Test for T29755131
#[test]
fn skip_i32_ok() {
    use crate::compact_protocol::CompactProtocolDeserializer;
    use crate::compact_protocol::CompactProtocolSerializer;
    use crate::protocol::ProtocolReader;
    use crate::protocol::ProtocolWriter;

    let buf = {
        let mut serialize =
            CompactProtocolSerializer::with_buffer(bytes::BytesMut::with_capacity(128));

        serialize.write_i32(123);

        serialize.finish()
    };

    let mut deserializer = CompactProtocolDeserializer::new(Cursor::new(buf));

    match deserializer.skip(TType::I32) {
        Ok(()) => {}
        Err(err) => panic!("Unexpected error {:?}", err),
    }
}

// Test for T29755131
#[test]
fn skip_stop() {
    use crate::compact_protocol::CompactProtocolDeserializer;
    use crate::compact_protocol::CompactProtocolSerializer;
    use crate::protocol::ProtocolReader;
    use crate::protocol::ProtocolWriter;
    use crate::ProtocolError;

    let buf = {
        let mut serialize =
            CompactProtocolSerializer::with_buffer(bytes::BytesMut::with_capacity(128));

        serialize.write_field_stop();

        serialize.finish()
    };

    let mut deserializer = CompactProtocolDeserializer::new(Cursor::new(buf));

    match deserializer.skip(TType::Stop) {
        Ok(()) => panic!("Unexpected success"),
        Err(err) => match err.downcast_ref::<ProtocolError>() {
            Some(ProtocolError::UnexpectedStopInSkip) => {}
            _ => panic!("Unexpected error {:?}", err),
        },
    }
}

// Test for T29755131
#[test]
fn skip_stop_in_container() {
    use crate::compact_protocol::CompactProtocolDeserializer;
    use crate::compact_protocol::CompactProtocolSerializer;
    use crate::protocol::ProtocolReader;
    use crate::protocol::ProtocolWriter;
    use crate::ProtocolError;

    let buf = {
        let mut serialize =
            CompactProtocolSerializer::with_buffer(bytes::BytesMut::with_capacity(128));

        serialize.write_list_begin(TType::Stop, 123);

        serialize.finish()
    };

    let mut deserializer = CompactProtocolDeserializer::new(Cursor::new(buf));

    match deserializer.skip(TType::List) {
        Ok(()) => panic!("Unexpected Success"),
        Err(err) => match err.downcast_ref::<ProtocolError>() {
            Some(ProtocolError::UnexpectedStopInSkip) => {}
            _ => panic!("unexpected error {:?}", err),
        },
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Test fixtures borrowed from protocol_test.go
const BOOL_VALUES: [bool; 5] = [false, true, false, false, true];
const BYTE_VALUES: [i8; 7] = [117i8, 0, 1, 32, 127, -128, 44];
const INT16_VALUES: [i16; 8] = [459i16, 0, 1, -1, -128, 127, 32767, -32768];
const INT32_VALUES: [i32; 9] = [459i32, 0, 1, -1, -128, 127, 32767, 2147483647, -2147483535];
const INT64_VALUES: [i64; 13] = [
    459i64,
    0,
    1,
    -1,
    -128,
    127,
    32767,
    2147483647,
    -2147483535,
    34359738481,
    -35184372088719,
    -9223372036854775808,
    9223372036854775807,
];
const FLOAT_VALUES: [f32; 14] = [
    459.3f32,
    0.0,
    -1.0,
    1.0,
    0.5,
    0.3333,
    3.14159,
    1.537e-38,
    1.673e25,
    6.0221417e23,
    -6.0221417e23,
    ::std::f32::INFINITY,
    ::std::f32::NEG_INFINITY,
    ::std::f32::NAN,
];
const DOUBLE_VALUES: [f64; 14] = [
    459.3f64,
    0.0,
    -1.0,
    1.0,
    0.5,
    0.3333,
    3.14159,
    1.537e-38,
    1.673e25,
    6.02214179e23,
    -6.02214179e23,
    ::std::f64::INFINITY,
    ::std::f64::NEG_INFINITY,
    ::std::f64::NAN,
];

mod binary;
mod compact;
mod simplejson;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// NOTE: See dep_test/cpp_compat_test.cpp for a comparison

use serde_json::json;

use super::BOOL_VALUES;
use super::DOUBLE_VALUES;
use super::INT64_VALUES;
use crate::simplejson_protocol;
use crate::simplejson_protocol::SimpleJsonProtocolDeserializer;
use crate::thrift_protocol::MessageType;
use crate::ttype::TType;
use crate::Deserialize;
use crate::ProtocolWriter;
use crate::SimpleJsonProtocol;

#[test]
fn write_bool_list() {
    let thetype = TType::Bool;
    let thelen = BOOL_VALUES.len();

    // Write
    let buf = serialize!(SimpleJsonProtocol, |p| {
        p.write_list_begin(thetype, thelen);

        for v in &BOOL_VALUES {
            p.write_list_value_begin();
            p.write_bool(*v);
        }

        p.write_list_end();
    });

    let v: serde_json::Result<serde_json::Value> = serde_json::from_slice(&buf);
    assert!(v.is_ok());
    assert_eq!(buf, "[false,true,false,false,true]");
}

#[test]
fn write_string_list() {
    let string_values = vec![
        String::from(""),
        String::from("a"),
        String::from("st[uf]f"),
        String::from("st,u:ff with spaces"),
        String::from("stuff\twith\nescape\\characters'...\"lots{of}fun</xml>"),
        String::from("UNICODE\u{1F60A}UH OH"),
    ];
    let thetype = TType::String;
    let thelen = string_values.len();

    let buf = serialize!(SimpleJsonProtocol, |p| {
        p.write_list_begin(thetype, thelen);

        for v in &string_values {
            p.write_list_value_begin();
            p.write_string(v);
        }
        p.write_list_end();
    });

    let v: serde_json::Result<serde_json::Value> = serde_json::from_slice(&buf);
    assert!(v.is_ok());
    // String in the json should be escaped
    // But unicode is left alone
    //
    // See cpp_compat_test.cpp for a comparison
    assert_eq!(
        buf,
        "[\"\",\"a\",\"st[uf]f\",\"st,\
               u:ff with spaces\",\"stuff\\twith\\nescape\\\\characters'...\\\"lots{of}fun\
               </xml>\",\"UNICODE\u{1F60A}UH OH\"]"
    );
}

#[test]
fn write_i64_list() {
    let thetype = TType::I64;
    let thelen = INT64_VALUES.len();

    let buf = serialize!(SimpleJsonProtocol, |p| {
        p.write_list_begin(thetype, thelen);

        for v in &INT64_VALUES {
            p.write_list_value_begin();
            p.write_i64(*v);
        }
        p.write_list_end();
    });

    let v: serde_json::Result<serde_json::Value> = serde_json::from_slice(&buf);
    assert!(v.is_ok());
    assert_eq!(
        buf,
        "[459,0,1,-1,-128,127,32767,2147483647,\
               -2147483535,34359738481,-35184372088719,\
               -9223372036854775808,9223372036854775807]"
    );
}

#[test]
fn write_f64_list() {
    let thetype = TType::Double;
    let thelen = DOUBLE_VALUES.len();

    let buf = serialize!(SimpleJsonProtocol, |p| {
        p.write_list_begin(thetype, thelen);

        for v in &DOUBLE_VALUES {
            p.write_list_value_begin();
            p.write_double(*v);
        }
        p.write_list_end();
    });

    // json can't handle infinity or nan
    let v: serde_json::Result<serde_json::Value> = serde_json::from_slice(&buf);
    assert!(v.is_err());

    let buf2 = serialize!(SimpleJsonProtocol, |p| {
        p.write_list_begin(thetype, thelen - 2);

        for v in &DOUBLE_VALUES {
            if v >= &::std::f64::INFINITY || v <= &::std::f64::NEG_INFINITY || v.is_nan() {
                continue;
            }
            p.write_list_value_begin();
            p.write_double(*v);
        }
        p.write_list_end();
    });

    let v: serde_json::Result<serde_json::Value> = serde_json::from_slice(&buf2);
    assert!(v.is_ok());
    assert_eq!(
        buf2,
        "[459.3,0.0,-1.0,1.0,0.5,0.3333,3.14159,\
        1.537e-38,1.673e25,6.02214179e23,-6.02214179e23]"
    );
}

#[test]
fn write_binary() {
    let buf = serialize!(SimpleJsonProtocol, |p| {
        // "1234"
        p.write_binary(&[49u8, 50u8, 51u8, 52u8]);
    });

    // the \ isnt escaped
    assert_eq!(buf, "\"MTIzNA\"");
}

#[test]
fn write_message() {
    let msg_name = String::from("hello_message");
    let msg_type = MessageType::Call;
    let seq_id = 10;

    let buf = serialize!(SimpleJsonProtocol, |p| {
        p.write_message_begin(&msg_name, msg_type, seq_id);
        p.write_message_end();
    });

    let v: serde_json::Result<serde_json::Value> = serde_json::from_slice(&buf);
    assert!(v.is_ok());
    assert_eq!(buf, "[\"hello_message\",1,10]");
}

#[test]
fn read_json_value() {
    let json: &[u8] = br#" {
        "void" : null ,
        "bool" : true ,
        "double" : 1.0 ,
        "utf8" : "..." ,
        "spaces" : " spaces ",
        "list" : [ null , 1 ] ,
        "struct" : { "" : null , "x" : false }
    } "#;
    let mut de = SimpleJsonProtocolDeserializer::new(json);
    let actual = serde_json::Value::read(&mut de).unwrap();
    let expected = json!({
        "void": null,
        "bool": true,
        "double": 1.0,
        "utf8": "...",
        "spaces": " spaces ",
        "list": [null, 1],
        "struct": {"": null, "x": false},
    });
    assert_eq!(actual, expected);
}

#[test]
fn fail_to_read_json_value() {
    let json: &[u8] = br#" [null,] "#;
    let mut de = SimpleJsonProtocolDeserializer::new(json);
    let err = serde_json::Value::read(&mut de).unwrap_err();
    assert_eq!("Found trailing comma", err.to_string());
}

#[test]
fn test_trailing() {
    let json: &[u8] = br#" "..." null "#;
    match simplejson_protocol::deserialize(json) {
        Ok(ok) => {
            let _: String = ok;
            panic!("trailing data is supposed to cause deserialization failure");
        }
        Err(err) => assert_eq!(
            "Unexpected trailing data after the end of a value",
            err.to_string(),
        ),
    }
}

#[test]
fn fail_to_read_object() {
    let json: &[u8] = br#" { "#;
    let mut de = SimpleJsonProtocolDeserializer::new(json);
    let err = serde_json::Value::read(&mut de).unwrap_err();
    assert_eq!(
        "Expected the following chars: \"}\", not enough bytes remaining",
        err.to_string()
    );
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fmt::Debug;
use std::io::Cursor;

use bytes::Bytes;
use fbthrift::serialize;
use fbthrift::CompactProtocol;
use fbthrift::Deserialize;
use fbthrift::Protocol;
use fbthrift::Serialize;
use fbthrift::ThriftEnum;
use indexmap::IndexMap;
use indexmap::IndexSet;
use interface::NonstandardCollectionTypes;
use interface::TestBytesShared;
use interface::TestEnum;
use interface::TestEnumEmpty;
use interface::TestSkipV1;
use interface::TestSkipV2;
use interface::WrapBinary;
use interface::WrapString;
use smallvec::SmallVec;

#[test]
fn test_nonstandard_collection_types() {
    assert_round_trip(NonstandardCollectionTypes {
        defaultset: make_btreeset(),
        btreeset: make_btreeset(),
        hashset: make_hashset(),
        indexset_a: make_indexset(),
        indexset_b: make_indexset(),
        indexset_c: IndexSet::new(),
        defaultmap: make_btreemap(),
        btreemap: make_btreemap(),
        hashmap: make_hashmap(),
        indexmap_a: make_indexmap(),
        indexmap_b: make_indexmap(),
        indexmap_c: IndexMap::new(),
        bin_smallvec: SmallVec::from(&b"smallvec"[..]),
        bin_bytes: Bytes::from(&b"bytes"[..]),
        ..Default::default()
    });
}

fn make_btreeset() -> BTreeSet<String> {
    let mut set = BTreeSet::new();
    set.insert("btreeset".to_owned());
    set
}

fn make_hashset() -> HashSet<String> {
    let mut set = HashSet::new();
    set.insert("hashset".to_owned());
    set
}

fn make_indexset() -> IndexSet<String> {
    let mut set = IndexSet::new();
    set.insert("indexset".to_owned());
    set
}

fn make_btreemap() -> BTreeMap<String, String> {
    let mut map = BTreeMap::new();
    map.insert("btreemap".to_owned(), String::new());
    map
}

fn make_hashmap() -> HashMap<String, String> {
    let mut map = HashMap::new();
    map.insert("hashmap".to_owned(), String::new());
    map
}

fn make_indexmap() -> IndexMap<String, String> {
    let mut map = IndexMap::new();
    map.insert("indexmap".to_owned(), String::new());
    map
}

fn assert_round_trip<T>(value: T)
where
    T: Serialize<<CompactProtocol as Protocol>::Sizer>
        + Serialize<<CompactProtocol as Protocol>::Serializer>
        + Deserialize<<CompactProtocol as Protocol>::Deserializer>
        + PartialEq
        + Debug,
{
    let bytes = serialize!(CompactProtocol, |w| Serialize::write(&value, w));
    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(bytes));
    let back = Deserialize::read(&mut deserializer).unwrap();
    assert_eq!(value, back);
}

#[test]
fn test_variants_fn_enum() {
    let expected: &'static [&'static str] = &["FOO", "BAR", "BAZ"];
    assert_eq!(TestEnum::variants(), expected);
    let expected_empty: &'static [&'static str] = &[];
    assert_eq!(TestEnumEmpty::variants(), expected_empty);
}

#[test]
fn test_variant_values_fn_enum() {
    let expected = &[TestEnum::FOO, TestEnum::BAR, TestEnum::BAZ];
    assert_eq!(TestEnum::variant_values(), expected);
    let expected_empty: &'static [TestEnumEmpty] = &[];
    assert_eq!(TestEnumEmpty::variant_values(), expected_empty);
}

#[test]
fn test_enumerate_fn_enum() {
    let expected = &[
        (TestEnum::FOO, "FOO"),
        (TestEnum::BAR, "BAR"),
        (TestEnum::BAZ, "BAZ"),
    ];
    assert_eq!(TestEnum::enumerate(), expected);
    let expected_empty: &'static [(TestEnumEmpty, &str)] = &[];
    assert_eq!(TestEnumEmpty::enumerate(), expected_empty);
}

#[test]
fn test_deserialize_skip_seq() {
    let v2 = TestSkipV2::default();
    let bytes = serialize!(CompactProtocol, |w| Serialize::write(&v2, w));
    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(bytes));
    let v1: TestSkipV1 = Deserialize::read(&mut deserializer).unwrap();
    assert_eq!(v1, TestSkipV1::default());
}

#[test]
fn test_bytes_shared() {
    // Test that when using `Bytes` for `binary` types and for the buffer the
    // data is deserialized from, the deserialized structs share data with the
    // buffer.  Do this by deserializing the same `Bytes`-backed buffer twice
    // and checking both deserialized copies have the same address.  This
    // should be somewhere within the serialized buffer.
    let original = TestBytesShared {
        b: Bytes::from(&b"data"[..]),
        ..Default::default()
    };
    let bytes = serialize!(CompactProtocol, |w| Serialize::write(&original, w));
    let mut deserializer1 = <CompactProtocol>::deserializer(Cursor::new(bytes.clone()));
    let shared1: TestBytesShared = Deserialize::read(&mut deserializer1).unwrap();
    let mut deserializer2 = <CompactProtocol>::deserializer(Cursor::new(bytes));
    let shared2: TestBytesShared = Deserialize::read(&mut deserializer2).unwrap();
    assert_eq!(shared1.b.as_ptr() as usize, shared2.b.as_ptr() as usize);
}

#[test]
fn test_nonutf8_string() {
    // Serializing `binary` and deserializing to `string` is okay, as long as
    // the data is valid UTF-8. Thrift intentionally uses the same on-wire
    // format for these two types.
    let data = b"...".to_vec();
    let value = WrapBinary { data };
    let repr = serialize!(CompactProtocol, |w| Serialize::write(&value, w));
    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(repr));
    let value2 = WrapString::read(&mut deserializer).unwrap();
    assert_eq!("...", value2.data);

    // Same thing with non-UTF-8 data is not okay. It happens to work in C++
    // because their only string type holds arbitrary bytes, but not Rust or
    // other languages which enforce a string encoding.
    let data = b"..\xff".to_vec();
    let value = WrapBinary { data };
    let repr = serialize!(CompactProtocol, |w| Serialize::write(&value, w));
    let mut deserializer = <CompactProtocol>::deserializer(Cursor::new(repr));
    let error = WrapString::read(&mut deserializer).unwrap_err();
    let expected = "deserializing `string` from Thrift compact protocol got invalid utf-8, you need to use `binary` instead: invalid utf-8 sequence of 1 bytes from index 2";
    assert_eq!(expected, error.to_string());
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// If you want an adapter to refer to a Thrift struct defined in the Thrift file using the adapter,
// you have to use `rust_include_srcs` to avoid the target circular dependency.
use std::fmt::Debug;
use std::marker::PhantomData;

use anyhow::Context;
use fbthrift::adapter::ThriftAdapter;
use fbthrift::metadata::ThriftAnnotations;

use crate::types as crate_types;
use crate::types::AdaptedString;
use crate::types::Foo;
use crate::types::WrappedAdaptedBytes;

#[derive(Clone, Debug, PartialEq)]
pub struct Wrapper<T: Clone + Debug + PartialEq + Sync + Send>(pub T);

pub struct FieldCheckerAdapter {}

impl ThriftAdapter for FieldCheckerAdapter {
    type StandardType = String;
    type Error = anyhow::Error;

    type AdaptedType = String;

    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
        Ok(value)
    }

    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
        value.clone()
    }

    fn from_thrift_field<T: ThriftAnnotations>(
        value: Self::StandardType,
        field_id: i16,
    ) -> Result<Self::AdaptedType, Self::Error> {
        assert_eq!(field_id, 8);
        assert_eq!(std::any::TypeId::of::<Foo>(), std::any::TypeId::of::<T>());

        Self::from_thrift(value)
    }

    fn to_thrift_field<T: ThriftAnnotations>(
        value: &Self::AdaptedType,
        field_id: i16,
    ) -> Self::StandardType {
        assert_eq!(field_id, 8);
        assert_eq!(std::any::TypeId::of::<Foo>(), std::any::TypeId::of::<T>());

        Self::to_thrift(value)
    }
}

pub struct IOBufIdentityAdapter {}

impl ThriftAdapter for IOBufIdentityAdapter {
    type StandardType = bytes::Bytes;
    type Error = std::convert::Infallible;

    type AdaptedType = bytes::Bytes;

    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
        Ok(value)
    }

    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
        value.clone()
    }
}

pub struct WrappedAdaptedBytesIdentityAdapter {}

impl ThriftAdapter for WrappedAdaptedBytesIdentityAdapter {
    type StandardType = WrappedAdaptedBytes;
    type Error = std::convert::Infallible;

    type AdaptedType = WrappedAdaptedBytes;

    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
        Ok(value)
    }

    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
        value.clone()
    }
}

pub struct AdaptedStringListIdentityAdapter {}

impl ThriftAdapter for AdaptedStringListIdentityAdapter {
    type StandardType = Vec<AdaptedString>;
    type Error = std::convert::Infallible;

    type AdaptedType = Vec<AdaptedString>;

    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
        Ok(value)
    }

    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
        value.clone()
    }
}

#[derive(Debug, Default, Clone, PartialEq)]
pub enum AssetType {
    #[default]
    Unknown,
    Laptop,
    Server,
}

#[derive(Debug, Default, Clone, PartialEq)]
pub struct Asset {
    pub type_: AssetType,
    pub id: u32,
}

pub struct AssetAdapter {}

impl ThriftAdapter for AssetAdapter {
    type StandardType = crate_types::unadapted::Asset;
    type Error = anyhow::Error;

    type AdaptedType = Asset;

    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
        Ok(Asset {
            type_: match value.type_ {
                crate_types::ThriftAssetType::LAPTOP => AssetType::Laptop,
                crate_types::ThriftAssetType::SERVER => AssetType::Server,
                // Rust Thrift will NOT use 0 as the default enum value by default, but instead
                // will use i32::MIN.
                _ => AssetType::Unknown,
            },
            id: value.id.try_into().context("invalid i64 for id")?,
        })
    }

    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
        crate_types::unadapted::Asset {
            type_: match value.type_ {
                AssetType::Unknown => crate_types::ThriftAssetType::UNKNOWN,
                AssetType::Laptop => crate_types::ThriftAssetType::LAPTOP,
                AssetType::Server => crate_types::ThriftAssetType::SERVER,
            },
            id: value.id.into(),
            ..Default::default()
        }
    }
}

pub struct AdaptedAssetIdentityAdapter;

impl ThriftAdapter for AdaptedAssetIdentityAdapter {
    type StandardType = Asset;
    type Error = std::convert::Infallible;

    type AdaptedType = Asset;

    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
        Ok(value)
    }

    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
        value.clone()
    }
}

pub struct TransitiveTestAdapter<T>(PhantomData<T>);

impl<T> ThriftAdapter for TransitiveTestAdapter<T>
where
    T: Clone + Debug + PartialEq + Send + Sync,
{
    type StandardType = T;
    type AdaptedType = Wrapper<T>;

    type Error = std::convert::Infallible;

    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
        Ok(Wrapper(value))
    }

    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
        value.0.clone()
    }
}

pub struct AnnotationTestAdapter;

impl ThriftAdapter for AnnotationTestAdapter {
    type StandardType = String;
    type AdaptedType = Wrapper<String>;

    type Error = std::convert::Infallible;

    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
        Ok(Wrapper(value))
    }

    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
        value.0.clone()
    }

    fn from_thrift_field<T: ThriftAnnotations>(
        value: Self::StandardType,
        field_id: i16,
    ) -> Result<Self::AdaptedType, Self::Error> {
        assert_eq!(field_id, 1);
        assert_eq!(
            std::any::TypeId::of::<crate_types::unadapted::TransitiveStruct>(),
            std::any::TypeId::of::<T>()
        );

        assert_eq!(
            T::get_structured_annotation::<crate_types::unadapted::TransitiveAdapterAnnotation>()
                .unwrap()
                .payload,
            "hello_world"
        );
        assert_eq!(
            T::get_structured_annotation::<crate_types::FirstAnnotation>()
                .unwrap()
                .uri,
            "thrift/test"
        );

        Self::from_thrift(value)
    }

    fn to_thrift_field<T: ThriftAnnotations>(
        value: &Self::AdaptedType,
        field_id: i16,
    ) -> Self::StandardType {
        assert_eq!(field_id, 1);
        assert_eq!(
            std::any::TypeId::of::<crate_types::unadapted::TransitiveStruct>(),
            std::any::TypeId::of::<T>()
        );

        assert_eq!(
            T::get_structured_annotation::<crate_types::unadapted::TransitiveAdapterAnnotation>()
                .unwrap()
                .payload,
            "hello_world"
        );
        assert_eq!(
            T::get_structured_annotation::<crate_types::FirstAnnotation>()
                .unwrap()
                .uri,
            "thrift/test"
        );

        Self::to_thrift(value)
    }
}

pub struct TransitiveAnnotationTestAdapter<T>(PhantomData<T>);

impl<T> ThriftAdapter for TransitiveAnnotationTestAdapter<T>
where
    T: Clone + Debug + PartialEq + Sync + Send,
{
    type StandardType = T;
    type AdaptedType = Wrapper<T>;

    type Error = std::convert::Infallible;

    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
        Ok(Wrapper(value))
    }

    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
        value.0.clone()
    }

    fn from_thrift_field<S: ThriftAnnotations>(
        value: Self::StandardType,
        field_id: i16,
    ) -> Result<Self::AdaptedType, Self::Error> {
        assert_eq!(field_id, 2);
        assert_eq!(
            std::any::TypeId::of::<crate_types::unadapted::TransitiveStruct>(),
            std::any::TypeId::of::<S>()
        );

        assert_eq!(
            S::get_structured_annotation::<crate_types::unadapted::TransitiveAdapterAnnotation>()
                .unwrap()
                .payload,
            "hello_world"
        );
        assert_eq!(
            S::get_structured_annotation::<crate_types::FirstAnnotation>()
                .unwrap()
                .uri,
            "thrift/test"
        );

        assert_eq!(
            S::get_field_structured_annotation::<
                crate_types::unadapted::TransitiveFieldAdapterAnnotation,
            >(field_id)
            .unwrap()
            .payload,
            "foobar"
        );
        assert_eq!(
            S::get_field_structured_annotation::<crate_types::FirstAnnotation>(field_id)
                .unwrap()
                .uri,
            "thrift/transitive_field_test"
        );

        Self::from_thrift(value)
    }

    fn to_thrift_field<S: ThriftAnnotations>(
        value: &Self::AdaptedType,
        field_id: i16,
    ) -> Self::StandardType {
        assert_eq!(field_id, 2);
        assert_eq!(
            std::any::TypeId::of::<crate_types::unadapted::TransitiveStruct>(),
            std::any::TypeId::of::<S>()
        );

        assert_eq!(
            S::get_structured_annotation::<crate_types::unadapted::TransitiveAdapterAnnotation>()
                .unwrap()
                .payload,
            "hello_world"
        );
        assert_eq!(
            S::get_structured_annotation::<crate_types::FirstAnnotation>()
                .unwrap()
                .uri,
            "thrift/test"
        );

        assert_eq!(
            S::get_field_structured_annotation::<
                crate_types::unadapted::TransitiveFieldAdapterAnnotation,
            >(field_id)
            .unwrap()
            .payload,
            "foobar"
        );
        assert_eq!(
            S::get_field_structured_annotation::<crate_types::FirstAnnotation>(field_id)
                .unwrap()
                .uri,
            "thrift/transitive_field_test"
        );

        Self::to_thrift(value)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::fmt::Debug;
use std::marker::PhantomData;
use std::num::NonZeroI64;

use fbthrift::adapter::ThriftAdapter;

#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct CustomString(pub String);

pub struct StringAdapter {}

impl ThriftAdapter for StringAdapter {
    type StandardType = String;
    type Error = std::convert::Infallible;

    type AdaptedType = CustomString;

    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
        Ok(CustomString(value))
    }

    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
        value.0.clone()
    }
}

pub struct NonZeroI64Adapter {}

impl ThriftAdapter for NonZeroI64Adapter {
    type StandardType = i64;
    type Error = anyhow::Error;

    type AdaptedType = NonZeroI64;

    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
        match NonZeroI64::new(value) {
            Some(v) => Ok(v),
            None => {
                anyhow::bail!("Given i64 is not non-zero: {}", value);
            }
        }
    }

    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
        value.get()
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct SortedVec(pub Vec<String>);

pub struct ListAdapter {}

impl ThriftAdapter for ListAdapter {
    type StandardType = Vec<String>;
    type Error = std::convert::Infallible;

    type AdaptedType = SortedVec;

    fn from_thrift(mut value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
        value.sort();

        Ok(SortedVec(value))
    }

    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
        value.0.clone()
    }
}

pub struct IdentityAdapter<T>
where
    T: Clone + Debug + Send + Sync + PartialEq,
{
    inner: PhantomData<T>,
}

impl<T> ThriftAdapter for IdentityAdapter<T>
where
    T: Clone + Debug + Send + Sync + PartialEq,
{
    type StandardType = T;
    type AdaptedType = T;

    type Error = std::convert::Infallible;

    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
        Ok(value)
    }

    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
        value.clone()
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::collections::BTreeMap;
use std::num::NonZeroI64;

use adapters::CustomString;
use adapters::SortedVec;
use fbthrift::simplejson_protocol;
use thrift_test::consts;
use thrift_test::types::Asset;
use thrift_test::types::Bar;
use thrift_test::types::Foo;
use thrift_test::types::TransitiveStructWrapper;
use thrift_test::types::WrappedAdaptedBytes;
use thrift_test::types::WrappedAdaptedString;
use thrift_test::types::WrappedAdaptedWrappedAdaptedBytes;
use thrift_test::AdaptedListNewType;
use thrift_test::AssetType;

#[test]
fn test_foo_default() {
    let default_foos: Vec<Foo> = vec![
        Foo::default(),
        simplejson_protocol::deserialize("{}").unwrap(),
    ];

    for default_foo in default_foos {
        assert_eq!(default_foo.str_val, "");
        assert_eq!(default_foo.int_val, 0);
        assert_eq!(default_foo.str_val_adapted, CustomString("".to_string()));
        assert_eq!(
            default_foo.str_val_adapted_default,
            CustomString("hello".to_string())
        );
        assert_eq!(default_foo.str_val_adapted_optional, None);
        assert_eq!(default_foo.validated_int_val, NonZeroI64::new(1).unwrap());
        assert_eq!(
            default_foo.list_val,
            SortedVec(vec!["ispum".to_string(), "lorem".to_string()])
        );
        assert_eq!(default_foo.ident_field, "".to_string());
        assert_eq!(default_foo.typedef_str_val, CustomString("".to_string()));
        assert_eq!(
            default_foo.pass_through_adapted_i64,
            NonZeroI64::new(42).unwrap()
        );
        assert_eq!(
            default_foo.pass_through_adapted_and_field_i64,
            NonZeroI64::new(2).unwrap()
        );
        assert_eq!(
            default_foo.adapted_int_list,
            vec![
                NonZeroI64::new(1).unwrap(),
                NonZeroI64::new(2).unwrap(),
                NonZeroI64::new(3).unwrap()
            ]
        );
        assert_eq!(
            default_foo.adapted_string_list,
            vec![
                CustomString("hello".to_string()),
                CustomString("world".to_string()),
            ]
        );
        assert_eq!(
            default_foo.nested_adapted_string_list,
            vec![vec![vec![
                CustomString("hello".to_string()),
                CustomString("world".to_string()),
            ]]]
        );
        assert_eq!(
            default_foo.field_adapted_adapted_string_list,
            vec![CustomString("zucc".to_string()),]
        );
        assert_eq!(
            default_foo.adapted_struct,
            Asset {
                type_: AssetType::Unknown,
                id: 0
            }
        );
        assert_eq!(
            default_foo.double_adapted_struct,
            Asset {
                type_: AssetType::Unknown,
                id: 0
            }
        );
        assert_eq!(
            default_foo.adapted_struct_list,
            vec![Asset {
                type_: AssetType::Laptop,
                id: 10
            }]
        );
        assert_eq!(
            default_foo.adapted_list_new_type,
            AdaptedListNewType(vec![
                CustomString("hi".to_string()),
                CustomString("there".to_string())
            ])
        );
        assert_eq!(default_foo.map_with_adapted_key_val, {
            let mut map = BTreeMap::new();
            map.insert(
                CustomString("what".to_string()),
                WrappedAdaptedWrappedAdaptedBytes(WrappedAdaptedBytes("are those?".into())),
            );

            map
        });
        assert_eq!(default_foo.map_with_adapted_wrapped_key_val, {
            let mut map = BTreeMap::new();
            map.insert(
                WrappedAdaptedString(CustomString("marco".to_string())),
                WrappedAdaptedWrappedAdaptedBytes(WrappedAdaptedBytes("polo".into())),
            );

            map
        });

        assert_eq!(
            r#"{
          "str_val": "",
          "int_val": 0,
          "str_val_adapted": "",
          "str_val_adapted_default": "hello",
          "validated_int_val": 1,
          "list_val": ["ispum", "lorem"],
          "field_checked": "",
          "ident_field": "",
          "typedef_str_val": "",
          "pass_through_adapted_i64": 42,
          "pass_through_adapted_and_field_i64": 2,
          "adapted_int_list": [1, 2, 3],
          "adapted_string_list": ["hello", "world"],
          "nested_adapted_string_list": [[["hello", "world"]]],
          "nested_adapted_int_map": {
            "hello": [[[1, 2, 3], [4, 5, 6]]],
            "world": [[[7, 8, 9]]]
          },
          "field_adapted_adapted_string_list": ["zucc"],
          "adapted_struct": {"type_": 0, "id": 0, "id1": 1, "id2": 2, "id3": 3, "comment": ""},
          "double_adapted_struct": {"type_": 0, "id": 0, "id1": 1, "id2": 2, "id3": 3, "comment": ""},
          "adapted_struct_list": [{"type_": 1, "id": 10, "id1": 1, "id2": 2, "id3": 3, "comment": ""}],
          "adapted_list_new_type": ["hi", "there"],
          "map_with_adapted_key_val": {"what": "YXJlIHRob3NlPw"},
          "map_with_adapted_wrapped_key_val": {"marco": "cG9sbw"}
        }"#
            .replace(['\n', ' '], ""),
            String::from_utf8(simplejson_protocol::serialize(default_foo).into()).unwrap()
        );
    }
}

#[test]
fn test_foo_deser() {
    let foo: Foo = simplejson_protocol::deserialize(
        r#"{
          "str_val": "rust",
          "int_val": 3,
          "str_val_adapted": "python",
          "str_val_adapted_default": "c++",
          "str_val_adapted_optional": "golang",
          "validated_int_val": 42,
          "list_val": ["zzz", "hi", "there"],
          "pass_through_adapted_and_field_i64": 100
        }"#,
    )
    .unwrap();

    assert_eq!(foo.str_val, "rust");
    assert_eq!(foo.int_val, 3);
    assert_eq!(foo.str_val_adapted, CustomString("python".to_string()));
    assert_eq!(foo.str_val_adapted_default, CustomString("c++".to_string()));
    assert_eq!(
        foo.str_val_adapted_optional,
        Some(CustomString("golang".to_string()))
    );
    assert_eq!(foo.validated_int_val, NonZeroI64::new(42).unwrap());
    assert_eq!(
        foo.list_val,
        SortedVec(vec![
            "hi".to_string(),
            "there".to_string(),
            "zzz".to_string()
        ])
    );
    assert_eq!(
        foo.pass_through_adapted_and_field_i64,
        NonZeroI64::new(100).unwrap()
    );
}

#[test]
fn test_foo_deser_fail() {
    let deser_result: Result<Foo, anyhow::Error> = simplejson_protocol::deserialize(
        r#"{
          "str_val": "rust",
          "int_val": 3,
          "str_val_adapted": "python",
          "str_val_adapted_default": "c++",
          "str_val_adapted_optional": "golang",
          "validated_int_val": 0,
          "list_val": ["zzz", "hi", "there"]
        }"#,
    );

    assert!(deser_result.is_err());
    assert_eq!(
        deser_result.unwrap_err().to_string(),
        "Given i64 is not non-zero: 0"
    );
}

#[test]
fn test_foo_ser() {
    let foo = Foo {
        str_val: "rust".to_string(),
        int_val: 3,
        str_val_adapted: CustomString("python".to_string()),
        str_val_adapted_default: CustomString("c++".to_string()),
        str_val_adapted_optional: Some(CustomString("golang".to_string())),
        validated_int_val: NonZeroI64::new(42).unwrap(),
        list_val: SortedVec(vec![
            "hi".to_string(),
            "there".to_string(),
            "zzz".to_string(),
        ]),
        ident_field: "foobar".to_string(),
        typedef_str_val: CustomString("haskell".to_string()),
        pass_through_adapted_i64: NonZeroI64::new(13).unwrap(),
        pass_through_adapted_and_field_i64: NonZeroI64::new(14).unwrap(),
        adapted_int_list: vec![NonZeroI64::new(15).unwrap()],
        adapted_string_list: vec![CustomString("java".to_string())],
        nested_adapted_string_list: vec![vec![vec![CustomString("ada".to_string())]]],
        ..Default::default()
    };

    assert_eq!(
        r#"{
          "str_val": "rust",
          "int_val": 3,
          "str_val_adapted": "python",
          "str_val_adapted_default": "c++",
          "str_val_adapted_optional": "golang",
          "validated_int_val": 42,
          "list_val": ["hi", "there", "zzz"],
          "field_checked": "",
          "ident_field": "foobar",
          "typedef_str_val": "haskell",
          "pass_through_adapted_i64": 13,
          "pass_through_adapted_and_field_i64": 14,
          "adapted_int_list": [15],
          "adapted_string_list": ["java"],
          "nested_adapted_string_list": [[["ada"]]],
          "nested_adapted_int_map": {
            "hello": [[[1, 2, 3], [4, 5, 6]]],
            "world": [[[7, 8, 9]]]
          },
          "field_adapted_adapted_string_list": ["zucc"],
          "adapted_struct": {"type_": 0, "id": 0, "id1": 1, "id2": 2, "id3": 3, "comment": ""},
          "double_adapted_struct": {"type_": 0, "id": 0, "id1": 1, "id2": 2, "id3": 3, "comment": ""},
          "adapted_struct_list": [{"type_": 1, "id": 10, "id1": 1, "id2": 2, "id3": 3, "comment": ""}],
          "adapted_list_new_type": ["hi", "there"],
          "map_with_adapted_key_val": {"what": "YXJlIHRob3NlPw"},
          "map_with_adapted_wrapped_key_val": {"marco": "cG9sbw"}
        }"#
        .replace(['\n', ' '], ""),
        std::string::String::from_utf8(simplejson_protocol::serialize(foo).into()).unwrap()
    );
}

#[test]
fn test_bar_default() {
    assert_eq!(Bar::UnknownField(-1), Bar::default());
}

#[test]
fn test_bar_deser() {
    let bar: Bar = simplejson_protocol::deserialize(
        r#"{
          "list_val": ["world", "hello"]
        }"#,
    )
    .unwrap();

    assert_eq!(
        Bar::list_val(SortedVec(vec!["hello".to_string(), "world".to_string()])),
        bar
    );

    let bar: Bar = simplejson_protocol::deserialize(
        r#"{
          "str_val": "zucc"
        }"#,
    )
    .unwrap();

    assert_eq!(Bar::str_val(CustomString("zucc".to_string())), bar);

    let bar: Bar = simplejson_protocol::deserialize(
        r#"{
          "str_val_not_adapted": "zucc"
        }"#,
    )
    .unwrap();

    assert_eq!(Bar::str_val_not_adapted("zucc".to_string()), bar);

    let bar: Bar = simplejson_protocol::deserialize(
        r#"{
          "validated_int_val": 42
        }"#,
    )
    .unwrap();

    assert_eq!(Bar::validated_int_val(NonZeroI64::new(42).unwrap()), bar);

    let bar: Bar = simplejson_protocol::deserialize(r#"{}"#).unwrap();

    assert_eq!(Bar::UnknownField(-1), bar);

    let bar: Bar = simplejson_protocol::deserialize(
        r#"{
          "does_not_exist": 42
        }"#,
    )
    .unwrap();

    assert_eq!(Bar::UnknownField(-1), bar);
}

#[test]
fn test_bar_deser_failure() {
    let deser_result: Result<Bar, anyhow::Error> = simplejson_protocol::deserialize(
        r#"{
          "validated_int_val": 0,
        }"#,
    );

    assert!(deser_result.is_err());
    assert_eq!(
        deser_result.unwrap_err().to_string(),
        "Given i64 is not non-zero: 0"
    );
}

#[test]
fn test_bar_ser() {
    assert_eq!(
        String::from_utf8(
            simplejson_protocol::serialize(Bar::list_val(SortedVec(vec![
                "hello".to_string(),
                "world".to_string()
            ])))
            .into()
        )
        .unwrap(),
        r#"{
          "list_val": ["hello", "world"]
        }"#
        .replace(['\n', ' '], "")
    );

    assert_eq!(
        String::from_utf8(
            simplejson_protocol::serialize(Bar::str_val_not_adapted("zucc".to_string())).into()
        )
        .unwrap(),
        r#"{
          "str_val_not_adapted": "zucc"
        }"#
        .replace(['\n', ' '], "")
    );
}

#[test]
fn test_newtype() {
    let wrapped = WrappedAdaptedWrappedAdaptedBytes(WrappedAdaptedBytes("hello world".into()));

    assert_eq!(
        String::from_utf8(simplejson_protocol::serialize(wrapped.clone()).into()).unwrap(),
        r#""aGVsbG8gd29ybGQ""#
    );

    // The '=' is just a padding and can be omitted if desired.
    let deser: WrappedAdaptedWrappedAdaptedBytes =
        simplejson_protocol::deserialize(r#""aGVsbG8gd29ybGQ=""#).unwrap();

    assert_eq!(wrapped, deser);
}

#[test]
fn test_consts() {
    assert_eq!(*consts::adapted_int, NonZeroI64::new(5).unwrap());
    assert_eq!(
        *consts::pass_through_adapted_int,
        NonZeroI64::new(6).unwrap()
    );
    assert_eq!(
        *consts::adapted_bytes_const,
        WrappedAdaptedWrappedAdaptedBytes(WrappedAdaptedBytes("some_bytes".into()))
    );
    assert_eq!(
        *consts::adapted_list_const,
        AdaptedListNewType(vec![
            CustomString("hello".to_string()),
            CustomString("world".to_string())
        ])
    );
    assert_eq!(
        *consts::adapted_struct_const,
        Asset {
            type_: AssetType::Server,
            id: 42,
        }
    );
}

#[test]
fn test_annotations() {
    let deser: TransitiveStructWrapper =
        simplejson_protocol::deserialize(r#"{"test_field": "boo", "test_field_2": "spooky"}"#)
            .unwrap();

    assert_eq!(deser.0.0.test_field.0, "boo");
    assert_eq!(deser.0.0.test_field_2.0, "spooky");

    assert_eq!(
        simplejson_protocol::serialize(deser),
        r#"{"test_field":"boo","test_field_2":"spooky"}"#
    );
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::error::Error;

use fbthrift::application_exception::ApplicationException;
use fbthrift::application_exception::ApplicationExceptionErrorCode;
use k9::snapshot;
use thrift_test::errors::test_service::DoFooError;
use thrift_test::errors::test_service::DoFooStreamStreamError;
use thrift_test::errors::test_service::DoNoThrowError;
use thrift_test::types::ErrorCode;
use thrift_test::types::InternalError;
use thrift_test::types::RetryableError;

fn make_ie() -> DoFooError {
    DoFooError::ie(InternalError {
        code: ErrorCode::INVALID_VALUE,
        message: "got some invalid value".to_string(),
        ..Default::default()
    })
}

#[test]
fn test_internal_error() {
    let ie = make_ie();

    snapshot!(
        format!("{}", ie),
        "TestService::do_foo failed with ie(InternalError)"
    );
    snapshot!(
        format!("{:#}", ie),
        "TestService::do_foo failed with variant `ie`: InternalError: got some invalid value"
    );
    snapshot!(
        format!("{:?}", ie),
        "ie(InternalError { code: ErrorCode::INVALID_VALUE, message: \"got some invalid value\" })"
    );
    snapshot!(
        format!("{:#?}", ie),
        r#"
ie(
    InternalError {
        code: ErrorCode::INVALID_VALUE,
        message: "got some invalid value",
    },
)
"#
    );

    snapshot!(
        format!("{}", Into::<anyhow::Error>::into(make_ie())),
        "TestService::do_foo failed with ie(InternalError)"
    );
    snapshot!(
        format!("{:#}", Into::<anyhow::Error>::into(make_ie())),
        "TestService::do_foo failed with ie(InternalError): InternalError: got some invalid value"
    );
    snapshot!(
        format!("{:?}", Into::<anyhow::Error>::into(make_ie())),
        "
TestService::do_foo failed with ie(InternalError)

Caused by:
    InternalError: got some invalid value
"
    );
    snapshot!(
        format!("{:#?}", Into::<anyhow::Error>::into(make_ie())),
        r#"
ie(
    InternalError {
        code: ErrorCode::INVALID_VALUE,
        message: "got some invalid value",
    },
)
"#
    );

    assert_eq!(
        ie.source()
            .unwrap()
            .downcast_ref::<InternalError>()
            .unwrap()
            .message,
        "got some invalid value"
    );
}

fn make_ie_stream() -> DoFooStreamStreamError {
    DoFooStreamStreamError::ie(InternalError {
        code: ErrorCode::INVALID_VALUE,
        message: "got some invalid value".to_string(),
        ..Default::default()
    })
}

#[test]
fn test_internal_error_stream() {
    let ie = make_ie_stream();

    snapshot!(
        format!("{}", ie),
        "TestService::do_foo_stream failed with ie(InternalError)"
    );
    snapshot!(
        format!("{:#}", ie),
        "TestService::do_foo_stream failed with variant `ie`: InternalError: got some invalid value"
    );
    snapshot!(
        format!("{:?}", ie),
        "ie(InternalError { code: ErrorCode::INVALID_VALUE, message: \"got some invalid value\" })"
    );
    snapshot!(
        format!("{:#?}", ie),
        "
ie(
    InternalError {
        code: ErrorCode::INVALID_VALUE,
        message: \"got some invalid value\",
    },
)
"
    );

    snapshot!(
        format!("{}", Into::<anyhow::Error>::into(make_ie_stream())),
        "TestService::do_foo_stream failed with ie(InternalError)"
    );
    snapshot!(
        format!("{:#}", Into::<anyhow::Error>::into(make_ie_stream())),
        "TestService::do_foo_stream failed with ie(InternalError): InternalError: got some invalid value"
    );
    snapshot!(
        format!("{:?}", Into::<anyhow::Error>::into(make_ie_stream())),
        "
TestService::do_foo_stream failed with ie(InternalError)

Caused by:
    InternalError: got some invalid value
"
    );
    snapshot!(
        format!("{:#?}", Into::<anyhow::Error>::into(make_ie_stream())),
        "
ie(
    InternalError {
        code: ErrorCode::INVALID_VALUE,
        message: \"got some invalid value\",
    },
)
"
    );

    assert_eq!(
        ie.source()
            .unwrap()
            .downcast_ref::<InternalError>()
            .unwrap()
            .message,
        "got some invalid value"
    );
}

fn make_re() -> DoFooError {
    DoFooError::re(RetryableError {
        code: ErrorCode::INVALID_VALUE,
        ..Default::default()
    })
}

#[test]
fn test_retryable_error() {
    let re = make_re();

    snapshot!(
        format!("{}", re),
        "TestService::do_foo failed with re(RetryableError)"
    );
    snapshot!(
        format!("{:#}", re),
        "TestService::do_foo failed with variant `re`: RetryableError { code: ErrorCode::INVALID_VALUE }"
    );
    snapshot!(
        format!("{:?}", re),
        "re(RetryableError { code: ErrorCode::INVALID_VALUE })"
    );
    snapshot!(
        format!("{:#?}", re),
        "
re(
    RetryableError {
        code: ErrorCode::INVALID_VALUE,
    },
)
"
    );

    snapshot!(
        format!("{}", Into::<anyhow::Error>::into(make_re())),
        "TestService::do_foo failed with re(RetryableError)"
    );
    snapshot!(
        format!("{:#}", Into::<anyhow::Error>::into(make_re())),
        "TestService::do_foo failed with re(RetryableError): RetryableError { code: ErrorCode::INVALID_VALUE }"
    );
    snapshot!(
        format!("{:?}", Into::<anyhow::Error>::into(make_re())),
        "
TestService::do_foo failed with re(RetryableError)

Caused by:
    RetryableError { code: ErrorCode::INVALID_VALUE }
"
    );
    snapshot!(
        format!("{:#?}", Into::<anyhow::Error>::into(make_re())),
        "
re(
    RetryableError {
        code: ErrorCode::INVALID_VALUE,
    },
)
"
    );

    assert_eq!(
        re.source()
            .unwrap()
            .downcast_ref::<RetryableError>()
            .unwrap()
            .code,
        ErrorCode::INVALID_VALUE
    );
}

fn make_re_stream() -> DoFooStreamStreamError {
    DoFooStreamStreamError::re(RetryableError {
        code: ErrorCode::INVALID_VALUE,
        ..Default::default()
    })
}

#[test]
fn test_retryable_error_stream() {
    let re = make_re_stream();

    snapshot!(
        format!("{}", re),
        "TestService::do_foo_stream failed with re(RetryableError)"
    );
    snapshot!(
        format!("{:#}", re),
        "TestService::do_foo_stream failed with variant `re`: RetryableError { code: ErrorCode::INVALID_VALUE }"
    );
    snapshot!(
        format!("{:?}", re),
        "re(RetryableError { code: ErrorCode::INVALID_VALUE })"
    );
    snapshot!(
        format!("{:#?}", re),
        "
re(
    RetryableError {
        code: ErrorCode::INVALID_VALUE,
    },
)
"
    );

    snapshot!(
        format!("{}", Into::<anyhow::Error>::into(make_re_stream())),
        "TestService::do_foo_stream failed with re(RetryableError)"
    );
    snapshot!(
        format!("{:#}", Into::<anyhow::Error>::into(make_re_stream())),
        "TestService::do_foo_stream failed with re(RetryableError): RetryableError { code: ErrorCode::INVALID_VALUE }"
    );
    snapshot!(
        format!("{:?}", Into::<anyhow::Error>::into(make_re_stream())),
        "
TestService::do_foo_stream failed with re(RetryableError)

Caused by:
    RetryableError { code: ErrorCode::INVALID_VALUE }
"
    );
    snapshot!(
        format!("{:#?}", Into::<anyhow::Error>::into(make_re_stream())),
        "
re(
    RetryableError {
        code: ErrorCode::INVALID_VALUE,
    },
)
"
    );

    assert_eq!(
        re.source()
            .unwrap()
            .downcast_ref::<RetryableError>()
            .unwrap()
            .code,
        ErrorCode::INVALID_VALUE
    );
}

fn make_ae() -> DoFooError {
    DoFooError::ApplicationException(ApplicationException {
        message: "some injected failure".to_string(),
        type_: ApplicationExceptionErrorCode::InjectedFailure,
        ..Default::default()
    })
}

#[test]
fn test_application_exception() {
    let ae = make_ae();

    snapshot!(
        format!("{}", ae),
        "TestService::do_foo failed with ApplicationException"
    );
    snapshot!(
        format!("{:#}", ae),
        "TestService::do_foo failed with ApplicationException: InjectedFailure: some injected failure"
    );
    snapshot!(
        format!("{:?}", ae),
        r#"ApplicationException(ApplicationException { message: "some injected failure", type_: InjectedFailure })"#
    );
    snapshot!(
        format!("{:#?}", ae),
        r#"
ApplicationException(
    ApplicationException {
        message: "some injected failure",
        type_: InjectedFailure,
    },
)
"#
    );

    snapshot!(
        format!("{}", Into::<anyhow::Error>::into(make_ae())),
        "TestService::do_foo failed with ApplicationException"
    );
    snapshot!(
        format!("{:#}", Into::<anyhow::Error>::into(make_ae())),
        "TestService::do_foo failed with ApplicationException: InjectedFailure: some injected failure"
    );
    snapshot!(
        format!("{:?}", Into::<anyhow::Error>::into(make_ae())),
        "
TestService::do_foo failed with ApplicationException

Caused by:
    InjectedFailure: some injected failure
"
    );
    snapshot!(
        format!("{:#?}", Into::<anyhow::Error>::into(make_ae())),
        r#"
ApplicationException(
    ApplicationException {
        message: "some injected failure",
        type_: InjectedFailure,
    },
)
"#
    );

    assert_eq!(
        ae.source()
            .unwrap()
            .downcast_ref::<ApplicationException>()
            .unwrap()
            .type_,
        ApplicationExceptionErrorCode::InjectedFailure
    );
}

fn make_ae_stream() -> DoFooStreamStreamError {
    DoFooStreamStreamError::ApplicationException(ApplicationException {
        message: "some injected failure".to_string(),
        type_: ApplicationExceptionErrorCode::InjectedFailure,
        ..Default::default()
    })
}

#[test]
fn test_application_exception_stream() {
    let ae = make_ae_stream();

    snapshot!(
        format!("{}", ae),
        "TestService::do_foo_stream failed with ApplicationException"
    );
    snapshot!(
        format!("{:#}", ae),
        "TestService::do_foo_stream failed with ApplicationException: InjectedFailure: some injected failure"
    );
    snapshot!(
        format!("{:?}", ae),
        r#"ApplicationException(ApplicationException { message: "some injected failure", type_: InjectedFailure })"#
    );
    snapshot!(
        format!("{:#?}", ae),
        r#"
ApplicationException(
    ApplicationException {
        message: "some injected failure",
        type_: InjectedFailure,
    },
)
"#
    );

    snapshot!(
        format!("{}", Into::<anyhow::Error>::into(make_ae_stream())),
        "TestService::do_foo_stream failed with ApplicationException"
    );
    snapshot!(
        format!("{:#}", Into::<anyhow::Error>::into(make_ae_stream())),
        "TestService::do_foo_stream failed with ApplicationException: InjectedFailure: some injected failure"
    );
    snapshot!(
        format!("{:?}", Into::<anyhow::Error>::into(make_ae_stream())),
        "
TestService::do_foo_stream failed with ApplicationException

Caused by:
    InjectedFailure: some injected failure
"
    );
    snapshot!(
        format!("{:#?}", Into::<anyhow::Error>::into(make_ae_stream())),
        r#"
ApplicationException(
    ApplicationException {
        message: "some injected failure",
        type_: InjectedFailure,
    },
)
"#
    );

    assert_eq!(
        ae.source()
            .unwrap()
            .downcast_ref::<ApplicationException>()
            .unwrap()
            .type_,
        ApplicationExceptionErrorCode::InjectedFailure
    );
}

fn make_te() -> DoFooError {
    DoFooError::ThriftError(anyhow::anyhow!("foo").context("bar").context("baz"))
}

#[test]
fn test_thrift_error() {
    let te = make_te();

    snapshot!(
        format!("{}", te),
        "TestService::do_foo failed with ThriftError"
    );
    snapshot!(
        format!("{:#}", te),
        "TestService::do_foo failed with ThriftError: baz: bar: foo"
    );
    snapshot!(
        format!("{:?}", te),
        "
ThriftError(baz

Caused by:
    0: bar
    1: foo)
"
    );
    snapshot!(
        format!("{:#?}", te),
        r#"
ThriftError(
    Error {
        context: "baz",
        source: Error {
            context: "bar",
            source: "foo",
        },
    },
)
"#
    );

    snapshot!(
        format!("{}", Into::<anyhow::Error>::into(make_te())),
        "TestService::do_foo failed with ThriftError"
    );
    snapshot!(
        format!("{:#}", Into::<anyhow::Error>::into(make_te())),
        "TestService::do_foo failed with ThriftError: baz: bar: foo"
    );
    snapshot!(
        format!("{:?}", Into::<anyhow::Error>::into(make_te())),
        "
TestService::do_foo failed with ThriftError

Caused by:
    0: baz
    1: bar
    2: foo
"
    );
    snapshot!(
        format!("{:#?}", Into::<anyhow::Error>::into(make_te())),
        r#"
ThriftError(
    Error {
        context: "baz",
        source: Error {
            context: "bar",
            source: "foo",
        },
    },
)
"#
    );
}

fn make_te_stream() -> DoFooStreamStreamError {
    DoFooStreamStreamError::ThriftError(anyhow::anyhow!("foo").context("bar").context("baz"))
}

#[test]
fn test_thrift_error_stream() {
    let te = make_te_stream();

    snapshot!(
        format!("{}", te),
        "TestService::do_foo_stream failed with ThriftError"
    );
    snapshot!(
        format!("{:#}", te),
        "TestService::do_foo_stream failed with ThriftError: baz: bar: foo"
    );
    snapshot!(
        format!("{:?}", te),
        "
ThriftError(baz

Caused by:
    0: bar
    1: foo)
"
    );
    snapshot!(
        format!("{:#?}", te),
        r#"
ThriftError(
    Error {
        context: "baz",
        source: Error {
            context: "bar",
            source: "foo",
        },
    },
)
"#
    );

    snapshot!(
        format!("{}", Into::<anyhow::Error>::into(make_te_stream())),
        "TestService::do_foo_stream failed with ThriftError"
    );
    snapshot!(
        format!("{:#}", Into::<anyhow::Error>::into(make_te_stream())),
        "TestService::do_foo_stream failed with ThriftError: baz: bar: foo"
    );
    snapshot!(
        format!("{:?}", Into::<anyhow::Error>::into(make_te_stream())),
        "
TestService::do_foo_stream failed with ThriftError

Caused by:
    0: baz
    1: bar
    2: foo
"
    );
    snapshot!(
        format!("{:#?}", Into::<anyhow::Error>::into(make_te_stream())),
        r#"
ThriftError(
    Error {
        context: "baz",
        source: Error {
            context: "bar",
            source: "foo",
        },
    },
)
"#
    );
}

fn make_no_throw_error() -> DoNoThrowError {
    DoNoThrowError::ThriftError(
        anyhow::anyhow!("TransportError: failed to connect to host").context("some client failed"),
    )
}

#[test]
fn test_no_throw_error() {
    let no_throw_err = make_no_throw_error();

    snapshot!(format!("{}", no_throw_err), "some client failed");
    snapshot!(
        format!("{:#}", no_throw_err),
        "some client failed: TransportError: failed to connect to host"
    );
    snapshot!(
        format!("{:?}", no_throw_err),
        "
ThriftError(some client failed

Caused by:
    TransportError: failed to connect to host)
"
    );
    snapshot!(
        format!("{:#?}", no_throw_err),
        r#"
ThriftError(
    Error {
        context: "some client failed",
        source: "TransportError: failed to connect to host",
    },
)
"#
    );

    snapshot!(
        format!("{}", Into::<anyhow::Error>::into(make_no_throw_error())),
        "some client failed"
    );
    snapshot!(
        format!("{:#}", Into::<anyhow::Error>::into(make_no_throw_error())),
        "some client failed: TransportError: failed to connect to host"
    );
    snapshot!(
        format!("{:?}", Into::<anyhow::Error>::into(make_no_throw_error())),
        "
some client failed

Caused by:
    TransportError: failed to connect to host
"
    );
    snapshot!(
        format!("{:#?}", Into::<anyhow::Error>::into(make_no_throw_error())),
        r#"
ThriftError(
    Error {
        context: "some client failed",
        source: "TransportError: failed to connect to host",
    },
)
"#
    );
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::fmt::Debug;
use std::marker::PhantomData;

pub use ::get_struct_name::GetStructName;
pub use ::get_struct_name_derive::GetStructName;
use fbthrift::adapter::ThriftAdapter;

pub struct WrapperAdapter<T>(PhantomData<T>);

#[derive(Clone, Debug, PartialEq)]
pub struct Wrapper<T: Clone + Debug + PartialEq + Sync + Send>(pub T);

impl<T> ThriftAdapter for WrapperAdapter<T>
where
    T: Clone + Debug + PartialEq + Sync + Send,
{
    type StandardType = T;
    type AdaptedType = Wrapper<T>;

    type Error = std::convert::Infallible;

    fn from_thrift(value: Self::StandardType) -> Result<Self::AdaptedType, Self::Error> {
        Ok(Wrapper(value))
    }

    fn to_thrift(value: &Self::AdaptedType) -> Self::StandardType {
        value.0.clone()
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

pub trait GetStructName {
    fn get_struct_name() -> &'static str;
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use proc_macro::TokenStream;
use quote::quote;

#[proc_macro_derive(GetStructName)]
pub fn standalone_subcommand_derive(input: TokenStream) -> TokenStream {
    let ast: syn::DeriveInput = syn::parse(input).unwrap();

    match ast.data {
        syn::Data::Struct(s) => s,
        _ => panic!("`#[derive(GetStructName)]` only supports Thrift structs"),
    };

    let name = &ast.ident;

    let name_string = name.to_string();

    let gen = quote! {
        impl ::get_struct_name::GetStructName for #name {
            fn get_struct_name() -> &'static str {
                #name_string
            }
        }
    };

    gen.into()
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use get_struct_name::types::GetStructName as GetStructNameThrift;
use get_struct_name::GetStructName;
use get_struct_name::Wrapper;
use thrift_test::types::Foo;

#[test]
fn test_struct_name() {
    assert_eq!(<Foo as GetStructName>::get_struct_name(), "Foo");
    assert_eq!(
        <GetStructNameThrift as GetStructName>::get_struct_name(),
        "GetStructName"
    );
}

#[test]
fn test_adapter() {
    let foo = Foo::default();

    assert_eq!(foo.bar, Wrapper("hello".to_string()));
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::collections::BTreeSet;

use anyhow::Result;
use regex::Regex;

struct CrateMapping {
    module: String,
    crate_name: String,
}

// Thrift writes a rust crate name mapping as follows:
// <thrift_module_name> <rust_crate_name>
//
// If you want to change this, please coordinate with dependent teams to ensure
// these libraries will still be able to divine the crate name.
fn parse_map(lines: &str) -> Vec<CrateMapping> {
    let re = Regex::new(r"(?m)^\s*([[:alnum:]_-]+)\s+([[:alnum:]_-]+)").unwrap();
    re.captures_iter(lines)
        .map(|caps| CrateMapping {
            module: caps[1].to_string(),
            crate_name: caps[2].to_string(),
        })
        .collect()
}

#[test]
fn test_crate_schema() -> Result<()> {
    let lines = include_str!("fubar_crate_map");
    let mappings = parse_map(lines);
    assert_eq!(mappings.len(), 2);
    let modules: BTreeSet<String> = mappings.iter().map(|m| m.module.clone()).collect();
    let crate_names: BTreeSet<String> = mappings.iter().map(|m| m.crate_name.clone()).collect();
    assert_eq!(crate_names.len(), 1);
    assert!(crate_names.contains("best_crate"));
    assert_eq!(modules.len(), 2);
    assert!(modules.contains("foos"));
    assert!(modules.contains("bars"));
    Ok(())
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::fmt;
use std::path::PathBuf;

use crate::types::PackType;
use crate::types::Platform;

#[derive(Debug)]
pub enum Error {
    MissingPackFile(Platform, PackType, PathBuf),
    MissingPlatformDefinition(Platform),
}

impl std::error::Error for Error {}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::MissingPackFile(platform, pack_type, path) => write!(
                f,
                "Couldn't open file to pack for platform {:?} and type {:?}: {}",
                platform,
                pack_type,
                path.to_string_lossy()
            ),
            Self::MissingPlatformDefinition(platform) => write!(
                f,
                "Platform {:?} is not defined in the given packlist.",
                platform
            ),
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! Intrinsic hash for a tarball.

use std::collections;
use std::io;

use anyhow::Result;

use crate::types;

/// Computes the intrinsic SHA256 checksum of a tar archive.
pub fn tarsum<R: io::Read>(reader: R) -> Result<types::HashSum> {
    use sha2::Digest;

    let mut archive = tar::Archive::new(reader);
    let mut map = collections::BTreeMap::new();

    // Store all entries in a BTreeMap using their path as key which implements `Ord`.
    // This way we ensure that the hash of hashes is consistent indepent of the
    // file order inside the archive.
    for entry in archive.entries()? {
        let mut e = entry?;
        let path = e.path()?.into_owned();
        map.insert(path.clone(), digest_file(&mut e)?);
    }

    let mut digest = sha2::Sha256::new();
    for (_, file_hash) in map {
        digest.update(file_hash.0);
    }
    let hash = digest.finalize();
    Ok(types::HashSum(data_encoding::HEXLOWER.encode(&hash)))
}

fn digest_file<R: io::Read>(reader: &mut R) -> io::Result<types::HashSum> {
    use sha2::Digest;
    let mut digest = sha2::Sha256::new();
    io::copy(reader, &mut digest)?;
    let hash = digest.finalize();
    Ok(types::HashSum(data_encoding::HEXLOWER.encode(&hash)))
}

#[cfg(test)]
mod test {
    use std::fs;
    use std::path;

    use super::*;

    #[test]
    fn test_nested_archive_tarsum() {
        // This is an archive with a nested directory structure.
        let archive_path = path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .join("src")
            .join("__fixtures__")
            .join("nested_archive.tar");
        let reader = fs::File::open(archive_path).unwrap();
        let res = tarsum(reader).unwrap();

        assert_eq!(
            res,
            types::HashSum(
                "6f92565bb50b9469494b3e1ad668f5d809caa3ffb534c3e56dec75f7ea7912df".to_string()
            )
        );
    }

    #[test]
    fn test_differently_ordered_archives() {
        // These archives have equivalent contents but were created in reverse ways:
        // $ tar cf archive_a.tar archive/a.txt
        // $ tar cf archive_b.tar archive/b.txt
        // $ tar rf archive_a.tar archive/b.txt
        // $ tar rf archive_b.tar archive/a.txt
        // $ gsha256sum archive_*.tar
        // 8de80c3904d85115d1595d48c215022e5db225c920811d4d2eee80586e6390c8  archive_a.tar
        // 60097b704cb1684f52f7e98e98193595ea2876047e9ecc6931db97757bc8a5fd  archive_b.tar
        let fixture_path = path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .join("src")
            .join("__fixtures__");
        let archive_a = fixture_path.join("archive_a.tar");
        let archive_b = fixture_path.join("archive_b.tar");

        let reader_a = fs::File::open(archive_a).unwrap();
        let reader_b = fs::File::open(archive_b).unwrap();
        let res_a = tarsum(reader_a).unwrap();
        let res_b = tarsum(reader_b).unwrap();

        assert_eq!(res_a, res_b);
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::fmt;
use std::fmt::Display;
use std::str;

#[derive(Eq, PartialEq, Debug, PartialOrd, Ord, Clone, serde::Deserialize)]
pub struct Platform(pub String);

impl str::FromStr for Platform {
    type Err = &'static str;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self(s.to_string()))
    }
}

impl Display for Platform {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[derive(
    Debug,
    Clone,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    serde::Deserialize,
    serde::Serialize
)]
#[serde(rename_all = "lowercase")]
pub struct PackType(String);

#[cfg(test)]
impl PackType {
    pub fn new(s: &str) -> Self {
        Self(s.to_string())
    }
}

#[derive(
    Debug,
    Clone,
    Copy,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    serde::Deserialize,
    serde::Serialize
)]
#[serde(rename_all = "lowercase")]
pub enum PackMode {
    /// All paths need to be specified.
    Exact,
    /// Can use `*` and `!` syntax to specify patterns for inclusion and exclusion.
    /// Only works on the root folder level.
    Glob,
}

impl Display for PackType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[derive(Eq, PartialEq, Debug, serde::Serialize)]
pub struct HashSum(pub String);