This commit is contained in:
Jonas Rabenstein 2026-03-05 02:05:32 +01:00
commit b09e8eafbb
16 changed files with 2531 additions and 258 deletions

View file

@ -1,5 +1,5 @@
use restson::{Error, Response, RestClient, RestPath};
use serde::{Deserialize, Serialize};
use restson::{RestClient, RestPath, Error, Response};
#[derive(Debug, Copy, Clone, Serialize)]
struct Email<'a> {
@ -37,8 +37,8 @@ impl<'a> RestPath<()> for Token<'a> {
}
pub mod token {
use serde::{Serialize, Deserialize};
use crate::util::DateTime;
use serde::{Deserialize, Serialize};
use std::ops::Deref;
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -114,14 +114,25 @@ where
Ok(tokens.into_inner())
}
pub fn email<'a>(client: &'a RestClient, email: &'a str, password: &'a str) -> impl Future<Output=Result<Tokens, Error>> + 'a {
pub fn email<'a>(
client: &'a RestClient,
email: &'a str,
password: &'a str,
) -> impl Future<Output = Result<Tokens, Error>> + 'a {
authenticate(client, Email { email, password })
}
pub fn phone<'a>(client: &'a RestClient, phone: &'a str, password: &'a str) -> impl Future<Output=Result<Tokens, Error>> + 'a {
pub fn phone<'a>(
client: &'a RestClient,
phone: &'a str,
password: &'a str,
) -> impl Future<Output = Result<Tokens, Error>> + 'a {
authenticate(client, Phone { phone, password })
}
pub fn token<'a>(client: &'a RestClient, token: &'a str) -> impl Future<Output=Result<Tokens, Error>> + 'a {
pub fn token<'a>(
client: &'a RestClient,
token: &'a str,
) -> impl Future<Output = Result<Tokens, Error>> + 'a {
authenticate(client, Token { token })
}

View file

@ -58,16 +58,13 @@ impl<'a> Query<'a> {
}
#[derive(Serialize)]
#[derive(Default)]
pub enum Order {
#[default]
Ascending,
Descending,
}
impl Default for Order {
fn default() -> Self {
Self::Ascending
}
}
impl std::fmt::Display for Order {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

View file

@ -37,8 +37,5 @@ pub async fn identity(client: &RestClient) -> Result<Profile, Error> {
}
pub async fn with_id(client: &RestClient, id: Id) -> Result<Profile, Error> {
Ok(client
.get_with::<_, Profile>(id, &[])
.await?
.into_inner())
Ok(client.get_with::<_, Profile>(id, &[]).await?.into_inner())
}

View file

@ -64,13 +64,10 @@ impl Spond {
#[builder]
pub fn response(
&self,
#[builder(start_fn)]
member: MemberId,
#[builder(finish_fn)]
client: &RestClient,
#[builder(default = true)]
accepted: bool,
) -> impl Future<Output=Result<Responses, Error>> {
#[builder(start_fn)] member: MemberId,
#[builder(finish_fn)] client: &RestClient,
#[builder(default = true)] accepted: bool,
) -> impl Future<Output = Result<Responses, Error>> {
response(self.id)
.member(member)
.accepted(accepted)
@ -78,27 +75,21 @@ impl Spond {
}
#[builder]
pub fn accept(&self,
#[builder(start_fn)]
member: MemberId,
#[builder(finish_fn)]
client: &RestClient,
) -> impl Future<Output=Result<Responses, Error>> {
self.response(member)
.accepted(true)
.call(client)
pub fn accept(
&self,
#[builder(start_fn)] member: MemberId,
#[builder(finish_fn)] client: &RestClient,
) -> impl Future<Output = Result<Responses, Error>> {
self.response(member).accepted(true).call(client)
}
#[builder]
pub fn decline(&self,
#[builder(start_fn)]
member: MemberId,
#[builder(finish_fn)]
client: &RestClient,
) -> impl Future<Output=Result<Responses, Error>> {
self.response(member)
.accepted(false)
.call(client)
pub fn decline(
&self,
#[builder(start_fn)] member: MemberId,
#[builder(finish_fn)] client: &RestClient,
) -> impl Future<Output = Result<Responses, Error>> {
self.response(member).accepted(false).call(client)
}
}
@ -198,11 +189,8 @@ pub fn decline(
#[builder(start_fn)] spond: Id,
#[builder(finish_fn)] client: &RestClient,
member: MemberId,
) -> impl std::future::Future<Output=Result<Responses, Error>> {
response(spond)
.member(member)
.accepted(false)
.call(client)
) -> impl std::future::Future<Output = Result<Responses, Error>> {
response(spond).member(member).accepted(false).call(client)
}
#[bon::builder]
@ -210,11 +198,8 @@ pub fn accept(
#[builder(start_fn)] spond: Id,
#[builder(finish_fn)] client: &RestClient,
member: MemberId,
) -> impl std::future::Future<Output=Result<Responses, Error>> {
response(spond)
.member(member)
.accepted(true)
.call(client)
) -> impl std::future::Future<Output = Result<Responses, Error>> {
response(spond).member(member).accepted(true).call(client)
}
#[bon::builder]
@ -236,7 +221,8 @@ pub async fn response(
}
}
let request = Request{accepted};
let response: restson::Response<Responses> = client.put_capture((spond, member), &request).await?;
let request = Request { accepted };
let response: restson::Response<Responses> =
client.put_capture((spond, member), &request).await?;
Ok(response.into_inner())
}

View file

@ -0,0 +1,33 @@
use serde::{Deserialize, Serialize, Serializer};
#[derive(Debug, Copy, Clone, Deserialize)]
#[serde(transparent)]
pub struct DateTime(chrono::DateTime<chrono::Utc>);
impl Serialize for DateTime {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}
}
impl Default for DateTime {
fn default() -> Self {
chrono::Utc::now().into()
}
}
impl std::fmt::Display for DateTime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.0.to_rfc3339_opts(chrono::SecondsFormat::Secs, true,)
)
}
}
impl From<chrono::DateTime<chrono::Utc>> for DateTime {
fn from(dt: chrono::DateTime<chrono::Utc>) -> Self {
Self(dt)
}
}

View file

@ -0,0 +1,5 @@
mod datetime;
pub use datetime::DateTime;
mod timestamp;
pub use timestamp::Timestamp;

View file

@ -0,0 +1,51 @@
use chrono::TimeZone;
use serde::{Deserialize, Deserializer, Serialize, Serializer}; // for timestamp_millis_opt
#[derive(Debug, Clone, Copy)]
pub struct Timestamp(chrono::DateTime<chrono::Utc>);
impl Serialize for Timestamp {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_i64(self.0.timestamp_millis())
}
}
impl<'de> Deserialize<'de> for Timestamp {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let millis = i64::deserialize(deserializer)?;
let dt = chrono::Utc
.timestamp_millis_opt(millis)
.single()
.ok_or_else(|| serde::de::Error::custom("invalid timestamp"))?;
Ok(Timestamp(dt))
}
}
impl Default for Timestamp {
fn default() -> Self {
chrono::Utc::now().into()
}
}
impl std::fmt::Display for Timestamp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.0.to_rfc3339_opts(chrono::SecondsFormat::Secs, true,)
)
}
}
impl<I: Into<chrono::DateTime<chrono::Utc>>> From<I> for Timestamp {
fn from(dt: I) -> Self {
Self(dt.into())
}
}

114
api/src/util/id.rs Normal file
View file

@ -0,0 +1,114 @@
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::hash::{Hash, Hasher};
fn short_type_name(full: &str) -> &str {
let generic = full.find('<').unwrap_or(full.len());
let last_colon = full[..generic].rfind("::").map_or(0, |idx| idx + 2);
&full[last_colon..]
}
pub trait Type: Sized {
type Type: fmt::Display + Copy;
fn name() -> &'static str {
short_type_name(std::any::type_name::<Self>())
}
}
pub struct Id<T: Type>(T::Type);
impl<T: Type> AsRef<T::Type> for Id<T> {
fn as_ref(&self) -> &T::Type {
&self.0
}
}
impl<T: Type> Copy for Id<T> {}
impl<T: Type> Clone for Id<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Type> Id<T> {
pub fn new(src: T::Type) -> Self {
Self(src)
}
pub async fn load(
&self,
client: &restson::RestClient,
) -> Result<T, restson::Error>
where
T: restson::RestPath<Id<T>>,
T: serde::de::DeserializeOwned,
{ Ok(client.get_with::<_, T>(*self, &[]).await?.into_inner()) }
}
impl<T: Type> fmt::Debug for Id<T>
where
T::Type: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ID<{}:{}>", T::name(), self.0)
}
}
impl<T: Type> fmt::Display for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<T> Serialize for Id<T>
where
T: Type,
T::Type: Serialize,
{
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<'de, T> Deserialize<'de> for Id<T>
where
T: Type,
T::Type: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(Self(T::Type::deserialize(deserializer)?))
}
}
impl<T> PartialEq for Id<T>
where
T: Type,
T::Type: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T> Eq for Id<T>
where
T: Type,
T::Type: Eq,
{
}
impl<T> Hash for Id<T>
where
T: Type,
T::Type: Hash,
{
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}

11
api/src/util/mod.rs Normal file
View file

@ -0,0 +1,11 @@
pub mod chrono;
pub use chrono::{DateTime, Timestamp};
pub mod x128;
pub use x128::X128;
pub mod id;
pub use id::Id;
pub mod visibility;
pub use visibility::Visibility;

View file

@ -0,0 +1,36 @@
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Visibility {
Invitees,
Unknown(String),
}
impl Serialize for Visibility {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = match self {
Visibility::Invitees => "INVITEES",
Visibility::Unknown(other) => other,
};
serializer.serialize_str(s)
}
}
impl<'de> Deserialize<'de> for Visibility {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// Borrow when possible
let s = <&str>::deserialize(deserializer)?;
Ok(match s {
"INVITEES" => Visibility::Invitees,
other => Visibility::Unknown(other.to_owned()),
})
}
}

84
api/src/util/x128.rs Normal file
View file

@ -0,0 +1,84 @@
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
/// Wrapper for u128 that serializes/deserializes as 32-charachter hex
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct X128(u128);
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("invalid length: {0}")]
Length(usize),
#[error("parser error: {0}")]
Parser(#[from] std::num::ParseIntError),
}
impl X128 {
/// construct a new value
pub fn new(value: u128) -> Self {
Self(value)
}
/// access the inner value explicitely
pub fn value(&self) -> u128 {
self.0
}
}
impl<T: Into<u128>> From<T> for X128 {
fn from(src: T) -> Self {
Self::new(src.into())
}
}
impl FromStr for X128 {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s.len() {
32 => u128::from_str_radix(s, 16)
.map(Self::new)
.map_err(Error::Parser),
len => Err(Error::Length(len)),
}
}
}
//impl TryFrom<&str> for X128 {
// type Error = Error;
//
// fn try_from(s: &str) -> Result<Self, Error> {
// Self::from_str(s)
// }
//}
impl Deref for X128 {
type Target = u128;
fn deref(&self) -> &u128 {
&self.0
}
}
impl fmt::Display for X128 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:032X}", self.0)
}
}
impl Serialize for X128 {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for X128 {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Self::from_str(&s).map_err(serde::de::Error::custom)
}
}