Skip to content

Commit

Permalink
Separate lsps0 schema's
Browse files Browse the repository at this point in the history
The `crate::lsps::message`-module was becoming too big.
I've decided to separate all files related to LSPS0.

The code should better reflect how the LSP-specification is structured.

Follow-up commits for LSPS1 and LSPS2 will follow
  • Loading branch information
ErikDeSmedt committed Sep 20, 2023
1 parent eaa1737 commit 62f0aa6
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 143 deletions.
147 changes: 147 additions & 0 deletions libs/gl-client/src/lsps/lsps0/common_schemas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use serde::{Serialize, Deserialize};
use serde::de::Error as SeError;
use serde::ser::Error as DeError;

use time::format_description::FormatItem;
use time::macros::format_description;
use time::{OffsetDateTime, PrimitiveDateTime};


// Implements all the common schema's defined in LSPS0 common schema's

// Initially I used serde_as for the parsing and serialization of this type.
// However, the spec is more strict.
// It requires a yyyy-mm-ddThh:mm:ss.uuuZ format
//
// The serde_as provides us options such as rfc_3339.
// Note, that this also allows formats that are not compliant to the LSP-spec such as dropping
// the fractional seconds or use non UTC timezones.
//
// For LSPS2 the `valid_until`-field must be copied verbatim. As a client this can only be
// achieved if the LSPS2 sends a fully compliant timestamp.
//
// I have decided to fail early if another timestamp is received
#[derive(Debug)]
pub struct IsoDatetime {
pub datetime: PrimitiveDateTime,
}

const DATETIME_FORMAT: &[FormatItem] =
format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z");

impl IsoDatetime {
pub fn from_offset_date_time(datetime: OffsetDateTime) -> Self {
let offset = time::UtcOffset::from_whole_seconds(0).unwrap();
let datetime_utc = datetime.to_offset(offset);
let primitive = PrimitiveDateTime::new(datetime_utc.date(), datetime.time());
return Self {
datetime: primitive,
};
}

pub fn from_primitive_date_time(datetime: PrimitiveDateTime) -> Self {
return Self { datetime };
}

pub fn datetime(&self) -> OffsetDateTime {
return self.datetime.assume_utc();
}
}

impl Serialize for IsoDatetime {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let datetime_str = self
.datetime
.format(&DATETIME_FORMAT)
.map_err(|err| S::Error::custom(format!("Failed to format datetime {:?}", err)))?;

serializer.serialize_str(&datetime_str)
}
}

impl<'de> Deserialize<'de> for IsoDatetime {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let str_repr = <String as serde::de::Deserialize>::deserialize(deserializer)?;
time::PrimitiveDateTime::parse(&str_repr, DATETIME_FORMAT)
.map_err(|err| D::Error::custom(format!("Failed to parse Datetime. {:?}", err)))
.map(|dt| Self::from_primitive_date_time(dt))
}
}

#[derive(Debug)]
pub struct SatAmount(u64);
#[derive(Debug)]
pub struct MsatAmount(u64);

impl SatAmount {
pub fn sat_value(&self) -> u64 {
return self.0;
}

pub fn new(value : u64) -> Self {
return SatAmount(value)
}
}

impl MsatAmount {
pub fn msat_value(&self) -> u64 {
return self.0;
}


pub fn new(value : u64) -> Self {
return MsatAmount(value)
}
}

impl Serialize for SatAmount {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let amount_str = self.0.to_string();
serializer.serialize_str(&amount_str)
}
}

impl Serialize for MsatAmount {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let amount_str = self.0.to_string();
serializer.serialize_str(&amount_str)
}
}

impl<'de> Deserialize<'de> for SatAmount {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let str_repr = <String as serde::de::Deserialize>::deserialize(deserializer)?;
let u64_repr: Result<u64, _> = str_repr
.parse()
.map_err(|_| D::Error::custom(String::from("Failed to parse sat_amount")));
return Ok(Self(u64_repr.unwrap()));
}
}

impl<'de> Deserialize<'de> for MsatAmount {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let str_repr = <String as serde::de::Deserialize>::deserialize(deserializer)?;
let u64_repr: Result<u64, _> = str_repr
.parse()
.map_err(|_| D::Error::custom(String::from("Failed to parse sat_amount")));
return Ok(Self(u64_repr.unwrap()));
}
}
2 changes: 2 additions & 0 deletions libs/gl-client/src/lsps/lsps0/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod common_schemas;
pub mod schema;
6 changes: 6 additions & 0 deletions libs/gl-client/src/lsps/lsps0/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use serde::{Serialize, Deserialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct ProtocolList {
pub protocols: Vec<u32>,
}
153 changes: 10 additions & 143 deletions libs/gl-client/src/lsps/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
use crate::lsps::error::LspsError;
pub use crate::lsps::json_rpc::{DefaultError, JsonRpcMethod, MapErrorCode, NoParams};
use crate::lsps::json_rpc_erased::{JsonRpcMethodErased, JsonRpcMethodUnerased};

use crate::lsps::lsps0::common_schemas::{IsoDatetime, MsatAmount, SatAmount};
use crate::lsps::lsps0::schema::{ProtocolList};
use serde::de::Error as DeError;
use serde::ser::Error as SeError;
use serde::{Deserialize, Serialize};

use time::format_description::FormatItem;
use time::macros::format_description;
use time::{OffsetDateTime, PrimitiveDateTime};
use uuid::Uuid;

use super::error::map_json_rpc_error_code_to_str;
Expand Down Expand Up @@ -149,138 +148,6 @@ impl<'a> JsonRpcMethodUnerased<'a, Vec<u8>, Vec<u8>, Vec<u8>> for JsonRpcMethodE
}
}

#[derive(Debug)]
pub struct SatAmount(u64);
#[derive(Debug)]
pub struct MsatAmount(u64);

impl SatAmount {
pub fn value(&self) -> u64 {
return self.0;
}
}

impl MsatAmount {
pub fn value(&self) -> u64 {
return self.0;
}
}

impl Serialize for SatAmount {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let amount_str = self.0.to_string();
serializer.serialize_str(&amount_str)
}
}

impl Serialize for MsatAmount {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let amount_str = self.0.to_string();
serializer.serialize_str(&amount_str)
}
}

impl<'de> Deserialize<'de> for SatAmount {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let str_repr = <String as serde::de::Deserialize>::deserialize(deserializer)?;
let u64_repr: Result<u64, _> = str_repr
.parse()
.map_err(|_| D::Error::custom(String::from("Failed to parse sat_amount")));
return Ok(Self(u64_repr.unwrap()));
}
}

impl<'de> Deserialize<'de> for MsatAmount {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let str_repr = <String as serde::de::Deserialize>::deserialize(deserializer)?;
let u64_repr: Result<u64, _> = str_repr
.parse()
.map_err(|_| D::Error::custom(String::from("Failed to parse sat_amount")));
return Ok(Self(u64_repr.unwrap()));
}
}

// Initially I used serde_as for the parsing and serialization of this type.
// However, the spec is more strict.
// It requires a yyyy-mm-ddThh:mm:ss.uuuZ format
//
// The serde_as provides us options such as rfc_3339.
// Note, that this also allows formats that are not compliant to the LSP-spec such as dropping
// the fractional seconds or use non UTC timezones.
//
// For LSPS2 the `valid_until`-field must be copied verbatim. As a client this can only be
// achieved if the LSPS2 sends a fully compliant timestamp.
//
// I have decided to fail early if another timestamp is received
#[derive(Debug)]
pub struct Datetime {
datetime: PrimitiveDateTime,
}

const DATETIME_FORMAT: &[FormatItem] =
format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z");

impl Datetime {
pub fn from_offset_date_time(datetime: OffsetDateTime) -> Self {
let offset = time::UtcOffset::from_whole_seconds(0).unwrap();
let datetime_utc = datetime.to_offset(offset);
let primitive = PrimitiveDateTime::new(datetime_utc.date(), datetime.time());
return Self {
datetime: primitive,
};
}

pub fn from_primitive_date_time(datetime: PrimitiveDateTime) -> Self {
return Self { datetime: datetime };
}

pub fn datetime(&self) -> OffsetDateTime {
return self.datetime.assume_utc();
}
}

impl Serialize for Datetime {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let datetime_str = self
.datetime
.format(&DATETIME_FORMAT)
.map_err(|err| S::Error::custom(format!("Failed to format datetime {:?}", err)))?;

serializer.serialize_str(&datetime_str)
}
}

impl<'de> Deserialize<'de> for Datetime {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let str_repr = <String as serde::de::Deserialize>::deserialize(deserializer)?;
time::PrimitiveDateTime::parse(&str_repr, DATETIME_FORMAT)
.map_err(|err| D::Error::custom(format!("Failed to parse Datetime. {:?}", err)))
.map(|dt| Self::from_primitive_date_time(dt))
}
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ProtocolList {
pub protocols: Vec<u32>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Lsps1InfoResponse {
Expand Down Expand Up @@ -327,8 +194,8 @@ pub struct Lsps1GetOrderResponse {
token: String,
#[serde(rename = "announceChannel")]
announce_channel: bool,
created_at: Datetime,
expires_at: Datetime,
created_at: IsoDatetime,
expires_at: IsoDatetime,
order_state: OrderState,
payment: Payment,
}
Expand Down Expand Up @@ -412,7 +279,7 @@ impl MapErrorCode for Lsp2GetInfoError {
pub struct OpeningFeeParamsMenuItem {
min_fee_msat: MsatAmount,
proportional: u64,
_valid_until: Datetime,
_valid_until: IsoDatetime,
min_lifetime: u64,
max_client_to_self_delay: u64,
promise: Promise,
Expand Down Expand Up @@ -516,7 +383,7 @@ mod test {
let int_number: u64 = 10000000001;

let x = serde_json::from_str::<SatAmount>(json_str_number).unwrap();
assert_eq!(x.0, int_number);
assert_eq!(x.sat_value(), int_number);
}

#[test]
Expand All @@ -526,7 +393,7 @@ mod test {
let json_str_number = "\"10000000001\"";
let int_number: u64 = 10000000001;

let sat_amount = SatAmount(int_number);
let sat_amount = SatAmount::new(int_number);

let json_str = serde_json::to_string::<SatAmount>(&sat_amount).unwrap();
assert_eq!(json_str, json_str_number);
Expand All @@ -536,7 +403,7 @@ mod test {
fn parse_and_serialize_datetime() {
let datetime_str = "\"2023-01-01T23:59:59.999Z\"";

let dt = serde_json::from_str::<Datetime>(&datetime_str).unwrap();
let dt = serde_json::from_str::<IsoDatetime>(&datetime_str).unwrap();

assert_eq!(dt.datetime.year(), 2023);
assert_eq!(dt.datetime.month(), time::Month::January);
Expand All @@ -557,7 +424,7 @@ mod test {
// However, in LSPS2 the datetime_str must be repeated verbatim
let datetime_str = "\"2023-01-01T23:59:59.99Z\"";

let result = serde_json::from_str::<Datetime>(&datetime_str);
let result = serde_json::from_str::<IsoDatetime>(&datetime_str);
result.expect_err("datetime_str should not be parsed if it doesn't follow spec");
}

Expand Down
1 change: 1 addition & 0 deletions libs/gl-client/src/lsps/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ pub mod json_rpc;
pub mod json_rpc_erased;
pub mod message;
pub mod transport;
pub mod lsps0;

0 comments on commit 62f0aa6

Please sign in to comment.