Introducing the Rust Crash Course

Introducing the Rust Crash Course

Introducing the Rust Crash Course - Though not widely adopted by organizations, Rust is an increasingly popular language among developers...

Originally published by Michael Snoyman at dzone.com

This post is part of a series, Rust Crash Course. You can read all the posts:

 In this blog post, I want to explain:

  • Why I'm interested in Rust.
  • Why I'm writing up this series.
  • Who this series is for.
  • My intended flavor for the series.
  • Some examples of Rust gotchas I want to cover.

I'm getting to work on this series due to increased Rust usage at FP Complete.

Why Rust?

I'm a strong believer in using the compiler to help eliminate bugs. No programming language can eliminate all bugs and even the best-designed language will typically need to leave developers plenty of wiggle room to shoot themselves in the foot. Still, there's significant value in safety and long-term maintainability of projects that use languages with this focus.

For about ten years now, my primary language has been (and continues to be) Haskell. It gets a lot of things right here, such as immutability, explicit effects, and strong typing. But I've got two problems with an intense focus on Haskell:

  • There's always something to be learned from other languages. It's quite easy to fall into the trap of knowing one language well and becoming blind to its shortcomings. Haskell is now the language I've used for the longest stretch of time as a primary language, finally displacing Java. I need to avoid this trap.
  • There are some legitimate technical advantages of Rust:
  • Performance is better, and more reliable as well, relying less upon things like rewrite rules firing.
  • It's got a better story right now for mobile and front-end development.
  • Lack of garbage collection opens up new possibilities in real-time development.
  • Rust improves on Haskell's safety guarantees in some places, such as defaulting to requiring complete pattern matches.

Like many others, I've been hearing a buzz around Rust for years. Two or so years ago, I started playing around with it more, and have been steadily dedicating more personal time to it. But recently, we've had more Rust interest at work, and therefore we've been expanding our internal Rust team.

Why This Series?

We're a globally distributed team at FP Complete, and we make a heavy push towards using written communication tools wherever possible. This also overlaps heavily with training material. As I do more training (both internally, and for customers), I'm discovering places where:

  • I'm finding my own knowledge of the language was lacking.
  • Newcomers are stumbling hard.

This series is intended to collect both initial pointers of how to get started with Rust and hard-learned lessons of how to avoid getting stuck. Some of these stumbling blocks may favor the kind of audience I'm working with directly (lots of Haskell and DevOps engineers), but that will likely change over time.

Target Audience

I'm gearing this series towards the Rust curious. I'm assuming programming knowledge, and some basic idea of what Rust is about, but no real knowledge of the language itself. I'll try to call out when you should go read the Rust book.

If you're a Java user, a Rubyist, or a Haskeller, and Rust intrigues you, I hope this will help you. And maybe even Rustaceans will enjoy seeing the pain points I find myself and others are hitting.

Flavor of the Series

There is already really good material on Rust available from rust-lang.org. I have no intention of trying to replace that. Instead, I'll assume that people are reading through the Rust book, and point to sections where appropriate.

One concrete example: I don't intend to spend a lot of time talking about Rust syntax, or explaining that it's an expression-oriented language. The book covers that.

Instead, I want to give people:

  • Real code to look at and play with.
  • Simple problems to build up experience with.
  • Explanations of tricky cases that catch people up.

And on that note...

Rust Gotchas

A few people on Twitter asked me to share some Rust gotchas, especially coming from the perspective of a Haskell developer. I'll certainly be covering more gotchas going forward, but I wanted to give some examples in this first post so you can get a feel for the kinds of things we'll be addressing in the series. I'm not going to be explaining details of these problems here; that's what the series is for!

Just so you know: there's no content following these examples, besides the Disqus comments below. If you're not interested in the gotchas, feel free to quit reading now and stay tuned for more posts.

Mutable Values, Mutable Variables

I've got a simple mental model in Haskell. Values are all immutable, period. A few special reference types allow me to mutate their contents. And mutating like that is an effect tracked in the type system.

From that perspective, Rust is a bit surprising. Here's one:

fn main() {
    let i: isize = 1;
    let j: isize = foo(i);
    println!("{}", j);
}
fn foo(mut i: isize) -> isize {
    i += 1;
    i
}

Wait a second... i is immutable. Then I pass it to foo, and it becomes mutable. Then I return this mutable value as an immutable value. What?

I assure you, this ultimately makes sense, but it's kind of surprising. Also, the fact that x: &mut isize and mut x: &mut isize are both real things that mean different things:

fn main() {
    let mut i: isize = 1;
    let mut j: isize = 2;
    foo(&mut i, &mut j);
    println!("{} {}", i, j);
}
fn foo<'a>(mut i: &'a mut isize, j: &'a mut isize) {
    *i *= 10;
    i = j;
    *i *= 10;
}

So Many Strings

I was warned about this one and blew it off. I thought to myself, 'there's no way a Haskeller, trained in the arts of String, strict Text, lazy TextByteString s and more could be daunted.' I was wrong.

fn main() {
    let hello1 = String::from("Hello, ");
    let hello2 = String::from(", hello!");
    let name = "Alice";
    println!("{}", hello1 + name);
    println!("{}", name + hello2);
}

Nope, the code above doesn't compile.

Magically Works, Until it Doesn't

There are a number of "magic" things that happen in Rust in the name of ergonomics. Often, they work perfectly and save a lot of frustration. And sometimes, they fail. Look at this broken code:

fn main() {
    for arg in std::env::args().skip(1) {
        respond(arg);
    }
}
fn respond(arg: &str) {
    match arg {
        "hi" => println!("Hello there!"),
        "bye" => println!("OK, goodbye!"),
        _ => println!("Sorry, I don't know what {} means", arg),
    }
}

You'll get an error message:

expected &str, found struct `std::string::String`

Oh, well that makes sense! I need to get a reference to a str instead of the String I got from args(). Easy enough to fix:

respond(&arg);

But then I realize that the respond function is silly and inline the match:

fn main() {
    for arg in std::env::args().skip(1) {
        match &arg {
            "hi" => println!("Hello there!"),
            "bye" => println!("OK, goodbye!"),
            _ => println!("Sorry, I don't know what {} means", arg),
        }
    }
}

I remembered to match on &arg instead of arg, so you'd think it would be fine. But it isn't:

 |             ^^^^ expected struct `std::string::String`, found str
  |
  = note: expected type `&std::string::String`
             found type `&'static str`

Huh, that's weird. In order to figure out what's going on here, you have to understand quite a few details of the deref magic going on behind the scenes. I dare say some of this magic is even a leaky abstraction (don't worry, it's still zero cost). You can solve this with either:

match &*arg {

or

match arg.as_ref() {

Moving Data Into Closures

The biggest pain points I've encountered so far in my Rust odyssey are all around moving data into closures. I've been spoiled by Haskell: I like to use closures constantly, and am used to garbage collection just letting it all magically work.

I've got some real head-scratchers to demonstrate later, but have fun with this relatively simple example:

fn main() {
    let hello = String::from("Hello, ");
    let greet = |name| hello + name;
    println!("{}", greet("Alice"));
    println!("{}", greet("Bob"));
}


Originally published by Michael Snoyman at dzone.com

========================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More

☞ The Rust Programming Language

☞ Rust: Building Reusable Code with Rust from Scratch

☞  Programming in Rust: the good, the bad, the ugly.

☞  An introduction to Web Development with Rust for Node.js Developers


Mobile App Development Company India | Ecommerce Web Development Company India

Mobile App Development Company India | Ecommerce Web Development Company India

Best Mobile App Development Company India, WebClues Global is one of the leading web and mobile app development company. Our team offers complete IT solutions including Cross-Platform App Development, CMS & E-Commerce, and UI/UX Design.

We are custom eCommerce Development Company working with all types of industry verticals and providing them end-to-end solutions for their eCommerce store development.

Know more about Top E-Commerce Web Development Company

Hire PHP Developer and Web Developer for your Online Business

Hire PHP Developer and Web Developer for your Online Business

PHP is widely used open-source scripting language it helps in making dynamically easy your websites and web application. Mobiweb Technology is your best technical partner and offering you solution for any kind of website and application...

PHP is widely used open-source scripting language it helps in making dynamically easy your websites and web application. Mobiweb Technology is your best technical partner and offering you solution for any kind of website and application development. To hire PHP developer and web developer at affordable prices contact Mobiweb Technology via [email protected]

Practical Rust Web Development - GraphQL

Practical Rust Web Development - GraphQL

Practical Rust Web Development / GraphQL - According to the official homepage, GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data...

Originally published by Werner Echezuría  at dev.to

GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.

One advantage of GraphQL is the flexibility it provides, in one query you can obtain everything you need allowing easy maintenance of the code over time and easier communication between the server and the client.

Juniper is a crate that allows the creation of a GraphQL server, we'll be using it in our project.

Let's continue with our online store, we're going to need to control our sales in the site, so, let's create a sales module that will receive a GraphQL query.

migrations/2019-07-28-191653_add_sales/up.sql:

CREATE TABLE sales (
  id SERIAL PRIMARY KEY,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  sale_date DATE NOT NULL,
  total FLOAT NOT NULL
);

CREATE TABLE sale_products (
id SERIAL PRIMARY KEY,
product_id INTEGER NOT NULL REFERENCES products(id) ON DELETE CASCADE,
sale_id INTEGER NOT NULL REFERENCES sales(id) ON DELETE CASCADE,
amount FLOAT NOT NULL,
discount INTEGER NOT NULL,
tax INTEGER NOT NULL,
price INTEGER NOT NULL, --representing cents
total FLOAT NOT NULL
)

Then, we're gonna need the endpoint that will receive all our queries, this would be a post request.

src/graphql.rs:


pub fn graphql(
st: web::Data<Arc<Schema>>,
data: web::Json<GraphQLRequest>,
user: LoggedUser,
pool: web::Data<PgPool>
) -> impl Future<Item = HttpResponse, Error = Error> {
web::block(move || {
let pg_pool = pool
.get()
.map_err(|e| {
serde_json::Error::custom(e)
})?;

    let ctx = create_context(user.id, pg_pool);

    let res = data.execute(&amp;st, &amp;ctx);
    Ok::&lt;_, serde_json::error::Error&gt;(serde_json::to_string(&amp;res)?)
})
.map_err(Error::from)
.and_then(|user| {
    Ok(HttpResponse::Ok()
        .content_type("application/json")
        .body(user))
})

}

src/main.rs:

    HttpServer::new(
move || App::new()
.service(
web::resource("/graphql").route(web::post().to_async(graphql))
)

In order to output a response that reads the data we need a query, but if we need to modify state we'll be needing a mutation, let's add both resources in our sales module.

src/models/sale.rs:

use diesel::PgConnection;
use diesel::BelongingToDsl;
use diesel::sql_types;
use chrono::NaiveDate;
use juniper::{FieldResult};
use crate::schema;
use crate::schema::sales;
use crate::schema::sale_products;
use crate::db_connection::PgPooledConnection;
use crate::models::product::{ Product, PRODUCT_COLUMNS };
use crate::errors::MyStoreError;

#[derive(Identifiable, Queryable, Debug, Clone, PartialEq)]
#[table_name="sales"]
#[derive(juniper::GraphQLObject)]
#[graphql(description="Sale Bill")]
pub struct Sale {
pub id: i32,
pub user_id: i32,
pub sale_date: NaiveDate,
pub total: f64,
pub bill_number: Option<String>
}

#[derive(Insertable, Deserialize, Serialize, AsChangeset, Debug, Clone, PartialEq)]
#[table_name="sales"]
#[derive(juniper::GraphQLInputObject)]
#[graphql(description="Sale Bill")]
pub struct NewSale {
pub id: Option<i32>,
pub sale_date: Option<NaiveDate>,
pub user_id: Option<i32>,
pub total: Option<f64>,
pub bill_number: Option<String>
}

use crate::models::sale_product::{ SaleProduct, NewSaleProduct, NewSaleProducts, FullSaleProduct,FullNewSaleProduct };

#[derive(Debug, Clone)]
#[derive(juniper::GraphQLObject)]
pub struct FullSale {
pub sale: Sale,
pub sale_products: Vec<FullSaleProduct>
}

#[derive(Debug, Clone)]
#[derive(juniper::GraphQLObject)]
pub struct FullNewSale {
pub sale: NewSale,
pub sale_products: Vec<FullNewSaleProduct>
}

#[derive(Debug, Clone)]
#[derive(juniper::GraphQLObject)]
pub struct ListSale {
pub data: Vec<FullSale>
}

use std::sync::Arc;

pub struct Context {
pub user_id: i32,
pub conn: Arc<PgPooledConnection>,
}

impl juniper::Context for Context {}

pub struct Query;

type BoxedQuery<'a> =
diesel::query_builder::BoxedSelectStatement<'a, (sql_types::Integer,
sql_types::Integer,
sql_types::Date,
sql_types::Float8,
sql_types::Nullable<sql_types::Text>
),
schema::sales::table, diesel::pg::Pg>;

impl Sale {
fn searching_records<'a>(search: Option<NewSale>) -> BoxedQuery<'a> {
use diesel::QueryDsl;
use diesel::ExpressionMethods;
use crate::schema::sales::dsl::*;

    let mut query = schema::sales::table.into_boxed::&lt;diesel::pg::Pg&gt;();

    if let Some(sale) = search {
        if let Some(sale_sale_date) = sale.sale_date {
            query = query.filter(sale_date.eq(sale_sale_date));
        }
        if let Some(sale_bill_number) = sale.bill_number {
            query = query.filter(bill_number.eq(sale_bill_number));
        }
    }

    query
}

}

#[juniper::object(
Context = Context,
)]
impl Query {

fn listSale(context: &amp;Context, search: Option&lt;NewSale&gt;, limit: i32) 
    -&gt; FieldResult&lt;ListSale&gt; {
        use diesel::{ QueryDsl, RunQueryDsl, ExpressionMethods, GroupedBy };
        use crate::models::sale_product::SaleProduct;
        let conn: &amp;PgConnection = &amp;context.conn;
        let query = Sale::searching_records(search);

        let query_sales: Vec&lt;Sale&gt; =
            query
                .filter(sales::dsl::user_id.eq(context.user_id))
                .limit(limit.into())
                .load::&lt;Sale&gt;(conn)?;

        let query_products = 
            schema::products::table
                .inner_join(schema::sale_products::table)
                .select((PRODUCT_COLUMNS, 
                        (schema::sale_products::id, 
                         schema::sale_products::product_id, 
                         schema::sale_products::sale_id, 
                         schema::sale_products::amount,
                         schema::sale_products::discount,
                         schema::sale_products::tax,
                         schema::sale_products::price,
                         schema::sale_products::total)))
                .load::&lt;(Product, SaleProduct)&gt;(conn)?;

        let query_sale_products = 
            SaleProduct::belonging_to(&amp;query_sales)
                .inner_join(schema::products::table)
                .select(((schema::sale_products::id, 
                         schema::sale_products::product_id, 
                         schema::sale_products::sale_id, 
                         schema::sale_products::amount,
                         schema::sale_products::discount,
                         schema::sale_products::tax,
                         schema::sale_products::price,
                         schema::sale_products::total),
                         PRODUCT_COLUMNS))
                .load::&lt;(SaleProduct, Product)&gt;(conn)?
                .grouped_by(&amp;query_sales);

        let tuple_full_sale: Vec&lt;(Sale, Vec&lt;(SaleProduct, Product)&gt;)&gt; = 
            query_sales
                .into_iter()
                .zip(query_sale_products)
                .collect::&lt;Vec&lt;(Sale, Vec&lt;(SaleProduct, Product)&gt;)&gt;&gt;();

        let vec_full_sale = tuple_full_sale.iter().map (|tuple_sale| {
            let full_sale_product = tuple_sale.1.iter().map(|tuple_sale_product| {
                FullSaleProduct {
                    sale_product: tuple_sale_product.0.clone(),
                    product: tuple_sale_product.1.clone()
                }
            }).collect();
            FullSale {
                sale: tuple_sale.0.clone(),
                sale_products: full_sale_product
            }
        }).collect();

        Ok(ListSale { data: vec_full_sale })
    }

fn sale(context: &amp;Context, sale_id: i32) -&gt; FieldResult&lt;FullSale&gt; {
    use diesel::{ ExpressionMethods, QueryDsl, RunQueryDsl };

    let conn: &amp;PgConnection = &amp;context.conn;
    let sale: Sale =
        schema::sales::table
            .filter(sales::dsl::user_id.eq(context.user_id))
            .find(sale_id)
            .first::&lt;Sale&gt;(conn)?;

    let sale_products = 
        SaleProduct::belonging_to(&amp;sale)
            .inner_join(schema::products::table)
            .select(((schema::sale_products::id, 
                        schema::sale_products::product_id, 
                        schema::sale_products::sale_id, 
                        schema::sale_products::amount,
                        schema::sale_products::discount,
                        schema::sale_products::tax,
                        schema::sale_products::price,
                        schema::sale_products::total),
                        PRODUCT_COLUMNS))
            .load::&lt;(SaleProduct, Product)&gt;(conn)?
            .iter()
            .map(|tuple| {
                FullSaleProduct {
                    sale_product: tuple.0.clone(),
                    product: tuple.1.clone()
                }
            })
            .collect();
    Ok(FullSale{ sale, sale_products })
}

}

pub struct Mutation;

#[juniper::object(
Context = Context,
)]
impl Mutation {

fn createSale(context: &amp;Context, param_new_sale: NewSale, param_new_sale_products: NewSaleProducts) 
    -&gt; FieldResult&lt;FullSale&gt; {
        use diesel::{ RunQueryDsl, Connection, QueryDsl };

        let conn: &amp;PgConnection = &amp;context.conn;

        let new_sale = NewSale {
            user_id: Some(context.user_id),
            ..param_new_sale
        };

        conn.transaction(|| {
            let sale = 
                diesel::insert_into(schema::sales::table)
                    .values(new_sale)
                    .returning(
                        (
                            sales::dsl::id,
                            sales::dsl::user_id,
                            sales::dsl::sale_date,
                            sales::dsl::total,
                            sales::dsl::bill_number
                        )
                    )
                    .get_result::&lt;Sale&gt;(conn)?;

            let sale_products: Result&lt;Vec&lt;FullSaleProduct&gt;, _&gt; =
                param_new_sale_products.data.into_iter().map(|param_new_sale_product| {
                    let new_sale_product = NewSaleProduct {
                        sale_id: Some(sale.id),
                        ..param_new_sale_product.sale_product
                    };
                    let sale_product =
                        diesel::insert_into(schema::sale_products::table)
                            .values(new_sale_product)
                            .returning(
                                (
                                    sale_products::dsl::id,
                                    sale_products::dsl::product_id,
                                    sale_products::dsl::sale_id,
                                    sale_products::dsl::amount,
                                    sale_products::dsl::discount,
                                    sale_products::dsl::tax,
                                    sale_products::dsl::price,
                                    sale_products::dsl::total
                                )
                            )
                            .get_result::&lt;SaleProduct&gt;(conn);

                    if let Some(param_product_id) = param_new_sale_product.sale_product.product_id {
                        let product = 
                            schema::products::table
                                .select(PRODUCT_COLUMNS)
                                .find(param_product_id)
                                .first(conn);

                        Ok(
                            FullSaleProduct {
                                 sale_product: sale_product?, 
                                 product: product? 
                            }
                        )
                    } else {
                        Err(MyStoreError::PGConnectionError)
                    }
                }).collect();

            Ok(FullSale{ sale, sale_products: sale_products? })
        })
    }


fn updateSale(context: &amp;Context, param_sale: NewSale, param_sale_products: NewSaleProducts) 
    -&gt; FieldResult&lt;FullSale&gt; {
        use diesel::QueryDsl;
        use diesel::RunQueryDsl;
        use diesel::ExpressionMethods;
        use diesel::Connection;
        use crate::schema::sales::dsl;

        let conn: &amp;PgConnection = &amp;context.conn;
        let sale_id = param_sale.id.ok_or(
            diesel::result::Error::QueryBuilderError("missing id".into())
        )?;

        conn.transaction(|| {
            let sale = 
                diesel::update(dsl::sales
                                   .filter(dsl::user_id.eq(context.user_id))
                                   .find(sale_id))
                    .set(&amp;param_sale)
                    .get_result::&lt;Sale&gt;(conn)?;

            let sale_products: Result&lt;Vec&lt;FullSaleProduct&gt;, _&gt; =
                param_sale_products.data.into_iter().map (|param_sale_product| {
                    let sale_product =
                        diesel::update(schema::sale_products::table)
                            .set(&amp;param_sale_product.sale_product)
                            .get_result::&lt;SaleProduct&gt;(conn);

                    if let Some(param_product_id) = param_sale_product.sale_product.product_id {
                        let product = 
                            schema::products::table
                                .select(PRODUCT_COLUMNS)
                                .find(param_product_id)
                                .first(conn);

                        Ok(
                            FullSaleProduct {
                                 sale_product: sale_product?, 
                                 product: product? 
                            }
                        )
                    } else {
                        Err(MyStoreError::PGConnectionError)
                    }

                }).collect();

            Ok(FullSale{ sale, sale_products: sale_products? })
        })
    }

fn destroySale(context: &amp;Context, sale_id: i32) 
    -&gt; FieldResult&lt;i32&gt; {
        use diesel::QueryDsl;
        use diesel::RunQueryDsl;
        use diesel::ExpressionMethods;
        use crate::schema::sales::dsl;

        let conn: &amp;PgConnection = &amp;context.conn;
        diesel::delete(dsl::sales.filter(dsl::user_id.eq(context.user_id)).find(sale_id))
            .execute(conn)?;
        Ok(sale_id)
    }

}

pub type Schema = juniper::RootNode<'static, Query, Mutation>;

pub fn create_schema() -> Schema {
Schema::new(Query {}, Mutation {})
}

pub fn create_context(logged_user_id: i32, pg_pool: PgPooledConnection) -> Context {
Context { user_id: logged_user_id, conn: Arc::new(pg_pool)}
}

As you can see our business logic is very similar to a REST endpoint, what could change is the way we query the data, we then export the schema using create_schema function.

src/main.rs:

 let schema = std::sync::Arc::new(create_schema());

HttpServer::new(
move || App::new()
    .data(schema.clone())

Now, how can we fetch the data and performs our mutation?, we then need to write some tests and see if everything works as expected.

tests/sale_test.rs:

...
// create a sale:
let query =
format!(
r#"
{{
"query": "
mutation CreateSale($paramNewSale: NewSale!, $paramNewSaleProducts: NewSaleProducts!) {{
createSale(paramNewSale: $paramNewSale, paramNewSaleProducts: $paramNewSaleProducts) {{
sale {{
id
userId
saleDate
total
}}
saleProducts {{
product {{
name
}}
saleProduct {{
id
productId
amount
discount
tax
price
total
}}
}}
}}
}}
",
"variables": {{
"paramNewSale": {{
"saleDate": "{}",
"total": {}
}},
"paramNewSaleProducts": {{
"data":
[{{
"product": {{ }},
"saleProduct": {{
"amount": {},
"discount": {},
"price": {},
"productId": {},
"tax": {},
"total": {}
}}
}}]
}}
}}
}}"#,
...

// show a sale:

   let query = format!(r#"
        {{
            "query": "
                query ShowASale($saleId: Int!) {{
                    sale(saleId: $saleId) {{
                        sale {{
                            id
                            userId
                            saleDate
                            total
                        }}
                        saleProducts {{
                            product {{ name }}
                            saleProduct {{
                                id
                                productId
                                amount
                                discount
                                tax
                                price
                                total
                            }}
                        }}
                    }}
                }}
            ",
            "variables": {{
                "saleId": {}
            }}
        }}
    "#, id).replace("\n", "");

...

// update a sale

    let query = 
        format!(
        r#"
        {{
            "query": "
                mutation UpdateSale($paramSale: NewSale!, $paramSaleProducts: NewSaleProducts!) {{
                        updateSale(paramSale: $paramSale, paramSaleProducts: $paramSaleProducts) {{
                            sale {{
                                id
                                saleDate
                                total
                            }}
                            saleProducts {{
                                product {{ name }}
                                saleProduct {{
                                    id
                                    productId
                                    amount
                                    discount
                                    tax
                                    price
                                    total
                                }}
                            }}
                        }}
                }}
            ",
            "variables": {{
                "paramSale": {{
                    "id": {},
                    "saleDate": "{}",
                    "total": {}
                }},
                "paramSaleProducts": {{
                    "data":
                        [{{
                            "product": {{}},
                            "saleProduct": 
                            {{
                                "amount": {},
                                "discount": {},
                                "price": {},
                                "productId": {},
                                "tax": {},
                                "total": {}
                            }}
                        }}]
                }}
            }}
        }}"#

...

// delete a sale:

    let query = format!(r#"
        {{
            "query": "
                mutation DestroyASale($saleId: Int!) {{
                    destroySale(saleId: $saleId)
                }}
            ",
            "variables": {{
                "saleId": {}
            }}
        }}
    "#, id).replace("\n", "");

...

// search for a sale with specific date:

   let query = format!(r#"
        {{
            "query": "
                query ListSale($search: NewSale!, $limit: Int!) {{
                    listSale(search: $search, limit: $limit) {{
                        data {{
                            sale {{
                                id
                                saleDate
                                total
                            }}
                            saleProducts {{
                                product {{
                                    name
                                }}
                                saleProduct {{
                                    amount
                                    price
                                }}
                            }}
                        }}
                    }}
                }}
            ",
            "variables": {{
                "search": {{
                    "saleDate": "2019-11-10"
                }},
                "limit": 10
            }}
        }}
    "#).replace("\n", "");

You can take a look at the full source code here.

Originally published by Werner Echezuría  at dev.to

========================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More

☞ GraphQL with React: The Complete Developers Guide

☞ The Modern GraphQL Bootcamp (with Node.js and Apollo)

☞ Build a Realtime App with React Hooks and GraphQL

☞ Full-Stack React with GraphQL and Apollo Boost

☞ The Rust Programming Language

☞ Rust: Building Reusable Code with Rust from Scratch

☞  Programming in Rust: the good, the bad, the ugly.

☞  An introduction to Web Development with Rust for Node.js Developers

☞ Intro to Web Development with Rust for NodeJS Developers

☞ Introducing the Rust Crash Course