atakurt / iyzipay-rust

3 stars 1 forks source link

Builder Pattern #1

Open erayerdin opened 2 years ago

erayerdin commented 2 years ago

Rationale

This library is currently using setters to construct various types such as Options, CreatePaymentRequest, PaymentCard, Buyer etc. This has some drawbacks:

Instead of setter methods, factory pattern would be a better approach to handle:

This pattern is utilized in various libraries such as request construction in reqwest or server construction in rocket and actix.

Examples

Taking the example in README file, it would look like this with factory pattern:

fn main() {
    let options: Result<Options, Error> = Options::new()
        // if not provided, returns Error
        .api_key("YOUR_API_KEY")
        // if not provided, returns Error
        .secret_key("YOUR_SECRET_KEY")
        // if not provided, the default will be: https://sandbox-api.iyzipay.com
        .base_url("YOUR_BASE_URL")
        // returns the Result
        .build()
        // panics if Error
        .unwrap();

    let payment_card = PaymentCard::new()
        // if not provided, returns Error
        .card_holder_name("CARD_HOLDER_NAME")
        // if not provided, returns Error
        .card_number("CARD_NUMBER")
        // if not provided, returns Error
        .expire_month("12")
        // if not provided, returns Error
        .expire_year("2030")
        // if not provided, returns Error
        .cvc("123")
        // if not provided, the default will be: 0
        .register_card(0)
        // returns the Result
        .build()
        // panics if Error
        .unwrap();

    let buyer = Buyer::new()
        // if not provided, returns Error
        .id("BUYER_ID")
        // if not provided, returns Error
        .name("BUYER_NAME")
        // if not provided, returns Error
        .surname("BUYER_SURNAME")
        // if not provided, returns Error
        .gsm_number("BUYER_GSM_NUMBER")
        // if not provided, returns Error
        .email("BUYER_EMAIL")
        // if not provided, returns Error
        .identity_number("BUYER_IDENTITY_NUMBER")
        // if not provided, the default will be None
        .last_login_date("BUYER_LAST_LOGIN_DATE")
        // if not provided, the default will be None
        .registration_date("BUYER_REGISTRATION_DATE")
        // if not provided, returns Error
        .registration_address("BUYER_REGISTRATION_ADDRESS")
        // if not provided, returns Error
        .ip("BUYER_IP")
        // if not provided, returns Error
        .city("BUYER_CITY")
        // if not provided, returns Error
        .country("BUYER_COUNTRY")
        // if not provided, returns Error
        .zip_code("BUYER_ZIP_CODE")
        // returns the Result
        .build()
        // panics if Error
        .unwrap();

    let shipping_address = Address::new()
        // if not provided, returns Error
        .contact_name("CONTACT_NAME")
        // if not provided, returns Error
        .city("CONTACT_CITY")
        // if not provided, returns Error
        .country("CONTACT_COUNTRY")
        // if not provided, returns Error
        .address("CONTACT_ADDRESS")
        // if not provided, returns Error
        .zip_code("CONTACT_ZIP_CODE")
        // returns the Result
        .build()
        // panics if Error
        .unwrap();

    let billing_address = Address::new()
        // if not provided, returns Error
        .contact_name("CONTACT_NAME")
        // if not provided, returns Error
        .city("CONTACT_CITY")
        // if not provided, returns Error
        .country("CONTACT_COUNTRY")
        // if not provided, returns Error
        .address("CONTACT_ADDRESS")
        // if not provided, returns Error
        .zip_code("CONTACT_ZIP_CODE")
        // returns the Result
        .build()
        // panics if Error
        .unwrap();

    let basket_items: Vec<BasketItem> = vec![
        BasketItem::new()
            // if not provided, returns Error
            .id("BASKET_ITEM_ID")
            // if not provided, returns Error
            .name("BASKET_ITEM_NAME")
            // if not provided, the default will be None
            .category1("BASKET_ITEM_CATEGORY1")
            // if not provided, the default will be None
            .category2("BASKET_ITEM_CATEGORY2")
            // if not provided, returns Error
            .item_type(BasketItemType::Physical.value())
            // returns the Result
            .build()
            // panics if Error
            .unwrap(),
        BasketItem::new()
            // if not provided, returns Error
            .id("BASKET_ITEM_ID")
            // if not provided, returns Error
            .name("BASKET_ITEM_NAME")
            // if not provided, the default will be None
            .category1("BASKET_ITEM_CATEGORY1")
            // if not provided, the default will be None
            .category2("BASKET_ITEM_CATEGORY2")
            // if not provided, returns Error
            .item_type(BasketItemType::Digital.value())
            // returns the Result
            .build()
            // panics if Error
            .unwrap(),
    ];

    let request = CreatePaymentRequest::new()
        // if not provided, returns Error
        .locale(Locale::TR.value())
        // if not provided, returns Error
        .conversation_id("YOUR_CONVERSATION_ID")
        // if not provided, returns Error
        .price(BigDecimal::from_str("1").unwrap())
        // if not provided, returns Error
        .paid_price(BigDecimal::from_str("1.2").unwrap())
        // if not provided, returns Error
        .currency(Currency::TRY.to_string())
        // if not provided, the default will be: 1
        .installment(1)
        // if not provided, returns Error
        .basket_id("YOUR_BASEKET_ID".to_string())
        // if not provided, returns Error
        .payment_channel(PaymentChannel::Web.value())
        // if not provided, returns Error
        .payment_group(PaymentGroup::Product.value())
        // if not provided, returns Error
        .payment_card(payment_card)
        // if not provided, returns Error
        .buyer(buyer)
        // if not provided, returns Error
        .shipping_address(shipping_address)
        // if not provided, returns Error
        .billing_address(billing_address)
        // if it is empty, returns Error
        .basket_items(basket_items)
        // returns the Result
        .build();

    let payment = Payment::new()
        // if not provided, returns Error
        .request(request.unwrap())
        // if not provided, returns Error
        .options(options.unwrap())
        // returns the Result
        .build()
        // panics if Error
        .unwrap();
}

The below is a much more declarative example:

fn main() {
    let payment = Payment::new()
        // if not provided, returns Error
        .request(
            CreatePaymentRequest::new()
                // if not provided, returns Error
                .locale(Locale::TR.value())
                // if not provided, returns Error
                .conversation_id("YOUR_CONVERSATION_ID")
                // if not provided, returns Error
                .price(BigDecimal::from_str("1").unwrap())
                // if not provided, returns Error
                .paid_price(BigDecimal::from_str("1.2").unwrap())
                // if not provided, returns Error
                .currency(Currency::TRY.to_string())
                // if not provided, the default will be: 1
                .installment(1)
                // if not provided, returns Error
                .basket_id("YOUR_BASEKET_ID".to_string())
                // if not provided, returns Error
                .payment_channel(PaymentChannel::Web.value())
                // if not provided, returns Error
                .payment_group(PaymentGroup::Product.value())
                // if not provided, returns Error
                .payment_card(
                    PaymentCard::new()
                        // if not provided, returns Error
                        .card_holder_name("CARD_HOLDER_NAME")
                        // if not provided, returns Error
                        .card_number("CARD_NUMBER")
                        // if not provided, returns Error
                        .expire_month("12")
                        // if not provided, returns Error
                        .expire_year("2030")
                        // if not provided, returns Error
                        .cvc("123")
                        // if not provided, the default will be: 0
                        .register_card(0)
                        // returns the Result
                        .build()
                        // panics if Error
                        .unwrap(),
                )
                // if not provided, returns Error
                .buyer(
                    Buyer::new()
                        // if not provided, returns Error
                        .id("BUYER_ID")
                        // if not provided, returns Error
                        .name("BUYER_NAME")
                        // if not provided, returns Error
                        .surname("BUYER_SURNAME")
                        // if not provided, returns Error
                        .gsm_number("BUYER_GSM_NUMBER")
                        // if not provided, returns Error
                        .email("BUYER_EMAIL")
                        // if not provided, returns Error
                        .identity_number("BUYER_IDENTITY_NUMBER")
                        // if not provided, the default will be None
                        .last_login_date("BUYER_LAST_LOGIN_DATE")
                        // if not provided, the default will be None
                        .registration_date("BUYER_REGISTRATION_DATE")
                        // if not provided, returns Error
                        .registration_address("BUYER_REGISTRATION_ADDRESS")
                        // if not provided, returns Error
                        .ip("BUYER_IP")
                        // if not provided, returns Error
                        .city("BUYER_CITY")
                        // if not provided, returns Error
                        .country("BUYER_COUNTRY")
                        // if not provided, returns Error
                        .zip_code("BUYER_ZIP_CODE")
                        // returns the Result
                        .build()
                        // panics if Error
                        .unwrap(),
                )
                // if not provided, returns Error
                .shipping_address(
                    Address::new()
                        // if not provided, returns Error
                        .contact_name("CONTACT_NAME")
                        // if not provided, returns Error
                        .city("CONTACT_CITY")
                        // if not provided, returns Error
                        .country("CONTACT_COUNTRY")
                        // if not provided, returns Error
                        .address("CONTACT_ADDRESS")
                        // if not provided, returns Error
                        .zip_code("CONTACT_ZIP_CODE")
                        // returns the Result
                        .build()
                        // panics if Error
                        .unwrap(),
                )
                // if not provided, returns Error
                .billing_address(
                    Address::new()
                        // if not provided, returns Error
                        .contact_name("CONTACT_NAME")
                        // if not provided, returns Error
                        .city("CONTACT_CITY")
                        // if not provided, returns Error
                        .country("CONTACT_COUNTRY")
                        // if not provided, returns Error
                        .address("CONTACT_ADDRESS")
                        // if not provided, returns Error
                        .zip_code("CONTACT_ZIP_CODE")
                        // returns the Result
                        .build()
                        // panics if Error
                        .unwrap(),
                )
                // if it is empty, returns Error
                .basket_items(vec![
                    BasketItem::new()
                        // if not provided, returns Error
                        .id("BASKET_ITEM_ID")
                        // if not provided, returns Error
                        .name("BASKET_ITEM_NAME")
                        // if not provided, the default will be None
                        .category1("BASKET_ITEM_CATEGORY1")
                        // if not provided, the default will be None
                        .category2("BASKET_ITEM_CATEGORY2")
                        // if not provided, returns Error
                        .item_type(BasketItemType::Physical.value())
                        // returns the Result
                        .build()
                        // panics if Error
                        .unwrap(),
                    BasketItem::new()
                        // if not provided, returns Error
                        .id("BASKET_ITEM_ID")
                        // if not provided, returns Error
                        .name("BASKET_ITEM_NAME")
                        // if not provided, the default will be None
                        .category1("BASKET_ITEM_CATEGORY1")
                        // if not provided, the default will be None
                        .category2("BASKET_ITEM_CATEGORY2")
                        // if not provided, returns Error
                        .item_type(BasketItemType::Digital.value())
                        // returns the Result
                        .build()
                        // panics if Error
                        .unwrap(),
                ])
                // returns the Result
                .build()
                // panics if Error
                .unwrap(),
        )
        // if not provided, returns Error
        .options(
            Options::new()
                // if not provided, returns Error
                .api_key("YOUR_API_KEY")
                // if not provided, returns Error
                .secret_key("YOUR_SECRET_KEY")
                // if not provided, the default will be: https://sandbox-api.iyzipay.com
                .base_url("YOUR_BASE_URL")
                // returns the Result
                .build()
                // panics if Error
                .unwrap(),
        )
        // returns the Result
        .build()
        // panics if Error
        .unwrap();
}

If you'd like, I can work on this and provide it as a PR.

atakurt commented 2 years ago

sounds good, instead of one to one translation of java client, that would be better, i think for doing that with derive_builder crate would be easier

erayerdin commented 2 years ago

didn't know that this existed.

if you are ok with it, i can start with implementation.

i'm asking this because it is a breaking change to your api, methods (with their names) will change.

atakurt commented 2 years ago

i'm ok with it, we can bump the version when merging to master.