Initial commit

This commit is contained in:
Franklin 2023-02-23 12:40:19 -04:00
commit da78c9bbab
24 changed files with 4017 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

2906
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

39
Cargo.toml Normal file
View File

@ -0,0 +1,39 @@
[package]
name = "network"
version = "0.1.0"
edition = "2021"
[lib]
name = "network"
crate-type = ["staticlib", "cdylib"]
[dependencies]
# uniffi related:
uniffi = "0.22.0"
uniffi_macros = "0.22.0"
# actual deps:
reqwest = { version = "0.11.13", features = ["json", "blocking"]}
chrono = { version = "0.4", features = [ "serde" ] }
chrono-tz = { version = "0.8" }
thiserror = "1.0.37"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1" }
bincode = "1.3.3"
regex = "1.6.0" # Regular expression utilization
# net & async:
tokio-tungstenite = "0.18.0"
tokio = { version = "1.20.1", features = ["full"] }
futures = "0.3.25"
futures-util = "0.3.26"
# internal deps
err = { git = "https://git.franklinblanco.dev/franklinblanco/err.git" }
league-types = { git = "https://git.franklinblanco.dev/franklinblanco/league-types.git" }
dev-dtos = { git = "https://git.franklinblanco.dev/franklinblanco/user-svc-dtos-rust.git" }
chat-types = { git = "https://git.franklinblanco.dev/franklinblanco/chat-types.git" }
chat-communicators = { git = "https://git.franklinblanco.dev/franklinblanco/chat-communicators.git" }
[build-dependencies]
uniffi_build = "0.22.0"

0
Readme.md Normal file
View File

4
build.rs Normal file
View File

@ -0,0 +1,4 @@
fn main() {
//set_var("OUT_DIR", "./");
uniffi_build::generate_scaffolding("./src/network.udl").unwrap();
}

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

49
src/callbacks/chat.rs Normal file
View File

@ -0,0 +1,49 @@
use std::sync::{Arc};
use dev_dtos::dtos::user::user_dtos::UserForAuthenticationDto;
use tokio::runtime::Runtime;
use crate::{utils::storage, ForeignError, client::chat::init_client_connection};
pub enum ClientError {
One, Two, Three
}
pub trait WebsocketFfi: Send + Sync + std::fmt::Debug {
/// Method that rust will call once a message is recieved from the backend
/// Swift just needs to know the message info to actually update it.
fn message_recieved(&self, message: chat_types::client_types::chat_message::ChatMessage) -> Result<(), ForeignError>;
fn message_sent(&self, ) -> Result<(), ForeignError>;
fn message_delivered(&self, message: chat_types::client_types::chat_message::ChatMessage) -> Result<(), ForeignError>;
fn message_seen(&self, message: chat_types::client_types::chat_message::ChatMessage) -> Result<(), ForeignError>;
fn logged_in(&self) -> Result<(), ForeignError>;
fn error(&self, error: String) -> Result<(), ForeignError>;
fn client_error(&self, error: ClientError) -> Result<(), ForeignError>;
//fn attempt(&self, string_from_rust: String) -> Result<Option<String>, ForeignError>;
}
#[derive(Debug, Default, Clone)]
pub struct WebsocketCaller;
impl<'a> WebsocketCaller {
pub fn new() -> Self {
WebsocketCaller::default()
}
/// Method to be called from swift to initiate websocket connection.
/// This method being called assumes the following:
/// - Valid UserForAuthentication is stored in device storage
pub fn init_ws_connection(&'a self, websocket_ffi: Box<dyn WebsocketFfi>) {
let ws_ffi_rwlock = Arc::new(websocket_ffi);
let user: UserForAuthenticationDto = storage::read("user".into()).unwrap(); //TODO: Remove unwrap
//websocket_ffi.message_recieved(ChatMessage { id: 20, from_id: 2, to_id: 1, message: "What the fuck nigga".as_bytes().to_vec(), message_content: MessageContentType::Text, time_sent: 1, time_delivered: vec![], time_seen: vec![] }).unwrap();
let rt = Runtime::new().unwrap();
let _ = rt.block_on(
init_client_connection(user, ws_ffi_rwlock)
);
}
pub fn send_message(&self, ) {
}
}

1
src/callbacks/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod chat;

55
src/client/base.rs Normal file
View File

@ -0,0 +1,55 @@
use serde::{Serialize, de::DeserializeOwned};
use crate::{RustError, MessageResource};
pub fn perform_request_without_client_sync<B: Serialize, R: DeserializeOwned>(
base_url: String,
method: reqwest::Method,
path: String,
body: Option<B>,
expected_status_code: u16,
headers: Vec<(String, String)>,
) -> Result<R, RustError> {
let client = reqwest::blocking::Client::new();
let mut req_incomplete =
client.request(method, format!("{url}{path}", url = base_url, path = path));
for header in headers {
req_incomplete = req_incomplete.header(&header.0, &header.1);
}
let req_complete = match body {
Some(b) => req_incomplete.json(&b),
None => req_incomplete.header("content-length", 0),
};
println!("{:?}", req_complete);
match req_complete.send() {
// Error handling here
Ok(res) => {
// Request sent correctly
match res.status().as_u16() == expected_status_code {
true => {
match res.json::<R>() {
Ok(resp_dto) => Ok(resp_dto), // Return correctly deserialized obj
Err(err) => Err(RustError::SerdeError{ error: MessageResource { key: "COMMUNICATOR.DESERIALIZE_JSON_FAILED".into(), message: Some(err.to_string()) }, serde_error_str: None }),
}
}
false => {
//If status code is any other than expected
Err(RustError::UnexpectedStatusCode{
expected: expected_status_code,
actual: res.status().as_u16(),
errors: match res.json::<Vec<MessageResource>>() {
Ok(messages) => messages,
Err(e) => vec![MessageResource { key: "COMMUNICATOR.NO_ERRORS".into(), message: Some(e.to_string()) }],
},
})
}
}
}
Err(e) => {
// Request couldn't be sent
Err(RustError::Network{error: MessageResource { key: "COMMUNICATOR.NETWORK".into(), message: Some(e.to_string()) }})
}
}
}

View File

@ -0,0 +1,25 @@
use std::sync::{Arc};
use chat_types::dto::server_out::ServerMessageOut;
use tokio_tungstenite::tungstenite::Message;
use crate::{WebsocketFfi};
use super::utils::interpret_message;
pub async fn handle_message(message: Message, ws_caller: &Arc<Box<dyn WebsocketFfi>>) -> Result<(), Box<dyn std::error::Error + Send + Sync>>{
let server_message_out = interpret_message(message)?;
match server_message_out {
ServerMessageOut::Acknowledge => {
todo!()
},
ServerMessageOut::LoggedIn => ws_caller.logged_in()?,
ServerMessageOut::MessageSent => ws_caller.message_sent()?,
ServerMessageOut::MessageRecieved(message) => ws_caller.message_recieved(message.into())?,
ServerMessageOut::MessageDelivered(message) => ws_caller.message_delivered(message.into())?,
ServerMessageOut::MessageSeen(message) => ws_caller.message_seen(message.into())?,
ServerMessageOut::Error(error) => ws_caller.error(error)?,
}
Ok(())
}

96
src/client/chat/mod.rs Normal file
View File

@ -0,0 +1,96 @@
pub mod utils; pub mod handler;
use std::{sync::{RwLock, Arc}};
use chat_types::{dto::server_in::ServerMessageIn, domain::error::SocketError};
use dev_dtos::dtos::user::user_dtos::UserForAuthenticationDto;
use tokio::sync::Mutex;
use tokio_tungstenite::{connect_async};
use futures_util::{StreamExt};
use crate::{WebsocketFfi};
use self::{utils::send_message, handler::handle_message};
// TODO: Message queue for sending
static _MESSAGE_QUEUE: RwLock<Vec<ServerMessageIn>> = RwLock::new(Vec::new());
pub async fn init_client_connection(user: UserForAuthenticationDto, ws_caller: Arc<Box<dyn WebsocketFfi>>) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let ws_stream = match connect_async("ws://0.0.0.0:3000/websocket").await {
Ok((stream, _response)) => {
stream
}
Err(e) => {
println!("WebSocket handshake for server failed with {e}!");
return Err(SocketError::boxed_error(format!("WebSocket handshake for server failed with {e}!")));
}
};
let (sender, mut receiver) = ws_stream.split();
//
// Login
//
//spawn an async sender to push some more messages into the server
let caller = ws_caller.clone();
//receiver just prints whatever it gets
let mut recv_task = tokio::spawn(async move {
while let Some(Ok(msg)) = receiver.next().await {
// Never break this loop?
let a = handle_message(msg, &caller).await;
println!("Something happened {:?}", a);
}
});
let sender_arc = Arc::new(Mutex::new(sender));
send_message(sender_arc.clone(), ServerMessageIn::Login(user)).await?;
//wait for either task to finish and kill the other task
tokio::select! {
/*_ = (&mut send_task) => {
recv_task.abort();
},*/
_ = (&mut recv_task) => {
}
}
Ok(())
}
/*
/// Function to handle messages we get (with a slight twist that Frame variant is visible
/// since we are working with the underlying tungstenite library directly without axum here).
fn process_message(msg: Message) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
match msg {
Message::Text(t) => {
println!(">>> got str: {:?}", t);
}
Message::Binary(d) => {
println!(">>> got {} bytes: {:?}", d.len(), d);
}
Message::Close(c) => {
if let Some(cf) = c {
println!(
">>> got close with code {} and reason `{}`",
cf.code, cf.reason
);
} else {
println!(">>> somehow got close message without CloseFrame");
}
return ControlFlow::Break(());
}
Message::Pong(v) => {
println!(">>> got pong with {:?}", v);
}
// Just as with axum server, the underlying tungstenite websocket library
// will handle Ping for you automagically by replying with Pong and copying the
// v according to spec. But if you need the contents of the pings you can see them here.
Message::Ping(v) => {
println!(">>> got ping with {:?}", v);
}
Message::Frame(_) => {
unreachable!("This is never supposed to happen")
}
}
Ok(())
}*/

43
src/client/chat/utils.rs Normal file
View File

@ -0,0 +1,43 @@
use std::sync::Arc;
use chat_types::{dto::{server_in::{ServerMessageIn, Receivable}, server_out::{ServerMessageOut, Sendable}, message::ClientMessage}, domain::error::SocketError};
use futures::{SinkExt};
use tokio::sync::Mutex;
use tokio_tungstenite::{tungstenite::{Message}};
/// Este es el metodo para enviar mensajes a un cliente a traves de un websocket
/// Si le pasas un None en el payload tienes que darle un tipo al metodo, ya que
/// El compilador no permite especificarle un metodo default.
pub async fn send_message<S>(
sender: Arc<Mutex<S>>,
message: ServerMessageIn,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> where S: futures_util::Sink<tokio_tungstenite::tungstenite::Message> + Unpin{
Ok(
match sender
.lock()
.await
.send(Message::Text(serde_json::to_string(
&message.into_message()?,
)?))
.await {
Ok(_) => (),
Err(_) => return Err(SocketError::boxed_error("Message couldn't be sent for some weird reason...")),
})
}
/// use this function to convert a Message::Text() from a client socket connection
/// into a ClientMessage<Payload>
pub fn interpret_message(
message: Message,
) -> Result<ServerMessageOut, Box<dyn std::error::Error + Send + Sync>> {
if let Message::Text(txt) = message {
// txt should be a {"type": "SOMETHING"} or a {"type": "SOMETHING", "payload": {}}
let client_message: ClientMessage = serde_json::from_str(txt.as_str())?; //Add error message?
Ok(ServerMessageOut::from_message(client_message)?)
} else {
Err(SocketError::boxed_error(
"Recieved client Message is not of type Text...",
))
}
}

118
src/client/league/mod.rs Normal file
View File

@ -0,0 +1,118 @@
use dev_dtos::{domain::user::token::Token, dtos::user::user_dtos::{UserForLoginDto, UserForAuthenticationDto}};
use league_types::{domain::{sport::Sport, player::Player, league::League, place::Place, league_player::LeaguePlayer, enums::league_player_status::LeaguePlayerStatus, trust::Trust}, dto::{player::{PlayerForCreationDto, PlayerForUpdateDto, PlayerProfileDto}, league::LeagueForCreationDto, league_player::JoinRequest, trust::TrustRequestDto}};
use reqwest::Method;
use crate::{client::base::perform_request_without_client_sync as perform_request, RustError};
pub const BASE_URL: &str = "http://backend.blancoinfante.com/";
// #############
// SPORT ROUTES
// #############
pub fn get_all_sports() -> Result<Vec<Sport>, RustError> {
perform_request::<(), Vec<Sport>>(BASE_URL.to_string(), Method::GET, "league/sport".into(), None, 200, vec![])
}
// #############
// PLAYER ROUTES
// #############
pub fn create_player_profile(player: PlayerForCreationDto) -> Result<Token, RustError> {
perform_request::<PlayerForCreationDto, Token>(BASE_URL.to_string(), Method::POST, "league/player".into(), Some(player), 200, vec![])
}
pub fn edit_player_profile(player: PlayerForUpdateDto) -> Result<Player, RustError> {
perform_request::<PlayerForUpdateDto, Player>(BASE_URL.to_string(), Method::PUT, "league/player".into(), Some(player), 200, vec![])
}
pub fn login(user: UserForLoginDto) -> Result<Token, RustError> {
perform_request::<UserForLoginDto, Token>(BASE_URL.to_string(), Method::POST, "league/player/login".into(), Some(user), 200, vec![])
}
pub fn get_player_profile(player_id: u32) -> Result<PlayerProfileDto, RustError> {
perform_request::<(), PlayerProfileDto>(BASE_URL.to_string(), Method::GET, format!("league/player/profile/{}", player_id), None, 200, vec![])
}
pub fn get_player_trusted_list(player_id: u32) -> Result<Vec<Player>, RustError> {
perform_request::<(), Vec<Player>>(BASE_URL.to_string(), Method::GET, format!("league/player/trusted_by/{}", player_id), None, 200, vec![])
}
// #############
// LEAGUE ROUTES
// #############
pub fn create_league(league: LeagueForCreationDto) -> Result<League, RustError> {
perform_request::<LeagueForCreationDto, League>(BASE_URL.to_string(), Method::POST, "league/league".into(), Some(league), 200, vec![])
}
pub fn get_open_leagues_in_my_area(page: u16, user: UserForAuthenticationDto) -> Result<Vec<League>, RustError> {
perform_request::<UserForAuthenticationDto, Vec<League>>(BASE_URL.to_string(), Method::POST, format!("league/league/nearme/{}", page), Some(user), 200, vec![])
}
pub fn get_leagues_in_my_country(country: String, page: u16) -> Result<Vec<League>, RustError> {
perform_request::<(), Vec<League>>(BASE_URL.to_string(), Method::GET, format!("league/league/{}/{}", country, page), None, 200, vec![])
}
pub fn get_specific_league(league_id: u32) -> Result<League, RustError> {
perform_request::<(), League>(BASE_URL.to_string(), Method::GET, format!("league/league/{}", league_id), None, 200, vec![])
}
pub fn get_leagues_hosted_by_player(user: UserForAuthenticationDto, player_id: u32, page: u16) -> Result<Vec<League>, RustError> {
perform_request::<UserForAuthenticationDto, Vec<League>>(BASE_URL.to_string(), Method::POST, format!("league/league/player/{}/{}", player_id, page), Some(user), 200, vec![])
}
pub fn get_leagues_in_place(place_id: u32, page: u16) -> Result<Vec<League>, RustError> {
perform_request::<(), Vec<League>>(BASE_URL.to_string(), Method::GET, format!("league/league/place/{}/{}", place_id, page), None, 200, vec![])
}
pub fn get_average_league_age(user: UserForAuthenticationDto, league_id: u32) -> Result<u8, RustError> {
perform_request::<UserForAuthenticationDto, u8>(BASE_URL.to_string(), Method::POST, format!("league/league/{}/age", league_id), Some(user), 200, vec![])
}
// #############
// PLACE ROUTES
// #############
pub fn get_places_for_country(country: String, page: u16) -> Result<Vec<Place>, RustError> {
perform_request::<(), Vec<Place>>(BASE_URL.to_string(), Method::GET, format!("league/place/country/{}/page/{}", country, page), None, 200, vec![])
}
pub fn get_places_for_sport(sport_id: u32, page: u16) -> Result<Vec<Place>, RustError> {
perform_request::<(), Vec<Place>>(BASE_URL.to_string(), Method::GET, format!("league/place/sport/{}/page/{}", sport_id, page), None, 200, vec![])
}
pub fn get_places_in_my_area(user: UserForAuthenticationDto, page: u16) -> Result<Vec<Place>, RustError> {
perform_request::<UserForAuthenticationDto, Vec<Place>>(BASE_URL.to_string(), Method::POST, format!("league/place/nearme/{}", page), Some(user), 200, vec![])
}
// ####################
// LEAGUE_PLAYER ROUTES
// ####################
pub fn request_to_join_league(join_req: JoinRequest) -> Result<LeaguePlayer, RustError> {
perform_request::<JoinRequest, LeaguePlayer>(BASE_URL.to_string(), Method::POST, "league/league_player/request".into(), Some(join_req), 200, vec![])
}
pub fn get_league_request_status(join_req: JoinRequest) -> Result<LeaguePlayer, RustError> {
perform_request::<JoinRequest, LeaguePlayer>(BASE_URL.to_string(), Method::POST, "league/league_player/request/status".into(), Some(join_req), 200, vec![])
}
pub fn change_league_request_status(status: LeaguePlayerStatus, join_req: JoinRequest) -> Result<LeaguePlayer, RustError> {
perform_request::<JoinRequest, LeaguePlayer>(BASE_URL.to_string(), Method::PUT, format!("league/league_player/request/{}", status), Some(join_req), 200, vec![])
}
pub fn get_all_leagues_player_has_applied_to(join_req: JoinRequest, page: u16) -> Result<Vec<League>, RustError> {
perform_request::<JoinRequest, Vec<League>>(BASE_URL.to_string(), Method::POST, format!("league/league_player/leagues/{}", page), Some(join_req), 200, vec![])
}
pub fn get_all_players_in_league(join_req: JoinRequest) -> Result<Vec<Player>, RustError> {
perform_request::<JoinRequest, Vec<Player>>(BASE_URL.to_string(), Method::POST, "league/league_player/players".into(), Some(join_req), 200, vec![])
}
// #############
// TRUST ROUTES
// #############
pub fn add_trusted_player(trust_req: TrustRequestDto) -> Result<Trust, RustError> {
perform_request::<TrustRequestDto, Trust>(BASE_URL.to_string(), Method::POST, "league/trust".into(), Some(trust_req), 200, vec![])
}
pub fn remove_trusted_player(trust_req: TrustRequestDto) -> Result<Trust, RustError> {
perform_request::<TrustRequestDto, Trust>(BASE_URL.to_string(), Method::DELETE, "league/trust".into(), Some(trust_req), 200, vec![])
}

3
src/client/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod league;
pub mod base;
pub mod chat;

34
src/lib.rs Normal file
View File

@ -0,0 +1,34 @@
uniffi_macros::include_scaffolding!("network");
pub mod types;
pub mod client;
pub mod utils;
pub mod callbacks;
pub use chat_types::client_types::chat_room::ChatRoom;
pub use dev_dtos::dtos::user::user_dtos::UserForAuthenticationDto;
use utils::storage;
pub use utils::storage::*;
pub use callbacks::chat::*;
pub use league_types::domain::sport::Sport;
pub use types::error::*;
//pub use chat_communicators::client::chat::*;
pub use chat_types::client_types::chat_message::*;
pub fn get_all_sports() -> Result<Vec<Sport>, RustError> {
client::league::get_all_sports()
}
pub fn get_me() -> Result<UserForAuthenticationDto, RustError> {
storage::read("user".into())
}
#[macro_export]
macro_rules! unwrap_rust_error {
($e:expr) => {
match $e {
Ok(result) => result,
Err(error) => return Err(error.into())
}
};
}

373
src/network.swift Normal file
View File

@ -0,0 +1,373 @@
// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!
import Foundation
// Depending on the consumer's build setup, the low-level FFI code
// might be in a separate module, or it might be compiled inline into
// this module. This is a bit of light hackery to work with both.
#if canImport(networkFFI)
import networkFFI
#endif
fileprivate extension RustBuffer {
// Allocate a new buffer, copying the contents of a `UInt8` array.
init(bytes: [UInt8]) {
let rbuf = bytes.withUnsafeBufferPointer { ptr in
RustBuffer.from(ptr)
}
self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data)
}
static func from(_ ptr: UnsafeBufferPointer<UInt8>) -> RustBuffer {
try! rustCall { ffi_network_a3fa_rustbuffer_from_bytes(ForeignBytes(bufferPointer: ptr), $0) }
}
// Frees the buffer in place.
// The buffer must not be used after this is called.
func deallocate() {
try! rustCall { ffi_network_a3fa_rustbuffer_free(self, $0) }
}
}
fileprivate extension ForeignBytes {
init(bufferPointer: UnsafeBufferPointer<UInt8>) {
self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress)
}
}
// For every type used in the interface, we provide helper methods for conveniently
// lifting and lowering that type from C-compatible data, and for reading and writing
// values of that type in a buffer.
// Helper classes/extensions that don't change.
// Someday, this will be in a libray of its own.
fileprivate extension Data {
init(rustBuffer: RustBuffer) {
// TODO: This copies the buffer. Can we read directly from a
// Rust buffer?
self.init(bytes: rustBuffer.data!, count: Int(rustBuffer.len))
}
}
// A helper class to read values out of a byte buffer.
fileprivate class Reader {
let data: Data
var offset: Data.Index
init(data: Data) {
self.data = data
self.offset = 0
}
// Reads an integer at the current offset, in big-endian order, and advances
// the offset on success. Throws if reading the integer would move the
// offset past the end of the buffer.
func readInt<T: FixedWidthInteger>() throws -> T {
let range = offset..<offset + MemoryLayout<T>.size
guard data.count >= range.upperBound else {
throw UniffiInternalError.bufferOverflow
}
if T.self == UInt8.self {
let value = data[offset]
offset += 1
return value as! T
}
var value: T = 0
let _ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0, from: range)})
offset = range.upperBound
return value.bigEndian
}
// Reads an arbitrary number of bytes, to be used to read
// raw bytes, this is useful when lifting strings
func readBytes(count: Int) throws -> Array<UInt8> {
let range = offset..<(offset+count)
guard data.count >= range.upperBound else {
throw UniffiInternalError.bufferOverflow
}
var value = [UInt8](repeating: 0, count: count)
value.withUnsafeMutableBufferPointer({ buffer in
data.copyBytes(to: buffer, from: range)
})
offset = range.upperBound
return value
}
// Reads a float at the current offset.
@inlinable
func readFloat() throws -> Float {
return Float(bitPattern: try readInt())
}
// Reads a float at the current offset.
@inlinable
func readDouble() throws -> Double {
return Double(bitPattern: try readInt())
}
// Indicates if the offset has reached the end of the buffer.
@inlinable
func hasRemaining() -> Bool {
return offset < data.count
}
}
// A helper class to write values into a byte buffer.
fileprivate class Writer {
var bytes: [UInt8]
var offset: Array<UInt8>.Index
init() {
self.bytes = []
self.offset = 0
}
func writeBytes<S>(_ byteArr: S) where S: Sequence, S.Element == UInt8 {
bytes.append(contentsOf: byteArr)
}
// Writes an integer in big-endian order.
//
// Warning: make sure what you are trying to write
// is in the correct type!
func writeInt<T: FixedWidthInteger>(_ value: T) {
var value = value.bigEndian
withUnsafeBytes(of: &value) { bytes.append(contentsOf: $0) }
}
@inlinable
func writeFloat(_ value: Float) {
writeInt(value.bitPattern)
}
@inlinable
func writeDouble(_ value: Double) {
writeInt(value.bitPattern)
}
}
// Protocol for types that transfer other types across the FFI. This is
// analogous go the Rust trait of the same name.
fileprivate protocol FfiConverter {
associatedtype FfiType
associatedtype SwiftType
static func lift(_ value: FfiType) throws -> SwiftType
static func lower(_ value: SwiftType) -> FfiType
static func read(from buf: Reader) throws -> SwiftType
static func write(_ value: SwiftType, into buf: Writer)
}
// Types conforming to `Primitive` pass themselves directly over the FFI.
fileprivate protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType { }
extension FfiConverterPrimitive {
static func lift(_ value: FfiType) throws -> SwiftType {
return value
}
static func lower(_ value: SwiftType) -> FfiType {
return value
}
}
// Types conforming to `FfiConverterRustBuffer` lift and lower into a `RustBuffer`.
// Used for complex types where it's hard to write a custom lift/lower.
fileprivate protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustBuffer {}
extension FfiConverterRustBuffer {
static func lift(_ buf: RustBuffer) throws -> SwiftType {
let reader = Reader(data: Data(rustBuffer: buf))
let value = try read(from: reader)
if reader.hasRemaining() {
throw UniffiInternalError.incompleteData
}
buf.deallocate()
return value
}
static func lower(_ value: SwiftType) -> RustBuffer {
let writer = Writer()
write(value, into: writer)
return RustBuffer(bytes: writer.bytes)
}
}
// An error type for FFI errors. These errors occur at the UniFFI level, not
// the library level.
fileprivate enum UniffiInternalError: LocalizedError {
case bufferOverflow
case incompleteData
case unexpectedOptionalTag
case unexpectedEnumCase
case unexpectedNullPointer
case unexpectedRustCallStatusCode
case unexpectedRustCallError
case unexpectedStaleHandle
case rustPanic(_ message: String)
public var errorDescription: String? {
switch self {
case .bufferOverflow: return "Reading the requested value would read past the end of the buffer"
case .incompleteData: return "The buffer still has data after lifting its containing value"
case .unexpectedOptionalTag: return "Unexpected optional tag; should be 0 or 1"
case .unexpectedEnumCase: return "Raw enum value doesn't match any cases"
case .unexpectedNullPointer: return "Raw pointer value was null"
case .unexpectedRustCallStatusCode: return "Unexpected RustCallStatus code"
case .unexpectedRustCallError: return "CALL_ERROR but no errorClass specified"
case .unexpectedStaleHandle: return "The object in the handle map has been dropped already"
case let .rustPanic(message): return message
}
}
}
fileprivate let CALL_SUCCESS: Int8 = 0
fileprivate let CALL_ERROR: Int8 = 1
fileprivate let CALL_PANIC: Int8 = 2
fileprivate extension RustCallStatus {
init() {
self.init(
code: CALL_SUCCESS,
errorBuf: RustBuffer.init(
capacity: 0,
len: 0,
data: nil
)
)
}
}
private func rustCall<T>(_ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T {
try makeRustCall(callback, errorHandler: {
$0.deallocate()
return UniffiInternalError.unexpectedRustCallError
})
}
private func rustCallWithError<T, F: FfiConverter>
(_ errorFfiConverter: F.Type, _ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T
where F.SwiftType: Error, F.FfiType == RustBuffer
{
try makeRustCall(callback, errorHandler: { return try errorFfiConverter.lift($0) })
}
private func makeRustCall<T>(_ callback: (UnsafeMutablePointer<RustCallStatus>) -> T, errorHandler: (RustBuffer) throws -> Error) throws -> T {
var callStatus = RustCallStatus.init()
let returnedVal = callback(&callStatus)
switch callStatus.code {
case CALL_SUCCESS:
return returnedVal
case CALL_ERROR:
throw try errorHandler(callStatus.errorBuf)
case CALL_PANIC:
// When the rust code sees a panic, it tries to construct a RustBuffer
// with the message. But if that code panics, then it just sends back
// an empty buffer.
if callStatus.errorBuf.len > 0 {
throw UniffiInternalError.rustPanic(try FfiConverterString.lift(callStatus.errorBuf))
} else {
callStatus.errorBuf.deallocate()
throw UniffiInternalError.rustPanic("Rust panic")
}
default:
throw UniffiInternalError.unexpectedRustCallStatusCode
}
}
// Public interface members begin here.
fileprivate struct FfiConverterUInt8: FfiConverterPrimitive {
typealias FfiType = UInt8
typealias SwiftType = UInt8
static func read(from buf: Reader) throws -> UInt8 {
return try lift(buf.readInt())
}
static func write(_ value: UInt8, into buf: Writer) {
buf.writeInt(lower(value))
}
}
fileprivate struct FfiConverterInt32: FfiConverterPrimitive {
typealias FfiType = Int32
typealias SwiftType = Int32
static func read(from buf: Reader) throws -> Int32 {
return try lift(buf.readInt())
}
static func write(_ value: Int32, into buf: Writer) {
buf.writeInt(lower(value))
}
}
fileprivate struct FfiConverterString: FfiConverter {
typealias SwiftType = String
typealias FfiType = RustBuffer
static func lift(_ value: RustBuffer) throws -> String {
defer {
value.deallocate()
}
if value.data == nil {
return String()
}
let bytes = UnsafeBufferPointer<UInt8>(start: value.data!, count: Int(value.len))
return String(bytes: bytes, encoding: String.Encoding.utf8)!
}
static func lower(_ value: String) -> RustBuffer {
return value.utf8CString.withUnsafeBufferPointer { ptr in
// The swift string gives us int8_t, we want uint8_t.
ptr.withMemoryRebound(to: UInt8.self) { ptr in
// The swift string gives us a trailing null byte, we don't want it.
let buf = UnsafeBufferPointer(rebasing: ptr.prefix(upTo: ptr.count - 1))
return RustBuffer.from(buf)
}
}
}
static func read(from buf: Reader) throws -> String {
let len: Int32 = try buf.readInt()
return String(bytes: try buf.readBytes(count: Int(len)), encoding: String.Encoding.utf8)!
}
static func write(_ value: String, into buf: Writer) {
let len = Int32(value.utf8.count)
buf.writeInt(len)
buf.writeBytes(value.utf8)
}
}
public func `deez`(`ab`: Int32) -> UInt8 {
return try! FfiConverterUInt8.lift(
try!
rustCall() {
network_a3fa_deez(
FfiConverterInt32.lower(`ab`), $0)
}
)
}
/**
* Top level initializers and tear down methods.
*
* This is generated by uniffi.
*/
public enum NetworkLifecycle {
/**
* Initialize the FFI and Rust library. This should be only called once per application.
*/
func initialize() {
}
}

89
src/network.udl Normal file
View File

@ -0,0 +1,89 @@
dictionary MessageResource {
string key;
string? message;
};
dictionary Sport {
u32 id;
string name;
u32 category_id;
};
[Error]
enum RustError {
"Network",
"UnexpectedStatusCode",
"SerdeError",
"IO",
"Uknown",
};
[Error]
enum ForeignError {
"FfiCallbackError"
};
dictionary UserForAuthenticationDto {
string app;
string id;
string token;
};
dictionary TimeSensitiveAction {
u32 by;
i64 time;
};
enum MessageContentType {
"Text",
"Image",
"Audio",
"Video",
};
dictionary ChatMessage {
u32 id;
u32 from_id;
u32 to_id;
sequence<u8> message;
MessageContentType message_content;
i64 time_sent;
sequence<TimeSensitiveAction> time_delivered;
sequence<TimeSensitiveAction> time_seen;
};
dictionary ChatRoom {
u32 id;
string title;
u32 owner_id;
i64 time_created;
i64 last_updated;
u64 session_messages;
};
enum ClientError {
"One",
"Two",
"Three",
};
callback interface WebsocketFfi {
[Throws=ForeignError]
void message_recieved(ChatMessage message);
[Throws=ForeignError]
void logged_in();
[Throws=ForeignError]
void message_seen(ChatMessage message);
[Throws=ForeignError]
void message_delivered(ChatMessage message);
[Throws=ForeignError]
void message_sent();
[Throws=ForeignError]
void error(string error);
[Throws=ForeignError]
void client_error(ClientError error);
};
interface WebsocketCaller {
constructor();
void init_ws_connection(WebsocketFfi websocket_ffi);
};
namespace network {
[Throws=RustError]
UserForAuthenticationDto get_me();
[Throws=RustError]
sequence<Sport> get_all_sports();
void init_storage(string path);
};

68
src/networkFFI.h Normal file
View File

@ -0,0 +1,68 @@
// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!
#pragma once
#include <stdbool.h>
#include <stdint.h>
// The following structs are used to implement the lowest level
// of the FFI, and thus useful to multiple uniffied crates.
// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H.
#ifdef UNIFFI_SHARED_H
// We also try to prevent mixing versions of shared uniffi header structs.
// If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4
#ifndef UNIFFI_SHARED_HEADER_V4
#error Combining helper code from multiple versions of uniffi is not supported
#endif // ndef UNIFFI_SHARED_HEADER_V4
#else
#define UNIFFI_SHARED_H
#define UNIFFI_SHARED_HEADER_V4
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
typedef struct RustBuffer
{
int32_t capacity;
int32_t len;
uint8_t *_Nullable data;
} RustBuffer;
typedef int32_t (*ForeignCallback)(uint64_t, int32_t, RustBuffer, RustBuffer *_Nonnull);
typedef struct ForeignBytes
{
int32_t len;
const uint8_t *_Nullable data;
} ForeignBytes;
// Error definitions
typedef struct RustCallStatus {
int8_t code;
RustBuffer errorBuf;
} RustCallStatus;
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
#endif // def UNIFFI_SHARED_H
uint8_t network_a3fa_deez(
int32_t ab,
RustCallStatus *_Nonnull out_status
);
RustBuffer ffi_network_a3fa_rustbuffer_alloc(
int32_t size,
RustCallStatus *_Nonnull out_status
);
RustBuffer ffi_network_a3fa_rustbuffer_from_bytes(
ForeignBytes bytes,
RustCallStatus *_Nonnull out_status
);
void ffi_network_a3fa_rustbuffer_free(
RustBuffer buf,
RustCallStatus *_Nonnull out_status
);
RustBuffer ffi_network_a3fa_rustbuffer_reserve(
RustBuffer buf,int32_t additional,
RustCallStatus *_Nonnull out_status
);

6
src/networkFFI.modulemap Normal file
View File

@ -0,0 +1,6 @@
// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!
module networkFFI {
header "networkFFI.h"
export *
}

64
src/types/error.rs Normal file
View File

@ -0,0 +1,64 @@
use std::fmt::Display;
use serde::{Serialize, Deserialize};
#[derive(Debug, thiserror::Error, Serialize, Deserialize, Clone, PartialEq, PartialOrd)]
pub struct MessageResource {
pub key: String,
pub message: Option<String>
}
impl Display for MessageResource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MessageResource: Key: {}, Error: {:?}", self.key, self.message)
}
}
#[derive(Debug, thiserror::Error)]
pub enum RustError {
#[error("Network Error {error}")]
Network{error: MessageResource},
#[error("UnexpectedStatusCode expected: {expected}, actual: {actual}, errors: {:?}", errors)]
UnexpectedStatusCode{expected: u16, actual: u16, errors: Vec<MessageResource>},
#[error("SerdeError Error {error}, attempted to SerdeError string: {:?}", serde_error_str)]
SerdeError{error: MessageResource, serde_error_str: Option<String>},
#[error("IOError Error {error} ")]
IO{error: MessageResource},
#[error("Uknown error")]
Uknown,
}
#[derive(Debug, thiserror::Error)]
pub enum ForeignError {
#[error("FFiCallbackError")]
FfiCallbackError,
}
impl From<err::Error> for RustError {
fn from(value: err::Error) -> Self {
match value {
err::Error::Network(msg) => RustError::Network { error: msg.into() },
err::Error::IO(msg) => RustError::IO { error: msg.into() },
err::Error::UnexpectedStatusCode(expected, actual, messages) => RustError::UnexpectedStatusCode { expected, actual, errors: messages.into_iter().map(|message| message.into()).collect::<Vec<crate::MessageResource>>() },
err::Error::Serde(msg) => RustError::SerdeError { error: msg.into(), serde_error_str: None },
_ => RustError::Uknown,
}
}
}
pub fn convert_error(error: err::Error) -> RustError {
error.into()
}
impl From<err::MessageResource> for crate::types::error::MessageResource {
fn from(value: err::MessageResource) -> Self {
Self { key: match value.key {
Some(key) => key,
None => "".into(),
}, message: Some(value.message) }
}
}
impl From<uniffi::UnexpectedUniFFICallbackError> for ForeignError {
fn from(_value: uniffi::UnexpectedUniFFICallbackError) -> Self {
Self::FfiCallbackError
}
}

1
src/types/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod error;

1
src/utils/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod storage;

41
src/utils/storage.rs Normal file
View File

@ -0,0 +1,41 @@
use std::{sync::RwLock, fs};
use dev_dtos::dtos::user::user_dtos::UserForAuthenticationDto;
use serde::{Serialize, de::DeserializeOwned};
use crate::{RustError, MessageResource};
static STORAGE_PATH: RwLock<String> = RwLock::new(String::new());
/// This function MUST BE CALLED on startup or else Storage won't work at all
pub fn init_storage(path: String) {
let mut write = STORAGE_PATH.write().expect("FATAL ERROR. FAILED TO SECURE A RWLOCK CORRECTLY. STORAGE WON'T WORK NOW.");
*write = path;
drop(write);
// TESTING PURPOSES
store(UserForAuthenticationDto{ app: "".into(), id: "3".into(), token: "/2uuNJG3Z2bT9VVd64xBeACPxg64GicloiXtG9uO87as5q5g46TtNu0sAVTACyR8R8uMVXoTBlBP4Q3JhcGB2Q==".to_string() }, "user".into()).unwrap();
println!("Wrote shit");
}
// These functions won't be exposed to the clients, just used by the functions they call internally
pub fn store<T: Serialize>(ty: T, path: String) -> Result<(), RustError>{
let serialized_ty = match bincode::serialize(&ty) {
Ok(bytes) => bytes,
Err(e) => return Err(RustError::SerdeError { error: MessageResource { key: "STORAGE.OBJECT_SERIALIZATION".into(), message: Some(e.to_string()) }, serde_error_str: None }),
};
match fs::write(format!("{}/{}", STORAGE_PATH.read().unwrap(), path), serialized_ty) {
Ok(_) => Ok(()),
Err(e) => Err(RustError::IO { error: MessageResource { key:"STORAGE.OBJECT_WRITING".into(), message: Some(e.to_string())} }),
}
}
pub fn read<T: DeserializeOwned>(path: String) -> Result<T, RustError> {
let bytes = match fs::read(format!("{}/{}", STORAGE_PATH.read().unwrap(), path)) {
Ok(bytes) => bytes,
Err(e) => return Err(RustError::IO { error: MessageResource { key:"STORAGE.OBJECT_READING".into(), message: Some(e.to_string())} }),
};
match bincode::deserialize(&bytes) {
Ok(object) => Ok(object),
Err(e) => return Err(RustError::SerdeError { error: MessageResource { key: "STORAGE.OBJECT_DESERIALIZATION".into(), message: Some(e.to_string()) }, serde_error_str: None }),
}
}