vapix
Client for AXIS Communications devices' VAPIX API. Bullet points:
#![forbid(unsafe_code)]
Features:
axis::Device
monitors and controls devices running AXIS firmware >= 5.00axis::Transport
decouples the library from http
Optional features:
hyper
: HTTP via axis::HyperTransport
(enabled by default)``rust
// Instantiate a Device using
hyper` to communicate
let uri = http::Uri::from_static("http://user:pass@1.2.3.4");
let device = axis::Device::new(axis::HyperTransport::default(), uri);
// Probe for VAPIX APIs supported by this device let services = device.services().await?;
// If it supports the basic device info service... if let Some(basicdeviceinfo) = services.basicdeviceinfo.asref() { // ...ask for those properties let properties = basicdeviceinfo.properties().await?; println!("productfullname: {:?}", properties.productfullname); println!(" serialnumber: {:?}", properties.serialnumber); } else { // If not, assume it supports the legacy parameters API, and retrieve those parameters let parameters = device.parameters().list(None).await?; println!("productfullname: {:?}", parameters["root.Brand.ProdFullName"]); println!(" serialnumber: {:?}", parameters["root.Properties.System.Soc"]); } ```
Use cargo test
, cargo check
, cargo clippy
, rustfmt
as usual. Open issues with issues, open PRs with changesets.
Many API tests use crate::mock_device()
, bound to a testing Transport
which goes to a block instead of to
the network. If you want to ensure that a function sends the right request or does the right thing with a certain
response, mock up a test which covers exactly that.
```rust
async fn update() { let device = crate::mockdevice(|req| { asserteq!(req.method(), http::Method::GET); asserteq!( req.uri().pathandquery().map(|pq| pq.asstr()), Some("/axis-cgi/param.cgi?action=update&foo.bar=baz+quxx") );
http::Response::builder()
.status(http::StatusCode::OK)
.header(http::header::CONTENT_TYPE, "text/plain")
.body(vec![b"OK".to_vec()])
});
let response = device
.parameters()
.update(vec![("foo.bar", "baz quxx")])
.await;
match response {
Ok(()) => {}
Err(e) => panic!("update should succeed: {}", e),
};
} ```
Tests covering safe-to-call APIs use create::test_with_devices()
to test against recordings of actual devices.
test_with_devices()
takes an async block which gets called repeatedly with various TestDevice
s, containing metadata
and a Device
. Tests are free to fail with Error::UnsupportedFeature
, but assertion failures or other errors will
cause the containing test to fail.
```rust
fn testsomething() { crate::testwithdevices(|testdevice| async move { // This block is called with various testdevices. If the block fails for any device, the // test will fail. If the test depends on particular device features, the test must probe // for those features, and it must succeed if the feature is missing. // // testdevice.deviceinfo.{model, firmwareversion, ..} describe the test device // test_device.device is a crate::Device<_> and can make arbitrary calls
let parameters = test_device.device.parameters();
let all_params = parameters.list_definitions(None).await?;
// assert!() things which should always be true on every device ever
// If behavior depends on firmware version, inquire about test_device's metadata
});
} ```
Recordings are produced using the test suite itself. When RECORD_DEVICE_URI
is set, each test_with_devices()
block
is additionally run against the live device, and a fixture is produced.
```console $ RECORDDEVICEURI=http://user:pass@1.2.3.4 cargo test … $ git status On branch master
Untracked files:
(use "git add
It is desirable to collect recordings from devices in the field, so test_with_devices()
blocks must avoid modifying
the device state.