13 releases (6 breaking)
0.9.2 | Jun 18, 2024 |
---|---|
0.9.1 | Jun 18, 2024 |
0.8.0 | Jun 14, 2024 |
0.7.0 | Jun 7, 2024 |
0.3.0 | May 24, 2024 |
#212 in HTTP server
713 downloads per month
245KB
5.5K
SLoC
XAPI Oxidized
A RESTful abstraction layer library for interacting with XNAT web APIs. Allows the user to perform actions against a host programmatically via Rust.
The intent is to be able to build tools and automated tasking.
Getting started
To use this library in your project, you must add it to your dependencies. The easiest way to do this is by simply doing the following:
$ cargo add oxinat --features core
It is important to know that the core feature must be utilized as this unlocks the basic interface. This may be changed in future iterations, but for now the core stays as a separate feature.
Once installed, you should have access to structs Xnat
and
XnatBuilder
, as well as V1
and V2
-- the initial version
implemented structs granting access to building URIs mapped from
XAPI documentation.
use oxinat::Xnat;
let client = Xnat::configure("your.target.host")
.use_secure(true)
.with_username("your-username")
.with_password("your-password")
.build()
.expect_err(); // No version implementation set.
XAPI Oxidized API
Versions
A Version
is a trait defined in the core
feature, granting access
to the root component of XAPI URIs, and with URIBuilder
implemented types and traits, allows you to construct URIs in a
progressional manner guided by the oxinat
internal system.
use oxinat::{Version, ProjectUri};
fn foo<V: Version>(version: &V) -> Result<(), ()> {
version.project_data().with_id("PROJECT_ID").build()?;
Ok(())
}
At the above .build()
call, oxinat
will try to build a URI in
the form of a Result<String, ..>
. Assuming it is valid, and the
above case should be, the resulting string from unwrapping the the
Result
should produce something like:
"{data_uri}/projects/PROJECT_ID"
{data_uri}
should be already pre-formatted. When utilized through
an implementation of Version
, a call will be made to the type's
fn data_uri() -> String
method. This will also be true in the case
where fn root_uri() -> String
is required.
Custom Versions
At the surface level, the units V1
and V2
have already been
implemented to access UriBuilder
implemented types and traits. It
would be recommended to simply use these units, but it is possible
to define your own.
To do this, you will need to install the derive
feature, but then
after the full API will be unlocked and available.
[dependencies]
oxinat = { version = "0.6.1", features = ["core", "derive"]}
At the time of writing, it would be advisable to instead use the
full
feature and still make use of the entire suite.
With derive
enabled, defining a custom Version
should be
relatively easy with some requirements. You must make a call to
#[version()]
above the deriving type along with at least declaring
the root_uri
attribute. This tells the compiler how to construct
the initial Version
impl, prior to inclusion of URI building
traits. Said #[version()]
declaration comes with a few
additional attributes that are optional.
data_uri
defines the value offn data_uri() -> String
.root_uri
is used instead if omitted.legacy
tells the compiler to only implement sub-traits for legacy XNAT systems.
An example of deriving from a version would be:
use oxinat::{Version, ProjectUri, ExperimentUri, SubjectUri};
#[derive(Clone, Version, ProjectUri, ExperimentUri, SubjectUri)]
#[version(root_uri = "xapi", data_uri = "data", legacy = true)]
struct MyVersion;
MyVersion
.project_data()
.with_id("SOME_PROJECT")
.subjects()
.with_subject("SOME_SUBJECT")
.experiments()
.build()?
And then the resulting URI should look something like this:
"data/projects/SOME_PROJECT/subjects/SOME_SUBJECT/experiments"
Clients and Client Builders
Another key piece to the puzzle is going to be Xnat<V>
and
XnatBuilder<V>
. Where these types allow you to broker calls to
to/from a target XNAT host. As described earlier:
use oxinat::Xnat;
use crate::MyVersion;
let client = Xnat::configure("your.target.host")
.use_secure(true)
.with_username("your-username")
.with_password("your-password")
.with_version(MyVersion)
.build()
.expect("must build an XNAT client"); // Should produce a client.
Where now, we are setting the desired version, with the URI
builders we want, we can expect to have a proper Xnat
client.
Protocols
An effort is being made to predefine some common operations you may
wish to perform. We are defining them as protocols
where a protocol
is related to come CRUD operation against a resource XNAT makes
available to an authorized user.
For clients which implement ClientCore
and ClientREST
traits,
as we continue development, these additional traits will be available:
/// Type is able to implement CREATE requests for
/// a particular model. Upon creation, these
/// methods are expected to then return the
/// individual results, and then a
/// `Ok(Self::Model)` response if the request is
/// successful.
trait Create<M> {
/// Attempt to send a CREATE request to the
/// XNAT server for **multiple** models.
fn create_many(&self, models: M) -> Vec<PinnedFuture<'_, M>>;
/// Attempt to send a CREATE request to the
/// XNAT server for **one** model.
fn create_once(&self, model: M) -> anyhow::Result<M>;
}
/// Type is able to implement RETRIEVE requests
/// for a particular model.
trait Retrieve<M> {
/// Get all instances of a particular model
/// available to the user via the XNAT host.
async fn get_all(&self) -> anyhow::Result<Vec<M>>;
/// Get all instances of a particular model
/// using another model as the query
/// parameters for the request.
async fn get_any_from(&self, model: &M) -> anyhow::Result<Vec<M>>;
/// Get one instance of a particular model
/// using another model as the query
/// parameters for the request.
async fn get_one_from(&self, model: &M) -> anyhow::Result<M>;
}
/// Type is able to implement UPDATE or UPSERT
/// requests for a particular model.
trait Update<M> {
/// Attempt to send **multiple** UPDATE
/// requests to the XNAT host.
fn update_many(&self, models: M) -> Vec<PinnedFuture<'_, M>>;
/// Attempt to send **one** UPDATE request to
/// the XNAT host.
async fn update_once(&self, model: M) -> anyhow::Result<M>;
}
/// Type is able to implement DELETE requests for
/// a particular model.
trait Delete<M> {
/// Attempt to send **multiple** DELETE
/// requests to the XNAT host.
fn delete_many(&self, models: M) -> Vec<PinnedFuture<'_, M>>;
/// Attempt to send **one** DELETE request to
/// the XNAT host.
async fn delete_once(&self, model: M) -> anyhow::Result<M>;
}
Create
The Create
trait has been implemented on a generic Xnat client
for certain models. These models are available then to be passed to
the methods defined by this trait to create instances of the
below on, or against, your target XNAT host:
Project
Subject
Experiment
Scan
Resource
Depending on the depth at which each model requires, you will need to supply any or all identifiers required for your target XNAT host to know where/how the model should be created.
For example, in the case of a Scan
model, you will need to supply,
project
as the project ID, subject
as the subject label or ID,
experiment
as the experiment label or ID and the ID of the specified
scan as id
.
use oxinat::{ClientCore, ClientToken, Xnat};
use oxinat::models::Scan;
use oxinat::protocols::Create;
use crate::{MyVersion, client};
let mut scan = Scan::default();
scan.id = Some(14u64);
scan.experiment = Some("EXPERIMENT_LABEL".into());
scan.subject = Some("SUBJECT_LABEL".into());
scan.project = Some("PROJECT_ID".into());
scan = client.create_once(scan).await.expect("scan must be created");
Retrieve
The Retrieve
trait has already be implemented on a generic Xnat
client for certain models.
Project
Subject
Experiment
Scan
Assessor
Resource
Plugin
The Retrieve
trait itself is meant to allow you to GET resources
from your target XNAT instance.
use oxinat::{ClientCore, ClientToken, Xnat}
use oxinat::models::Project;
use oxinat::protocols::Retrieve;
use crate::{MyVersion, client};
// Should retrieve all project records from your
// XNAT instance.
let found: Vec<Project> = client.get_all().await.unwrap();
// Should retrieve one project which matches the
// project ID.
let mut project = Project::default();
project.id.clone_from(&Some("SOME_PROJECT_ID".into()));
let found = client.get_one_from(&project).await.unwrap();
The predefined getters, when performing a query, tries to construct the request path by first extracting relevant identifiers and consuming the remaining populated fields as query parameters.
Delete
The Delete
trait has been implemented for a number of models already
also. This allows these models to be used for requesting a DELETE
call on exisitng data within your target XNAT host.
Project
Subject
Experiment
Scan
Resource
Similar to the Create
trait, in order for delete
calls to be
successful, you must provide all necessary identifiers in order to
has a valid DELETE
call made. Without them, your XNAT instance will
not know which resources to remove, and as a guard-rail, oxinat
does not allow this operation by default.
Dependencies
~8–20MB
~265K SLoC