xapi_rs/
lib.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3#![warn(rust_2024_compatibility)]
4#![warn(missing_docs)]
5
6//!
7//! This project is an attempt at implementing a conformant xAPI 2.0.0 LRS.
8//!
9//! It consists of three main modules that roughly map to (a) a data layer that
10//! defines the Rust bindings for the xAPI types, (b) a storage layer that
11//! takes care of persisting and fetching Data Access Objects representing the
12//! structures defined in the data layer, and finally (c) a Web server to handle
13//! the LRS calls proper.
14//!
15//! # Third-party crates
16//!
17//! This project depends on few best-of-breed crates to achieve correct
18//! compliance w/ other [IETF][1] and [ISO][2] standards referenced in xAPI.
19//!
20//! Here's a list of the most important ones:
21//!
22//! 1. Deserialization and Serialization:
23//!     * [serde][3]: for the basic serialization + deserialization capabilities.
24//!     * [serde_json][4]: for the JSON format bindings.
25//!     * [serde_with][5]: for custom helpers.
26//!
27//! 2. IRL[^1], IRI[^2], URI[^3] and URL[^4]:
28//!     * [iri-string][6]: for IRIs and URIs incl. support for [serde]
29//!     * [url][7]: for Uniform Resource Locators.
30//!
31//! 3. UUID[^5]:
32//!     * [uuid][9]: for handling generating, parsing and formatting UUIDs.
33//!
34//! 4. Date, Time and Durations:
35//!     * [chrono][10]: for timezone-aware date and time handling.
36//!     * [speedate][11]: for fast and simple duration[^6] parsing.
37//!
38//! 5. Language Tags and MIME types:
39//!     * [language-tags][12]: for parsing , formatting and comparing language
40//!       tags as specified in [BCP 47][13].
41//!     * [mime][14]: for support of MIME types (a.k.a. Media Types) when
42//!       dealing w/ [Attachment]s.
43//!
44//! 6. Email Address:
45//!     * [email_address][15]: for parsing and validating email addresses.
46//!
47//! 7. Semantic Version:
48//!     * [semver][16]: for semantic version parsing and generation as per
49//!       [Semantic Versioning 2.0.0][17].
50//!
51//! 8. Case Insensitive Strings:
52//!     * [unicase][18]: for comparing strings when case is not important
53//!       (using Unicode Case-folding).
54//!
55//! 9. JWS signatures:
56//!     * [josekit][19]: for creating + validating JWS signed Statements.
57//!     * [openssl][21]: for handling X.509 certificates when included in
58//!       JWS Headers.
59//!
60//! [1]: https://www.ietf.org/
61//! [2]: https://www.iso.org/
62//! [3]: https://crates.io/crates/serde
63//! [4]: https://crates.io/crates/serde_json
64//! [5]: https://crates.io/crates/serde_with
65//! [6]: https://crates.io/crates/iri-string
66//! [7]: https://crates.io/crates/url
67//! [8]: https://url.spec.whatwg.org/
68//! [9]: https://crates.io/crates/uuid
69//! [10]: https://crates.io/crates/chrono
70//! [11]: https://crates.io/crates/speedate
71//! [12]: https://crates.io/crates/language-tags
72//! [13]: https://datatracker.ietf.org/doc/bcp47/
73//! [14]: https://crates.io/crates/mime
74//! [15]: https://crates.io/crates/email_address
75//! [16]: https://crates.io/crates/semver
76//! [17]: https://semver.org/
77//! [18]: https://crates.io/crates/unicase
78//! [19]: https://crates.io/crates/josekit
79//! [20]: https://dotat.at/tmp/ISO_8601-2004_E.pdf
80//! [21]: https://crates.io/crates/openssl
81//!
82//! [^1]: IRL: Internationalized Resource Locator.
83//! [^2]: IRI: Internationalized Resource Identifier.
84//! [^3]: URI: Uniform Resource Identifier.
85//! [^4]: URL: Uniform Resource Locator.
86//! [^5]: UUID: Universally Unique Identifier --see
87//! <https://en.wikipedia.org/wiki/Universally_unique_identifier>.
88//! [^6]: Durations in [ISO 8601:2004(E)][20] sections 4.4.3.2 and 4.4.3.3.
89//!
90
91#![doc = include_str!("../doc/DATA_README.md")]
92#![doc = include_str!("../doc/DB_README.md")]
93#![doc = include_str!("../doc/LRS_README.md")]
94
95mod config;
96mod data;
97mod db;
98mod error;
99mod lrs;
100
101pub use config::*;
102pub use data::*;
103pub use db::Aggregates;
104pub use error::MyError;
105pub use lrs::{
106    build, resources, verbs::VerbUI, CONSISTENT_THRU_HDR, CONTENT_TRANSFER_ENCODING_HDR, HASH_HDR,
107    TEST_USER_PLAIN_TOKEN, VERSION_HDR,
108};
109
110use tracing::error;
111
112/// Modes of operations of this LRS.
113#[derive(Debug)]
114pub enum Mode {
115    /// In this mode, access is unfettered and a hard-wired Authority is used
116    /// for vouching for the veracity of Statements.
117    Legacy,
118    /// In this mode, access is enforced through HTTP Basic Authentication (BA)
119    /// scheme but like w/ `Legacy`, a hard-wired Authority is used for vouching
120    /// for the veracity of Statements.
121    Auth,
122    /// In this mode, access is enfoced through BA and the same authenticated
123    /// user is used as the Authority for submitted Statements if they do not
124    /// contain a valid `authority` property.
125    User,
126}
127
128impl TryFrom<&str> for Mode {
129    type Error = MyError;
130
131    fn try_from(value: &str) -> Result<Self, Self::Error> {
132        match value.trim().to_lowercase().as_str() {
133            "legacy" => Ok(Mode::Legacy),
134            "auth" => Ok(Mode::Auth),
135            "user" => Ok(Mode::User),
136            x => {
137                let msg = format!("Invalid/unknown Mode: '{}'", x);
138                error!("Failed: {}", msg);
139                Err(MyError::Runtime(msg.into()))
140            }
141        }
142    }
143}
144
145/// The xAPI version this project supports by default.
146pub const V200: &str = "2.0.0";
147/// Verbs Extension IRI
148pub const EXT_VERBS: &str = "http://crates.io/xapi-rs/ext/verbs";
149/// Statistics/Metrics Extension IRI
150pub const EXT_STATS: &str = "http://crates.io/xapi-rs/ext/stats";
151/// User Management Extension IRI
152pub const EXT_USERS: &str = "http://crates.io/xapi-rs/ext/users";
153
154/// Generate a message (in the style of `format!` macro), log it at level
155/// _error_ and raise a [runtime error][crate::MyError#variant.Runtime].
156#[macro_export]
157macro_rules! runtime_error {
158    ( $( $arg: tt )* ) => {
159        {
160            let msg = std::fmt::format(core::format_args!($($arg)*));
161            tracing::error!("{}", msg);
162            return Err($crate::MyError::Runtime(msg.into()));
163        }
164    }
165}
166
167/// Log `$err` at level _error_ before returning it.
168#[macro_export]
169macro_rules! emit_error {
170    ( $err: expr_2021 ) => {{
171        tracing::error!("{}", $err);
172        return Err($err);
173    }};
174}
175
176/// Generate a message (in the style of `format!` macro), log it at level
177/// _error_ and raise a [data constraint violation error][crate::MyError#variant.Data].
178#[macro_export]
179macro_rules! constraint_violation_error {
180    ( $( $arg: tt )* ) => {
181        {
182            let msg = std::fmt::format(core::format_args!($($arg)*));
183            tracing::error!("{}", msg);
184            return Err($crate::MyError::Data(DataError::Validation(
185                ValidationError::ConstraintViolation(msg.into()),
186            )));
187        }
188    }
189}