# Dropbox SDK for Rust

Rust bindings to the Dropbox APIv2, generated by Stone from the official spec.

The Stone SDK and Dropbox API spec used to generate the code are in the `stone`
and `dropbox-api-spec` submodules, respectively. Use `git submodule init` and
`git submodule update` to fetch them.

The generated code is checked in under `src/generated` in order to simplify
building. To regenrate or update it, run `./generate.sh dropbox-api-spec`.
Doing so requires a working Python environment and some dependencies. See the
Stone documentation for details.

## Status of this SDK

This SDK is not yet official. What does this mean?
* There is no formal Dropbox support for the SDK at this point.
* Bugs may or may not get fixed.
* Not all SDK features may be implemented.

However, that said,
* The SDK is usable!
* We are happy to get feedback and/or pull requests from the community!

## A Note on Semver

This SDK is kept in sync with the current official API spec. While this spec is
generally changed in a backwards-compatible way, sometimes significant feature
additions are made, and sometimes backwards-incompatible changes may be
introduced. This crate will have its minor version bumped each time the spec is
updated. Users are encouraged to inspect the changes before updating the crate
version targeted, which is most easily done by looking at what revision the
`dropbox-api-spec` submodule is pinned to.

## HTTP Client

To actually use the API calls, you need a HTTP client -- all functions take a
`&HttpClient` as their first argument.  This trait is located at
`dropbox_sdk::client_trait::HttpClient`. Implement this trait and pass it as
the client argument.

If you don't want to implement your own, this SDK comes with an optional
default client that uses Hyper and your system's native TLS library.  To use
it, build with the `hyper_client` feature flag, and then there will be a
`dropbox_sdk::hyper_client::HyperClient` type that you can use.  The default
Hyper client needs a Dropbox API token; how you get one is up to you and your
program.

## Feature Flags

If you only use a subset of the API, and you want to cut down on the compile
time, you can explicitly specify features corresponding to the namespaces you
need. For each namespace there is a corresponding feature `dbx_{whatever}`. The
set of features can be updated if needed using the `update_manifest.py` script.
An example that only needs the 'files' and 'users' namespaces:
```
[dependencies.dropbox-sdk]
version = "*"
default_features = false
features = ["dbx_files", "dbx_users"]
```

## Result Types and Errors

Routes return a nested result type: `Result<Result<T, E>, dropbox_sdk::Error>`.
The outer `Result` is `Err` if something went wrong in the course of actually
making the request, such as network I/O errors or failure to serialize or
deserialize the request data. This `Result`'s `Ok` variant is another `Result`
where the `Ok` value is the deserialized successful result of the call, and the
`Err` value is the strongly-typed error returned by the API. This inner error
indicates some problem with the request, such as file not found, lacking
permissions, etc.

The rationale for splitting the errors this way is that the former category
usually can't be handled in any way other than by retrying the request, whereas
the latter category indicate problems with the actual request itself and
probably should not be retried. Since most callers can't handle I/O errors in
any sensible way, this allows them to use the `?` syntax to pass it up the
stack, while still handling errors returned by the server.

## Tests

The tests are auto-generated from the spec as well, but unlike the main code,
are not checked in. Run `./generate.sh` to generate the tests, and `cargo test`
to run them.

The test generator starts by generating a reference Python SDK and loading that
code. It then generates an instance of every type in the SDK and uses the
Python code to serialize them to JSON. Then it emits Rust tests that contain
the JSON as a string, deserialize it, assert that all fields contain the
expected values, re-serialize it, deserialize it again, and assert the fields
again. Thus we have reasonably good coverage of the serialization and
deserialization logic that the Rust generator emits, checked against the Python
implementation (which is what Dropbox uses server-side).

## Miscellaneous

Some implementation notes, limitations, and TODOs:
 * Stone allows structures to inherit from other structures and be polymorphic.
   Rust doesn't have these paradigms, so instead this SDK represents
   polymorphic parent structs as enums, and the inherited fields are put in all
   variants.  See `dropbox_sdk::files::Metadata` for an example.
 * The `error_chain` crate is used to generate a composite Error type for
   request errors, network I/O errors, and so on. This crate is deprecated, and
   so the implementation of this type will need to be changed at some point.
 * This crate only supports synchronous I/O. Eventually we probably want to
   support async I/O, which will require making incompatible changes to the
   types returned by routes. This should probably wait until the futures
   ecosystem and async/await have stabilized some more.
 * The `hyper` crate being used is the 0.10 branch, which is out of date. The
   reason for using this old version is that 0.10 is the last non-async Hyper
   implementation.
 * This code does not use `serde_derive` and instead uses manually-emitted
   serialization code. Previous work on this crate did attempt to use
   `serde_derive`, but the way the Dropbox API serializes unions containing
   structs (by collapsing their fields into the union) isn't supported by
   `serde_derive`. It also took an extremely long time to compile (~30 minutes
   for release build) and huge (~190MB) .rlib files. The hand-written code is
   more versatile, compiles faster, and produces a smaller binary, at the
   expense of making the generated source code much larger.
 * Types with constraints (such as strings with patterns or min/max lengths, or
   integers with a range) do not check that the data being stored in them meets
   the constraints.
 * The generated tests are not exhaustive. For unions with more than one
   variant, the test generator currently just picks one. Ideally it would emit
   tests for all variants.

## Happy Dropboxing!
