Skip to main content

Crate xapi_data

Crate xapi_data 

Source
Expand description

This LIBRARY consists of Rust bindings for the IEEE Std 9274.1.1, IEEE Standard for Learning Technology— JavaScript Object Notation (JSON) Data Model Format and Representational State Transfer (RESTful) Web Service for Learner Experience Data Tracking and Access.

The standard describes a JSON1 data model format and a RESTful2 Web Service API3 for communication between Activities experienced by an individual, group, or other entity and an LRS4. The LRS is a system that exposes the RESTful Web Service API for the purpose of tracking and accessing experiential data, especially in learning and human performance.

In this document, “xAPI” means the collection of published documents found here.

§The Validate trait

Types defined in this library rely on serde and a couple of other libraries to deserialize xAPI data. Unit and Integration Tests for these types use the published examples to partly ensure their correctness in at least they will consume the input stream and will produce instances of those types that can be later manipulated.

I said partly b/c not all the rules specified by the specifications can or are encoded in the techniques used for unmarshelling the input data stream.

For example when xAPI specifies that a property must be an IRL the corresponding field is defined as an IriString. But an IRL is not just an IRI! As xAPI (3. Definitions, acronyms, and abbreviations) states…

…an IRL is an IRI that when translated into a URI (per the IRI to URI rules), is a URL.

Unfortunately the iri-string library does not offer out-of-the-box support for IRLs.

Another example of the limitations of solely relying on serde for instantiating correct types is the email address (mbox) property of Agents and Groups. xAPI (4.2.2.1 Actor) states that an mbox is a…

mailto IRI. The required format is “mailto:email address”.

For those reasons a Validate trait is defined and implemented to ensure that an instance of a type that implements this trait is valid, in that it satisfies the xAPI constraints, if + when it passes the validate() call.

If a validate() call returns None when it shouldn’t it’s a bug.

§Equality and Equivalence - The Fingerprint trait

There’s the classical Equality concept ubiquitous in software that deals w/ object Equality. That concept affects (in Rust) the Hash, PartialEq and Eq Traits. Ensuring that our xAPI Data Types implement those Traits mean they can be used as Keys in HashMaps and distinct elements in HashSets.

The xAPI describes (and relies) on a concept of Equivalence that determines if two instances of the same Data Type (say Statements) are equal. That Equivalence is different from Equality introduced earlier. So it’s possible to have two Statements that have different hash5 values yet are equivalent. Note though that if two Statements (and more generally two instances of the same Data Type) are equal then they’re also equivalent. In other words, Equality implies Equivalence but not the other way around.

To satisfy Equivalence between instances of a Data Type we introduce a Fingerprint Trait. The required function of this Trait; i.e. fingerprint(), is used to test for Equivalence between two instances of the same Data Type.

For most xAPI Data Types, both the hash and fingerprint functions yield the same result. When they differ the Equivalence only considers properties the xAPI standard qualifies as preserving immutability requirements, for example for Statements those are…

  • Actor, except the ordering of Group members,
  • Verb, except for display property,
  • Object,
  • Duration, excluding precision beyond 0.01 second.

Note though that even when they yield the same result, the implementations take into consideration the following constraints –and hence apply the required conversions before the test for equality…

  • Case-insensitive string comparisons when the property can be safely compared this way such as the mbox (email address) of an Actor but not the name of an Account.
  • IRI Normalization by first splitting it into Absolute and Fragment parts then normalizing the Absolute part before hashing the two in sequence.

§The Canonical trait

xAPI requires LRS implementations to sometimes produce results in canonical form –See Language Filtering Requirements for Canonical Format Statements for example.

This trait defines a method implemented by types required to produce such format.

§Getters, Builders and Setters

Once a type is instantiated, access to any of its fields –sometimes referred to in the documentation as properties using the camel case form mostly used in xAPI– is done through methods that mirror the Rust field names of the structures representing said types.

For example the homePage property of an Account is obtained by calling the method home_page() of an Account instance which returns a reference to the IRI string as &IriStr.

Sometimes however it is convenient to access the field as another type. Using the same example as above, the Account implementation offers a home_page_as_str() which returns a reference to the same field as &str.

This pattern is generalized thoughtout this library.

The library so far, except for rare use-cases, does NOT offer setters for any type field. Creating new instances of types by hand –as opposed to deserializing (from the wire)– is done by (a) instantiating a Builder for a type, (b) calling the Builder setters (using the same field names as those of the to-be built type) to set the desired values, and when ready, (c) calling the build() method.

Builders signal the occurrence of errors by returning a Result w/ the error part being a DataError instance. Here’s an example…

    let act = Account::builder()
        .home_page("https://inter.net/login")?
        .name("example")?
        .build()?;
    // ...
    assert_eq!(act.home_page_as_str(), "https://inter.net/login");
    assert_eq!(act.name(), "example");

§Naming

Naming properties in xAPI Objects is inconsistent. Sometimes the singular form is used to refer to a collection of items; e.g. member instead of members when referring to a Group’s list of Agents. In other places the plural form is correctly used; e.g. attachments in a SubStatement, or extensions everywhere it’s referenced.

We tried to be consistent in naming the fields of the corresponding types while ensuring that their serialization to, and deserialization from, streams respect the label assigned to them in xAPI and backed by the accompanying examples. So to access a Group’s Agents one would call members(). To add an Agent to a Group one would call member() on a GroupBuilder.


  1. JSON: JavaScript Object Notation. 

  2. REST: Representational State Transfer. 

  3. API: Application Programming Interface. 

  4. LRS: Learning Record Store. 

  5. Just to be clear, hash here means the result of computing a message digest over the non-null values of an object’s field(s). 

  6. Durations in ISO 8601:2004(E) sections 4.4.3.2 and 4.4.3.3. 

Re-exports§

pub use prelude::*;

Modules§

prelude
Group imports of many common traits and types by adding a glob import for use by clients of this library.

Macros§

add_language
Given $map (a LanguageMap dictionary) insert $label keyed by $tag creating the collection in the process if it was None.
emit_error
Log $err at level error before returning it.
merge_maps
Given dst and src as two BTreeMaps wrapped in Option, replace or augment dst’ entries w/ src’s.
runtime_error
Generate a message (in the style of format! macro), log it at level error and raise a runtime error.
set_email
Both Agent and Group have an mbox property which captures an email address. This macro eliminates duplication of the logic involved in (a) parsing an argument $val into a valid EmailAddress, (b) raising a DataError if an error occurs, (b) assigning the result when successful to the appropriate field of the given $builder instance, and (c) resetting the other three IFI (Inverse Functional Identifier) fields to None.