Skip to main content

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