xapi_rs/
lib.rs

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