brcode 1.4.2

Crate to parse and emit BR Codes
Documentation
use brcode::{
    self, crc16_ccitt_from_message, edn_from_brcode, edn_to_brcode, from_str, json_from_brcode,
    json_to_brcode, str_to_brcode, BrCode, Data, Info, Label, MerchantInfo, Template,
};

#[test]
fn test_from_str() {
    assert_eq!(from_str(&code(None, None)), data_expected());
}

#[test]
fn test_to_string() {
    let actual = brcode::to_string(&data_expected());
    let expected = code(None, None);

    assert_eq!(actual, expected);
}

#[test]
fn assert_both_ways() {
    let from = brcode::from_str(&dynamic_code());
    let to = brcode::to_string(&from);

    assert_eq!(to, dynamic_code())
}

#[test]
fn test_brcode_to_string() {
    let actual = brcode::brcode_to_string(brcode_expected(None));
    let expected = code(None, None);

    assert_eq!(actual, expected);
}

#[test]
fn test_brcode_to_string_cents_edge_case1() {
    let actual = brcode::brcode_to_string(brcode_expected(Some(1.1)));
    let expected = code(Some(1.1), Some("779A"));

    assert_eq!(actual, expected);
}

#[test]
fn test_brcode_to_string_cents_edge_case2() {
    let actual = brcode::brcode_to_string(brcode_expected(Some(1.0)));
    let expected = code(Some(1.0), Some("5A3D"));

    assert_eq!(actual, expected);
}

#[test]
fn test_str_to_brcode() {
    assert_eq!(str_to_brcode(&code(None, None)), brcode_expected(None));
}

#[test]
fn minimum_breaking_code() {
    let code = "26062602oi";
    let expected = vec![(
        26usize,
        Data::Vector(vec![(26usize, Data::Single("oi".to_string()))]),
    )];
    assert_eq!(from_str(code), expected);
}

#[test]
fn brcode_is_pix() {
    let from = str_to_brcode(&code(None, None));

    assert!(from.is_pix())
}

#[test]
fn brcode_label() {
    let from = str_to_brcode(&code(None, None));

    assert_eq!(
        from.get_transaction_id(),
        Some("RP12345678-2019".to_string())
    )
}

#[test]
fn brcode_get_alias() {
    let from = str_to_brcode(&brcode_with_alias_message());

    assert_eq!(from.get_alias(), Some(vec!["11999887766".to_string()]))
}

#[test]
fn brcode_get_message() {
    let from = str_to_brcode(&brcode_with_alias_message());

    assert_eq!(from.get_message(), Some(vec!["Hello message".to_string()]))
}

#[test]
fn json_ffi() {
    let code = code(None, None);
    let result = json_from_brcode(to_c_char(code));
    let actual = to_string(result);

    assert_eq!(actual, json());
}

#[test]
fn edn_ffi() {
    let code = code(None, None);
    let result = edn_from_brcode(to_c_char(code));
    let actual = to_string(result);

    assert_eq!(actual, edn());
}

#[test]
fn edn_to_brcode_ffi() {
    let edn = edn();
    let code = code(None, None);
    let result = edn_to_brcode(to_c_char(edn));
    let actual = to_string(result);

    assert_eq!(actual, code);
}

#[test]
fn json_to_brcode_ffi() {
    let json = json();
    let code = code(None, None);
    let result = json_to_brcode(to_c_char(json));
    let actual = to_string(result);

    assert_eq!(actual, code);
}

#[test]
fn crc16_test() {
    let message = "00020101021226740014br.gov.bcb.spi210812345678220412342308123456782420001122334455667788995204000053039865406123.455802BR5913FULANO DE TAL6008BRASILIA62190515RP12345678-201980720014br.gov.bcb.spi2550bx.com.br/spi/U0VHUkVET1RPVEFMTUVOVEVBTEVBVE9SSU8=6304";
    let expected = "34D1";

    let result = crc16_ccitt_from_message(to_c_char(message.to_string()));
    let actual = to_string(result);

    assert_eq!(actual, expected);
}

fn dynamic_code() -> String {
    "00020101021226740014br.gov.bcb.spi210812345678220412342308123456782420001122334455667788995204000053039865406123.455802BR5913FULANO DE TAL6008BRASILIA62190515RP12345678-201980720014br.gov.bcb.spi2550bx.com.br/spi/U0VHUkVET1RPVEFMTUVOVEVBTEVBVE9SSU8=630434D1"
    .to_string()
}

fn code(amount: Option<f64>, crc16: Option<&str>) -> String {
    let amount = amount.unwrap_or(123.45);
    let amount_size = format!("{:.2}", amount).len();
    format!("00020104141234567890123426580014BR.GOV.BCB.PIX0136123e4567-e12b-12d1-a456-42665544000027300012BR.COM.OUTRO0110012345678952040000530398654{:02}{:.2}5802BR5917NOME DO RECEBEDOR6008BRASILIA61087007490062190515RP12345678-201980390012BR.COM.OUTRO01190123.ABCD.3456.WXYZ6304{}", amount_size, amount, crc16.unwrap_or("AD38"))
}

fn json() -> String {
    "{\"payload_version\":1,\"merchant_account_information\":\"12345678901234\",\"merchant_information\":[{\"id\":26,\"info\":[{\"id\":0,\"info\":\"BR.GOV.BCB.PIX\"},{\"id\":1,\"info\":\"123e4567-e12b-12d1-a456-426655440000\"}]},{\"id\":27,\"info\":[{\"id\":0,\"info\":\"BR.COM.OUTRO\"},{\"id\":1,\"info\":\"0123456789\"}]}],\"merchant_category_code\":0,\"merchant_name\":\"NOME DO RECEBEDOR\",\"merchant_city\":\"BRASILIA\",\"postal_code\":\"70074900\",\"currency\":\"986\",\"amount\":123.45,\"country_code\":\"BR\",\"field_template\":[{\"reference_label\":\"RP12345678-2019\"}],\"crc1610\":\"AD38\",\"templates\":[{\"id\":80,\"info\":[{\"id\":0,\"info\":\"BR.COM.OUTRO\"},{\"id\":1,\"info\":\"0123.ABCD.3456.WXYZ\"}]}]}"
    .to_string()
}

fn edn() -> String {
    "{ :payload-version 1, :initiation-method nil, :merchant-account-information \"12345678901234\", :merchant-information [{ :id 26, :info [{ :id 0, :info \"BR.GOV.BCB.PIX\", }, { :id 1, :info \"123e4567-e12b-12d1-a456-426655440000\", }], }, { :id 27, :info [{ :id 0, :info \"BR.COM.OUTRO\", }, { :id 1, :info \"0123456789\", }], }], :merchant-category-code 0, :merchant-name \"NOME DO RECEBEDOR\", :merchant-city \"BRASILIA\", :convenience nil, :convenience-fee-fixed nil, :convenience-fee-percentage nil, :postal-code \"70074900\", :currency \"986\", :amount 123.45, :country-code \"BR\", :field-template [{ :reference-label \"RP12345678-2019\", }], :crc1610 \"AD38\", :templates [{ :id 80, :info [{ :id 0, :info \"BR.COM.OUTRO\", }, { :id 1, :info \"0123.ABCD.3456.WXYZ\", }], }], }"
    .to_string()
}

fn brcode_expected(amount: Option<f64>) -> BrCode {
    BrCode {
        payload_version: 1,
        initiation_method: None,
        merchant_account_information: Some(String::from("12345678901234")),
        merchant_category_code: 0000u32,
        merchant_name: "NOME DO RECEBEDOR".to_string(),
        merchant_city: "BRASILIA".to_string(),
        convenience: None,
        convenience_fee_fixed: None,
        convenience_fee_percentage: None,
        merchant_information: vec![
            MerchantInfo {
                id: 26,
                info: vec![
                    Info {
                        id: 0,
                        info: "BR.GOV.BCB.PIX".to_string(),
                    },
                    Info {
                        id: 1,
                        info: "123e4567-e12b-12d1-a456-426655440000".to_string(),
                    },
                ],
            },
            MerchantInfo {
                id: 27,
                info: vec![
                    Info {
                        id: 0,
                        info: "BR.COM.OUTRO".to_string(),
                    },
                    Info {
                        id: 1,
                        info: "0123456789".to_string(),
                    },
                ],
            },
        ],
        currency: "986".to_string(),
        postal_code: Some("70074900".to_string()),
        amount: amount.or(Some(123.45)),
        country_code: "BR".to_string(),
        field_template: vec![Label {
            reference_label: "RP12345678-2019".to_string(),
        }],
        crc1610: "AD38".to_string(),
        templates: Some(vec![Template {
            id: 80usize,
            info: vec![
                Info {
                    id: 0usize,
                    info: "BR.COM.OUTRO".to_string(),
                },
                Info {
                    id: 1usize,
                    info: "0123.ABCD.3456.WXYZ".to_string(),
                },
            ],
        }]),
    }
}

fn data_expected() -> Vec<(usize, Data)> {
    vec![
        (0, Data::Single("01".to_string())),
        (4, Data::Single("12345678901234".to_string())),
        (
            26,
            Data::Vector(vec![
                (0, Data::Single("BR.GOV.BCB.PIX".to_string())),
                (
                    1,
                    Data::Single("123e4567-e12b-12d1-a456-426655440000".to_string()),
                ),
            ]),
        ),
        (
            27,
            Data::Vector(vec![
                (0, Data::Single("BR.COM.OUTRO".to_string())),
                (1, Data::Single("0123456789".to_string())),
            ]),
        ),
        (52, Data::Single("0000".to_string())),
        (53, Data::Single("986".to_string())),
        (54, Data::Single("123.45".to_string())),
        (58, Data::Single("BR".to_string())),
        (59, Data::Single("NOME DO RECEBEDOR".to_string())),
        (60, Data::Single("BRASILIA".to_string())),
        (61, Data::Single("70074900".to_string())),
        (
            62,
            Data::Vector(vec![(5, Data::Single("RP12345678-2019".to_string()))]),
        ),
        (
            80,
            Data::Vector(vec![
                (0, Data::Single("BR.COM.OUTRO".to_string())),
                (1, Data::Single("0123.ABCD.3456.WXYZ".to_string())),
            ]),
        ),
        (63, Data::Single("AD38".to_string())),
    ]
}

fn brcode_with_alias_message() -> String {
    "00020104141234567890123426500014BR.GOV.BCB.PIX0111119998877660213Hello message27300012BR.COM.OUTRO011001234567895204000053039865406123.455802BR5917NOME DO RECEBEDOR6008BRASILIA61087007490062190515RP12345678-201980390012BR.COM.OUTRO01190123.ABCD.3456.WXYZ63049059"
    .to_string()
}

// FFI Tests
use std::ffi::{CStr, CString};
use std::mem;
use std::os::raw::c_char;
use std::str;

fn to_string(pointer: *const c_char) -> String {
    let slice = unsafe { CStr::from_ptr(pointer).to_bytes() };
    str::from_utf8(slice).unwrap().to_string()
}

fn to_c_char(s: String) -> *const c_char {
    let cs = CString::new(s.as_bytes()).unwrap();
    let ptr = cs.as_ptr();
    mem::forget(cs);
    ptr
}