Skip to content

Instantly share code, notes, and snippets.

@jgaskins
Last active May 28, 2024 04:42
Show Gist options
  • Save jgaskins/2ea2657ec9ffacb90cdc09e31c6342da to your computer and use it in GitHub Desktop.
Save jgaskins/2ea2657ec9ffacb90cdc09e31c6342da to your computer and use it in GitHub Desktop.
Comparing Crystal and Rust with HTTP, JSON, and Redis
[package]
name = "post-example"
version = "0.1.0"
edition = "2021"
[dependencies]
rocket = { version = "*", features = ["json"] }
uuid = { version = "0.8", features = ["v4", "serde"] }
chrono = "0.4"
redis = "*"
rocket_db_pools = { version = "*", features = [ "deadpool_redis" ] }
require "armature"
require "json"
require "uuid/json"
require "redis"
REDIS = Redis::Client.new(URI.parse("redis:///?max_idle_pool_size=100"))
class App
include Armature::Route
include HTTP::Handler
def call(context)
route context do |r, response|
r.post "send-money" do
request = if body = r.body
SendMoneyRequest.from_json(body.gets_to_end)
else
response.status = :bad_request
return
end
REDIS.set "transaction:#{request.from.id}:#{request.to.id}", request.amount.to_s
Receipt.new(
from_account: request.from.name,
to_account: request.to.name,
amount: request.amount,
created_on: Time.utc,
to_address: request.to.address.to_s,
).to_json response
end
r.get "hi" do
response << "hi"
end
end
end
end
abstract struct Resource
include JSON::Serializable
macro field(field)
@[JSON::Field(key: {{field.var.id.camelcase(lower: true)}})]
getter {{field}}
end
end
struct AccountHolder < Resource
field id : UUID
field first_name : String
field last_name : String
field address : Address
field email : String
def name
"#{first_name} #{last_name}"
end
end
struct Address < Resource
field street : String
field city : String
field state : State
field zip : String
enum State
CA
NY
WA
TX
FL
IL
PA
OH
GA
MI
NC
NJ
VA
end
def to_s(io) : Nil
io << street << ", " << city << ", " << state << ", " << zip
end
end
struct SendMoneyRequest < Resource
field from : AccountHolder
field to : AccountHolder
field amount : Float64
field send_on : String
end
struct Receipt < Resource
field from_account : String
field to_account : String
field amount : Float64
field created_on : Time
field to_address : String
def initialize(*, @from_account, @to_account, @amount, @created_on, @to_address)
end
end
http = HTTP::Server.new([
App.new,
])
port = ENV.fetch("PORT", "8001").to_i
log = Log.for("post-example", level: :notice)
log.notice { "Listening on #{port}" }
http.listen port
#[macro_use]
extern crate rocket;
use chrono;
use rocket_db_pools::{Connection, Database, deadpool_redis};
use rocket::serde::{json::Json, Deserialize, Serialize};
use rocket::{launch, get};
use std::fmt;
use uuid::Uuid;
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct AccountHolder {
id: Uuid,
firstName: String,
lastName: String,
address: Address,
email: String,
}
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct Address {
street: String,
city: String,
state: State,
zip: String,
}
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct SendMoneyRequest {
from: AccountHolder,
to: AccountHolder,
amount: f64,
sendOn: String, // DateTime is not directly serializable in serde, use String instead
}
#[derive(Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
struct Receipt {
from_account: String,
to_account: String,
amount: f64,
created_on: String, // DateTime is not directly serializable in serde, use String instead
to_address: String,
}
#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
enum State {
CA,
NY,
WA,
TX,
FL,
IL,
PA,
OH,
GA,
MI,
NC,
NJ,
VA,
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
State::CA => write!(f, "CA"),
State::NY => write!(f, "NY"),
State::WA => write!(f, "WA"),
State::TX => write!(f, "TX"),
State::FL => write!(f, "FL"),
State::IL => write!(f, "IL"),
State::PA => write!(f, "PA"),
State::OH => write!(f, "OH"),
State::GA => write!(f, "GA"),
State::MI => write!(f, "MI"),
State::NC => write!(f, "NC"),
State::NJ => write!(f, "NJ"),
State::VA => write!(f, "VA"),
}
}
}
#[derive(Database)]
#[database("redis_pool")]
pub struct RedisPool(deadpool_redis::Pool);
#[get("/hi")]
fn index() -> &'static str {
"hi"
}
#[post("/send-money", data = "<request>")]
async fn send_money(request: Json<SendMoneyRequest>, mut redis: Connection<RedisPool>) -> Json<Receipt> {
let receipt = Receipt {
from_account: format!("{} {}", request.from.firstName, request.from.lastName),
to_account: format!("{} {}", request.to.firstName, request.to.lastName),
amount: request.amount,
created_on: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
to_address: format!(
"{}, {}, {}, {}",
request.to.address.street,
request.to.address.city,
request.to.address.state,
request.to.address.zip
),
};
let _ = redis.send_packed_command(
redis::cmd("SET")
.arg(format!("transaction:{}:{}", request.from.id, request.to.id))
.arg(request.amount)
).await;
Json(receipt)
}
#[launch]
fn rocket() -> _ {
rocket::build()
.mount("/", routes![index, send_money])
.attach(RedisPool::init())
}
name: post-example
version: 0.1.0
dependencies:
armature:
github: jgaskins/armature
crystal: '>= 1.12.1'
license: MIT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment