1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// #![feature(return_position_impl_trait_in_trait)]
// #![feature(provide_any)]
//! # anystore
//!
//! `anystore` is a polymorphic, type-safe, composable async framework for specifying API for arbitrary stores
//! (including databases and configuration systems). It supports addressing arbitrary type-safe hierarchies of objects.
//!
//! It is best used for prototyping and configuration. It is especially good for situations when a storage system is needed,
//! but it's not very important, and you want to be able to change the storage provider quickly, in case the requirements change.
//!
//! It is good for when you don't want to learn *yet another API*. (It also might be good if you don't want to invent *yet another API*,
//! as it gives enough structure, primitives, and utils to help you build a decent client.)
//!
//! It is not intended to be used when you need high performance or reliability.
//!
//! **This crate is nightly-only**. It heavily depends on the `async_fn_in_trait` feature, which is not stable yet.
//!
//! Goals:
//!
//! * Provide many reasonable defaults and demand very few inconsequential choices.
//! * Provide a variety of wrappers to compose or augment particular stores.
//! * Type safety: as long as the type is statically determinable, it should be inferred.
//! * Work nicely with autocompletion and type inference.
//! * Make it easy and safe to switch the store used by a program.
//! * Try to capture that blessed feeling of wonder when you implement a few small pieces and suddenly
//! you have a lot of cool emerging features.
//! * The values most passed around should be thread-safe, `Sync`, `Send` -- for ergonomic reasons.
//!
//! Hopes and dreams:
//! * Generic dynamic configuration for any store.
//! * Become a reasonable framework for developing API interfaces for stores.
//! * Provide an extensive test framework to make testing of new store implementations easier.
//! * Support CLI/TUI tools for navigating and editing arbitrary stores.
//!
//! Non-goals:
//!
//! * Serious performance considerations beyond simple hygiene. This will never be a goal.
//! * Supporting all the database features for a particular database. We'd rather support the most generally
//! useful common subset.
//! * Complete concurrency safety. We're constrained here by the particulars of the stores used.
//! * For now, avoiding unsafe/unstable features. This crate already uses `async_fn_in_trait`,
//! and while it's not stable, there's no point in trying to keep everything `stable`.
//! Hopefully one day it'll stabilize and we can rethink this non-goal.
//! # Quick examples
//!
//! Check those out:
//! * Recursive traversing of the trees: [`Location::walk_tree_recursively`](location::Location::walk_tree_recursively)
//! * Turn any store of Strings into JSON store: [`LocatedJsonStore`](stores::located::json::LocatedJsonStore)
//! * [`FilterAddressesWrapperStore`](wrappers::filter_addresses::FilterAddressesWrapperStore) -- dynamically filter out addresses in any store
//!
//! # Main concepts
//!
//! ## Location
//!
//! [`location::Location`] is the object you'll use the most, and it contains most of the important client API.
//! It is simply a pair of an address and a store. This is the value you'd most typically pass around,
//! and it has a bunch of helper methods. You can traverse it further:
//!
//! # use anystore::stores::json::*;
//! # use anystore::location::Location;
//! # fn testloc(jsonlocation: Location<JsonPath, JsonValueStore>) {
//! let location = jsonlocation
//! .sub(JsonPathPart::Key("subkey".to_owned()))
//! .sub(JsonPathPart::Index(2));
//! # }
//! ```
//!
//! At every point, `Location` tracks the type and is typically able to statically tell you what kind of
//! values you can expect there. `Store`s can define a [`DefaultValue`](address::Addressable::DefaultValue) for the addresses,
//! which allows you to get immediately the correct default type:
//!
//! # use anystore::location::Location;
//! # use anystore::stores::json::*;
//! # use anystore::store::*;
//! # use anystore::address::traits::*;
//! # async fn test<V, S: Store + Addressable<JsonPath, DefaultValue=V> + AddressableGet<V, JsonPath>>(location: Location<JsonPath, S>) -> StoreResult<(), S> {
//! let val = location.getv().await?;
//! # Ok(()) };
//! ```
//!
//! In cases when you need a more refined type than the default, you can use e.g. [`Location::get::<Type>`](location::Location::get), as long as
//! the address supports that type.
//!
//! # use anystore::store::StoreResult;
//! # use anystore::location::Location;
//! # use anystore::store::Store;
//! # use anystore::address::traits::*;
//! # use anystore::stores::json::*;
//! # type Value = ();
//! # async fn test<S: Store + AddressableGet<Value, JsonPath>>(location: Location<JsonPath, S>) -> StoreResult<(), S> {
//! let val = location.get::<Value>().await?;
//! # Ok(()) };
//! ```
//!
//! In many cases, you can also use strings to traverse it, which is sometimes convenient but less typesafe.
//!
//! # use anystore::stores::json::*;
//! # use anystore::store::StoreResult;
//! # use anystore::location::Location;
//! # fn testloc(jsonlocation: Location<JsonPath, JsonValueStore>) -> StoreResult<(), JsonValueStore> {
//! let location = jsonlocation.path("subkey[2]")?.path("deeper.anotherone[12]")?;
//! # Ok(()) };
//! ```
//!
//! ## Address
//!
//! A storage system is defined around the concept of [`address::Address`]. An address uniquely identifies
//! a piece of content. A storage system can support several types as [Addresses][`address::Address`]: e.g.
//! pointers to specific databases, tables, rows, cells, or even sub-values inside cells.
//!
//! If a system understands a particular address, it implements [traits][`address::traits`] like [`address::traits::AddressableGet`].
//! `address::traits::AddressableGet<SomeType, SomeAddr>` means that `SomeAddr` can be used to read a value of `SomeType`.
//! Often there's a bunch of `SomeType`s that is useable with an address, including special values
//! like `address::primitive::Existence` which is simply used to check whether something exists at that address.
//!
//! In some cases, "address" and "value" are more or less the same thing.
//! E.g., if you list your Airtable bases, you get the data about them, but then you can reuse it as an address.
//!
//! ## Wrappers
//!
//! The traits in this crate are designed to be easily composable without too much boilerplate. That allows
//! the creation of abstract wrappers that add functionality to the existing stores or compose stores together.
//!
//! # Table of contents
//!
//! Be aware that most of the things in this crate are hidden behind specific features.
//!
//! Basic stores:
//! - [`stores::fs::FileSystemStore`](stores::fs::FileSystemStore) file system as a store
//!
//! Wrappers:
//! - [`stores::located::json::LocatedJsonStore`] -- use this over any `Location` to store JSON in it
//! - [`wrappers::filter_addresses::FilterAddressesWrapperStore`] -- wrap this over a store to dynamically filter out addresses
//!
//! Cloud services:
//! - [`stores::cloud::airtable::AirtableStore`](stores::cloud::airtable::AirtableStore) -- Airtable
//!
//! Memory:
//! - [`stores::cell::MemoryCellStore`] arbitrary memory cell as a readable/writable location (use it with wrappers)
//! - [`stores::json::json_value_store`] `serde_json::Value` as a store (simply a [`MemoryCellStore`](stores::cell::MemoryCellStore) wrapped in [`LocatedJsonStore`](stores::located::json::LocatedJsonStore))
//! - [`stores::indexed_vec::IndexedVecStore`] Vector indexed by Id derived from the value
//!