summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/sellershut-core/Cargo.toml36
-rw-r--r--lib/sellershut-core/build.rs81
-rw-r--r--lib/sellershut-core/proto/auth/auth.proto35
-rw-r--r--lib/sellershut-core/src/auth.rs3
-rw-r--r--lib/sellershut-core/src/google.rs7
-rw-r--r--lib/sellershut-core/src/google/helpers.rs125
-rw-r--r--lib/sellershut-core/src/lib.rs13
7 files changed, 300 insertions, 0 deletions
diff --git a/lib/sellershut-core/Cargo.toml b/lib/sellershut-core/Cargo.toml
new file mode 100644
index 0000000..b5e1bb9
--- /dev/null
+++ b/lib/sellershut-core/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "sellershut-core"
+version = "0.1.0"
+edition = "2024"
+license.workspace = true
+homepage.workspace = true
+documentation.workspace = true
+description.workspace = true
+
+[dependencies]
+prost = "0.13.5"
+serde = { workspace = true, optional = true }
+serde_json = { workspace = true, optional = true }
+time = { workspace = true, optional = true }
+tonic.workspace = true
+tonic-types = "0.13.0"
+
+[features]
+default = []
+auth = []
+serde = ["dep:serde", "serde/derive", "serde_json"]
+time = [
+ "dep:time",
+ "serde",
+ "time/serde",
+ "time/parsing",
+ "time/formatting",
+ "time/macros",
+]
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
+
+[build-dependencies]
+tonic-build = { version = "0.13.0", features = ["cleanup-markdown"] }
diff --git a/lib/sellershut-core/build.rs b/lib/sellershut-core/build.rs
new file mode 100644
index 0000000..8ff48f1
--- /dev/null
+++ b/lib/sellershut-core/build.rs
@@ -0,0 +1,81 @@
+#[cfg(feature = "auth")]
+enum Entity {
+ #[cfg(feature = "auth")]
+ Auth,
+}
+
+#[cfg(feature = "auth")]
+impl Entity {
+ fn protos(&self) -> Vec<&'static str> {
+ let mut res: Vec<&'static str> = vec![];
+
+ match self {
+ #[cfg(feature = "auth")]
+ Entity::Auth => {
+ res.extend(vec!["proto/auth/auth.proto"]);
+ }
+ }
+ res
+ }
+}
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ println!("cargo:rerun-if-changed=proto");
+
+ #[cfg(feature = "auth")]
+ build_proto("auth", Entity::Auth);
+
+ Ok(())
+}
+
+#[cfg(feature = "auth")]
+fn build_proto(package: &str, entity: Entity) {
+ let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
+
+ let config = tonic_build::configure().compile_well_known_types(true)
+ .server_mod_attribute(
+ package,
+ format!("#[cfg(feature = \"{package}\")] #[cfg_attr(docsrs, doc(cfg(feature = \"{package}\")))]"),
+ )
+ .client_mod_attribute(
+ package,
+ format!("#[cfg(feature = \"{package}\")] #[cfg_attr(docsrs, doc(cfg(feature = \"{package}\")))]"),
+ );
+
+ #[cfg(feature = "serde")]
+ let config = add_serde(config);
+
+ #[cfg(feature = "time")]
+ let config = config
+ .type_attribute(
+ ".google.protobuf.Timestamp",
+ "#[serde(try_from = \"crate::google::helpers::DateItem\")] #[serde(into = \"String\")]",
+ )
+ .type_attribute(
+ ".google.type.Date",
+ "#[serde(try_from = \"crate::google::helpers::DateItem\")] #[serde(into = \"String\")]",
+ );
+
+ let include_paths = &["proto"];
+
+ config
+ .file_descriptor_set_path(out_dir.join(format!("{package}_descriptor.bin")))
+ .server_mod_attribute(
+ package,
+ format!("#[cfg(feature = \"{package}\")] #[cfg_attr(docsrs, doc(cfg(feature = \"{package}\")))]"),
+ )
+ .client_mod_attribute(
+ package,
+ format!("#[cfg(feature = \"{package}\")] #[cfg_attr(docsrs, doc(cfg(feature = \"{package}\")))]"),
+ )
+ .compile_well_known_types(true)
+ .compile_protos(&entity.protos(), include_paths).unwrap();
+}
+
+#[cfg(all(feature = "serde", feature = "auth",))]
+fn add_serde(config: tonic_build::Builder) -> tonic_build::Builder {
+ config.type_attribute(
+ ".",
+ "#[derive(serde::Serialize, serde::Deserialize)] #[serde(rename_all = \"snake_case\")]",
+ )
+}
diff --git a/lib/sellershut-core/proto/auth/auth.proto b/lib/sellershut-core/proto/auth/auth.proto
new file mode 100644
index 0000000..5e340b3
--- /dev/null
+++ b/lib/sellershut-core/proto/auth/auth.proto
@@ -0,0 +1,35 @@
+syntax = "proto3";
+
+package auth;
+
+import "google/protobuf/timestamp.proto";
+
+// A message representing a user
+message User {
+ // Unique identifier for the user
+ string id = 1;
+ // Email address of the user
+ string email = 2;
+ // Timestamp for when the user was created
+ google.protobuf.Timestamp created_at = 3;
+ // Timestamp for when the user was last updated
+ google.protobuf.Timestamp updated_at = 4;
+}
+
+// Define a message for sending a token to be validated
+message ValidationRequest {
+ // The token to validate
+ string token = 1;
+}
+
+// Define a message for the result of a token validation
+message ValidationResponse {
+ // Indicates whether the token is valid
+ bool valid = 1;
+}
+
+// Define the AuthServer gRPC service
+service Auth {
+ // Validate a token
+ rpc ValidateAuthToken (ValidationRequest) returns (ValidationResponse);
+}
diff --git a/lib/sellershut-core/src/auth.rs b/lib/sellershut-core/src/auth.rs
new file mode 100644
index 0000000..d555aab
--- /dev/null
+++ b/lib/sellershut-core/src/auth.rs
@@ -0,0 +1,3 @@
+tonic::include_proto!("auth");
+/// Auth file descriptor
+pub const AUTH_FILE_DESCRIPTOR_SET: &[u8] = tonic::include_file_descriptor_set!("auth_descriptor");
diff --git a/lib/sellershut-core/src/google.rs b/lib/sellershut-core/src/google.rs
new file mode 100644
index 0000000..b28aee0
--- /dev/null
+++ b/lib/sellershut-core/src/google.rs
@@ -0,0 +1,7 @@
+/// Well known types
+pub mod protobuf {
+ include!(concat!(env!("OUT_DIR"), "/google.protobuf.rs"));
+}
+
+#[cfg(feature = "time")]
+mod helpers;
diff --git a/lib/sellershut-core/src/google/helpers.rs b/lib/sellershut-core/src/google/helpers.rs
new file mode 100644
index 0000000..2d75655
--- /dev/null
+++ b/lib/sellershut-core/src/google/helpers.rs
@@ -0,0 +1,125 @@
+use crate::google::protobuf::Timestamp;
+
+#[derive(serde::Serialize, serde::Deserialize)]
+#[serde(untagged)]
+/// Date utility
+#[derive(Clone, Debug)]
+pub enum DateItem {
+ /// string
+ String(String),
+ /// ts
+ Timestamp { seconds: i64, nanos: i32 },
+}
+
+impl TryFrom<DateItem> for Timestamp {
+ type Error = time::Error;
+
+ fn try_from(value: DateItem) -> Result<Self, Self::Error> {
+ match value {
+ DateItem::String(ref string) => <Timestamp as std::str::FromStr>::from_str(string),
+ DateItem::Timestamp { seconds, nanos } => Ok(Self { seconds, nanos }),
+ }
+ }
+}
+
+impl From<time::OffsetDateTime> for Timestamp {
+ fn from(dt: time::OffsetDateTime) -> Self {
+ Timestamp {
+ seconds: dt.unix_timestamp(),
+ nanos: dt.nanosecond() as i32,
+ }
+ }
+}
+
+impl From<Timestamp> for String {
+ fn from(value: Timestamp) -> Self {
+ let odt = time::OffsetDateTime::try_from(value).expect("invalid date");
+ odt.format(&time::format_description::well_known::Rfc3339)
+ .expect("format is not rfc3339")
+ }
+}
+
+impl std::str::FromStr for Timestamp {
+ type Err = time::Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let timestamp =
+ time::OffsetDateTime::parse(s, &time::format_description::well_known::Rfc3339)?;
+
+ Ok(Timestamp::from(timestamp))
+ }
+}
+
+impl TryFrom<String> for Timestamp {
+ type Error = time::Error;
+
+ fn try_from(value: String) -> Result<Self, Self::Error> {
+ <Timestamp as std::str::FromStr>::from_str(&value)
+ }
+}
+
+impl TryFrom<Timestamp> for time::OffsetDateTime {
+ type Error = time::Error;
+
+ fn try_from(value: Timestamp) -> Result<Self, Self::Error> {
+ let dt = time::OffsetDateTime::from_unix_timestamp(value.seconds)?;
+
+ Ok(dt.replace_nanosecond(value.nanos as u32)?)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use time::{Duration, OffsetDateTime};
+
+ #[test]
+ fn test_offsetdatetime_to_timestamp() {
+ let now = OffsetDateTime::now_utc();
+ let timestamp: Timestamp = now.into();
+
+ assert_eq!(timestamp.seconds, now.unix_timestamp());
+ assert_eq!(timestamp.nanos, now.nanosecond() as i32);
+ }
+
+ #[test]
+ fn test_timestamp_to_offsetdatetime() {
+ let now = OffsetDateTime::now_utc();
+ let timestamp: Timestamp = now.into();
+ let dt: OffsetDateTime = timestamp.try_into().unwrap();
+
+ assert_eq!(dt, now);
+ }
+
+ #[test]
+ fn test_timestamp_to_offsetdatetime_with_nanos() {
+ let now = OffsetDateTime::now_utc();
+ let nanos = 123456789;
+ let dt = now + Duration::nanoseconds(nanos);
+ let timestamp: Timestamp = dt.into();
+ let dt_from_timestamp: OffsetDateTime = timestamp.try_into().unwrap();
+
+ assert_eq!(dt_from_timestamp, dt);
+ }
+
+ #[test]
+ fn test_timestamp_to_offsetdatetime_with_negative_nanos() {
+ let now = OffsetDateTime::now_utc();
+ let nanos = -123456789;
+ let dt = now + Duration::nanoseconds(nanos);
+ let timestamp: Timestamp = dt.into();
+ let dt_from_timestamp: OffsetDateTime = timestamp.try_into().unwrap();
+
+ assert_eq!(dt_from_timestamp, dt);
+ }
+
+ #[test]
+ fn test_timestamp_to_offsetdatetime_invalid_seconds() {
+ let timestamp = Timestamp {
+ seconds: i64::MIN,
+ nanos: 0,
+ };
+ let result: Result<OffsetDateTime, time::Error> = timestamp.try_into();
+ assert!(result.is_err());
+ }
+}
diff --git a/lib/sellershut-core/src/lib.rs b/lib/sellershut-core/src/lib.rs
new file mode 100644
index 0000000..ee2ed9b
--- /dev/null
+++ b/lib/sellershut-core/src/lib.rs
@@ -0,0 +1,13 @@
+//! Core
+#![cfg_attr(docsrs, feature(doc_cfg))]
+#![warn(
+ missing_docs,
+ rustdoc::broken_intra_doc_links,
+ missing_debug_implementations
+)]
+
+/// Protobuf types
+#[cfg(feature = "auth")]
+pub mod google;
+
+pub mod auth;