├── .github └── workflows │ ├── ci_cli_client.yml │ └── ci_server.yml ├── .gitignore ├── CASP_SPECIFICATION.md ├── README.md ├── cachew ├── Cargo.lock ├── Cargo.toml └── src │ ├── cli │ ├── arguments.rs │ ├── mod.rs │ └── validators.rs │ ├── database.rs │ ├── errors │ ├── authentication_errors.rs │ ├── database_errors.rs │ ├── mod.rs │ ├── parser_errors.rs │ └── protocol_errors.rs │ ├── main.rs │ ├── parser.rs │ ├── response.rs │ ├── schemas.rs │ ├── server.rs │ └── state.rs ├── cli-client ├── Cargo.toml ├── src │ ├── cli.rs │ ├── main.rs │ └── parser.rs └── test_client.py ├── dashboard └── .gitkeep ├── images ├── cachew-logo.png └── logo.png └── python-client └── .gitkeep /.github/workflows/ci_cli_client.yml: -------------------------------------------------------------------------------- 1 | name: CI cli-client 2 | 3 | on: 4 | push: 5 | paths: [ cli-client/** ] 6 | branches: [ "main" ] 7 | pull_request: 8 | paths: [ cli-client/** ] 9 | branches: [ "main" ] 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | RUST_BACKTRACE: 1 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Build 22 | run: cd ./cli-client && cargo build --verbose 23 | - name: Run tests 24 | run: cd ./cli-client && cargo test --verbose -------------------------------------------------------------------------------- /.github/workflows/ci_server.yml: -------------------------------------------------------------------------------- 1 | name: CI cachew server 2 | 3 | on: 4 | push: 5 | paths: [ cachew/** ] 6 | branches: [ "main" ] 7 | 8 | pull_request: 9 | paths: [ cachew/** ] 10 | branches: [ "main" ] 11 | 12 | env: 13 | CARGO_TERM_COLOR: always 14 | RUST_BACKTRACE: 1 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Build 23 | run: cd ./cachew && cargo build --verbose 24 | - name: Run tests 25 | run: cd ./cachew && cargo test --verbose -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.fuse* 2 | /cachew/target 3 | .vscode -------------------------------------------------------------------------------- /CASP_SPECIFICATION.md: -------------------------------------------------------------------------------- 1 | 2 | ## CASP - Cachew Socket Protocol 3 | 4 | CASP is the custom protocol used for communicating with a CachewDB instance via the TCP protocol. The following describes how requests to the server and responses from the server are structured (this information is important for creating clients). 5 | 6 | --- 7 | 8 | ### :point_up: In general...: 9 | Each request and response starts with the prefix ``CASP`` and ends with a ``\n``. All parts (prefix, status, message, etc.) of a CASP request/response are joined by the delimiter ``/``. 10 | 11 | --- 12 | 13 | ### :arrow_left: Request specification: 14 | #### Structure: 15 | | prefix | delimiter | message | delimiter |suffix | 16 | |:-------:|:-------:|:-------:|:-------:|:-------:| 17 | | CASP | / | *command* | / | \n | 18 | 19 | #### Examples: 20 | - ``CASP/AUTH password/\n`` 21 | - ``CASP/SET key "some value"/\n`` 22 | - ``CASP/SET MANY k1 1, k2 2, k3 3/\n`` 23 | - ``CASP/GET key/\n`` 24 | - ``CASP/GET MANY k1 k2 k3/\n`` 25 | - ``CASP/GET RANGE k1 k3/\n`` 26 | - ``CASP/DEL MANY k1 k2 k3/\n`` 27 | - ``CASP/DEL RANGE k1 k3/\n`` 28 | 29 | --- 30 | 31 | ### :arrow_right: Response specification: 32 | #### Response structure for GET requests: 33 | | prefix | delim. | status | delim. | cmd. type | delim. | type | delim. | value/s | delim. | suffix | 34 | |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:| 35 | | CASP | / | OK | / | *cmd. type* | / | *type* | / | *value/s* | / | \n | 36 | 37 | #### Response structure for other requests: 38 | | prefix | delim. | status | delim. | cmd. type | delim. | suffix | 39 | |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:| 40 | | CASP | / | OK | / | *cmd. type* | / | \n | 41 | 42 | #### Response structure for errors: 43 | | prefix | delim. | status | delim. | message | delim. | suffix | 44 | |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:| 45 | | CASP | / | ERROR | / | *error message* | / | \n | 46 | 47 | #### Where ... 48 | ... *type* is one of: 49 | ``STR``, ``INT``, ``FLOAT``, ``BOOL``, ``JSON`` 50 | ... *cmd. type* is one of the command identifiers: 51 | ``AUTH``, ``SET``, ``GET``, etc. 52 | 53 | #### Examples: 54 | - ``CASP/OK/AUTH/Authentication succeeded./\n`` 55 | - ``CASP/OK/SET/\n`` 56 | - ``CASP/OK/SET MANY/\n`` 57 | - ``CASP/OK/DEL/\n`` 58 | - ``CASP/OK/DEL MANY/\n`` 59 | - ``CASP/OK/DEL RANGE/\n`` 60 | - ``CASP/OK/GET/INT/-99/\n`` 61 | - ``CASP/OK/GET MANY/STR/"v1","v2""/\n`` 62 | - ``CASP/OK/GET RANGE/FLOAT/0.1, 0.5, 1.99/\n`` 63 | - ``CASP/ERROR/Some error message!/\n`` 64 | 65 | --- 66 | 67 | ### :mailbox: Response bodies for GET requests: 68 | As seen before, the value/-s returned from GET / GET MANY / GET RANGE requests are in the following section: 69 | ``` 70 | CASP/OK/GET/STR/<--HERE-->/\n 71 | ``` 72 | 73 | The following focuses on how to parse the data within the fourth and fifth ``/``. 74 | 75 | #### 1. Parsing integers: 76 | A GET request on an integer database promises to return a simple sequence of characters from 0-9 and potentially a minus sign at the beginning which can be parsed into an integer by the client. 77 | 78 | Responses on GET MANY / GET RANGE requests return a many integers seperated by a comma. 79 | 80 | ##### Examples: 81 | - ``CASP/OK/GET/INT/10/\n`` -> ``10`` 82 | - ``CASP/OK/GET MANY/INT/1,2,3/\n`` -> ``1``, ``2``, ``3`` 83 | - ``CASP/OK/GET RANGE/INT/3,2,7/\n`` -> ``3``, ``2``, ``7`` 84 | 85 | #### 2. Parsing floats: 86 | A GET request on a float database promises to return a simple sequence of characters from 0-9 and potentially a minus sign at the beginning and always with a dot ``.`` as the decimal seperator. which can be parsed into a float by the client. 87 | 88 | Responses on GET MANY / GET RANGE requests return a many floats seperated by a comma. 89 | 90 | ##### Examples: 91 | - ``CASP/OK/GET/FLOAT/-0.1/\n`` -> ``-0.1`` 92 | - ``CASP/OK/GET MANY/FLOAT/0.9,10.0,-20.284/\n`` -> ``0.9``, ``10.0``, ``-20.284`` 93 | - ``CASP/OK/GET RANGE/FLOAT/0.009,1.25/\n`` -> ``0.009``, ``1.25`` 94 | 95 | #### 2. Parsing strings: 96 | A GET request on a string database promises to return a simple sequence of any characters encapsuled in ``"`` which can be parsed into a string by the client. 97 | 98 | Responses on GET MANY / GET RANGE requests return a many strings seperated by a comma. 99 | 100 | ##### Examples: 101 | - ``CASP/OK/GET/STR/"hello"/\n`` -> ``"hello""`` 102 | - ``CASP/OK/GET MANY/STR/"a","b","c"/\n`` -> ``"a"``, ``"b"``, ``"c"`` 103 | - ``CASP/OK/GET RANGE/STR/"x","r","z","s"/\n`` -> ``"x"``, ``"r"``, ``"z"``, ``"s"`` 104 | 105 | --- 106 | 107 | #### ❌ Error responses: 108 | As seen before, the error message returned is in the following section: 109 | ``` 110 | CASP/ERR/<--HERE-->/\n 111 | ``` 112 | 113 | ##### Error types: 114 | | error type | description | 115 | |:-------|:-------| 116 | | AuthenticationError | Errors concerning the authentication. | 117 | | ProtocolError | Errors thrown if the request isn't valid CASP. | 118 | | ParserError | Errors thrown if the request body is invalid. | 119 | | DatabaseError | Errors thrown if a database query fails. | 120 | 121 | ##### Structure: 122 | All error messages are structured in the following way: 123 | The error type (see above), then a specific error type, then a colon and finally the error descripion. 124 | 125 | ##### Examples: 126 | - ``AuthenticationError 'notAuthenticated': Please authenticate before executing queries.`` 127 | - ``AuthenticationError 'authenticationFailed': Wrong password.`` 128 | - ``ProtocolError 'emptyRequest': Can't process empty request.`` 129 | - ``ProtocolError 'startMarkerNotFound': Expected request to start with 'CASP/'.`` 130 | - ``ProtocolError 'endMarkerNotFound': Expected request to end with '/\n'`` 131 | - ``ParserError 'invalidRange': Expected two keys got 3.`` 132 | - ``ParserError 'unexpectedCharacter': commata, slashes and spaces are not allowed in keys.`` 133 | - ``ParserError 'invalidKeyValuePair': Expected two parameters (key and value), found 1.`` 134 | - ``ParserError 'unknownQueryOperation': Query 'SER key 10' not recognized.`` 135 | - ``ParserError 'wrongValueType': The value doesn't match the database type.`` 136 | - ``ParserError 'wrongAuthentication': Couldn't read password. Expecting: 'AUTH '`` 137 | - ``DatabaseError 'keyNotFound': The key '2d837e' doesn't exist.`` 138 | - ``DatabaseError 'invalidRangeOrder': The lower key is bigger than the upper key.`` 139 | - ``DatabaseError 'wrongValueType': The value doesn't match the database type.`` 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![CI cachew server](https://github.com/theopfr/cachew-db/actions/workflows/ci_server.yml/badge.svg)](https://github.com/theopfr/cachew-db/actions/workflows/ci_server.yml) 3 |     4 | [![CI cli-client](https://github.com/theopfr/cachew-db/actions/workflows/ci_cli_client.yml/badge.svg)](https://github.com/theopfr/cachew-db/actions/workflows/ci_cli_client.yml) 5 | 6 |

7 | 8 |

9 | 10 | ## A light weight, typed, in-memory, ordered, key-value database. 11 | 12 | ### :memo: Features: 13 | - cached / in-memory, non-persistant 14 | - uses a b-tree map to store data ordered by keys 15 | - same type for all database values 16 | - simple password authentication 17 | - custom socket protocol for communication (_CASP_: Cashew Socket Protocol) 18 | - graceful shutdown 19 | - concurrent client handling 20 | 21 | --- 22 | 23 | ### :memo: Running the CachewDB server: 24 | ##### 1. Build using Cargo: 25 | ```bash 26 | cd ./cachew/ 27 | cargo build --release 28 | ``` 29 | 30 | ##### 2. Run using Cargo: 31 | To start the CachewDB server you have to run ``cargo run`` or the binary ``./target/release/cachew``. You also have to specify the following arguments either with command line flags or using environment variables: 32 | 33 | | flag | short | description | ENV | 34 | |:-------|:----------|:----------|:----------| 35 | | --db-type | -t | Sets the data type (check possible types in the "Types" section). | CACHEW_DB_TYPE | 36 | | --password | -p | Sets the password for the database (must have at least: 1 upper-, 1 lowercase letter, 1 special char., >= 8 chars.). | CACHEW_DB_PASSWORD | 37 | | --host | n/a | The host address the server will be running on (optional, default: ``127.0.0.1``). | CACHEW_DB_HOST | 38 | | --db-type | n/a | The port the server will be accessible on (optional, default: ``8080``). | CACHEW_DB_PORT | 39 | 40 | ##### Examples: 41 | 1. Using flags to set the db-type and password. 42 | ```bash 43 | cargo run --release -- -t STR -p Password123# # host will default to 127.0.0.1 and port to 8080 44 | ``` 45 | 2. Using ENV variables to specify all arguments: 46 | ```bash 47 | export CACHEW_DB_TYPE="STR" 48 | export CACHEW_DB_PASSWORD="Password123#" 49 | export CACHEW_DB_HOST="127.0.0.1" 50 | export CACHEW_DB_PORT="2345" 51 | cargo run --release 52 | ``` 53 | 54 | --- 55 | 56 | ### :memo: Running the CachewDB CLI client: 57 | 58 | ##### 1. Build using Cargo: 59 | ```bash 60 | cd ./cli-client/ 61 | cargo build --release 62 | ``` 63 | 64 | ##### 2. Run using Cargo: 65 | To start the CachewDB CLI client you have to run ``cargo run`` or the binary ``./target/release/cli-client``. You have to specify the following arguments with command line flags: 66 | 67 | | flag | description | 68 | |:-------|:----------| 69 | | --host | Sets host remote address of the CacheDB server. | 70 | | --port | Sets the remote port of the CachewDB server. | 71 | | --password | Sets the password needed for authenticating on the CachewDB server. | 72 | 73 | ##### Example: 74 | ``` 75 | cargo run --release -- --host 127.0.0.1 --port 8080 --password Password123# 76 | ``` 77 | 78 | --- 79 | 80 | ### :memo: Commands: 81 | | command | description | example | 82 | |:-------|:----------|:-------| 83 | | **AUTH** {password} | Authentication for a CachewDB instance. | AUTH mypwd123 | 84 | | **SET** {key} {value} | Insert new key value pair. | SET myKey "myValue" | 85 | | **SET MANY** {key} {value}, {key} {value} | Bulk insert multiple key value pairs. | SET MANY key1 "value 1", key2 "value 2" | 86 | | **GET** {key} | Get value from key. | GET myKey | 87 | | **GET MANY** {key} {key} ... | Get multiple values from their keys. | GET MANY key1 key2 key3 | 88 | | **GET RANGE** {lower-key} {upper-key} | Get values from a range of keys. | GET RANGE aKey zKey | 89 | | **DEL** {key} | Delete key value pair. | DEL myKey | 90 | | **DEL MANY** {key} {key} ... | Delete multiple key value pairs. | DEL MANY key1 key2 key3 | 91 | | **DEL RANGE** {lower-key} {upper-key} | Delete a range of key value pairs. | DEL RANGE aKey zKey | 92 | | **CLEAR** | Removes all entries in the database. | CLEAR | 93 | | **LEN** | Returns the amount of entries in the database.| LEN | 94 | | **EXISTS** {key} | Returns a bool signaling if a key exists in the database. | EXSITS key | 95 | | **PING** | Answers with "PONG" (used to check if the server is running). | PING | 96 | | **SHUTDOWN** | Gracefully shutdowns the database. | PING | 97 | --- 98 | 99 | ### :memo: Keys: 100 | A key can consist of every character with two exepctions: 101 | 1. If a key should contain a space, comma (``,``) or slash (``/``) it must be encapsuled by quotes (``"``). 102 | 2. If a key should contain quotes, the quotes must be escaped by adding a backslash before them (``\"``). 103 | 104 | These restrictions exist to make parsing requests and responses easier. 105 | 106 | ##### Examples of valid keys: 107 | - ``id5ffh26a`` 108 | - ``?9%39__`` 109 | - ``"my key / 1,2"`` 110 | - ``id\"5ffh26a\"`` 111 | 112 | ##### Examples of invalid keys: 113 | - ``id 5ffh26a`` 114 | - ``id/5ffh26a`` 115 | - ``id"5ffh26a"`` 116 | - ``id,5ffh26a`` 117 | 118 | --- 119 | 120 | ### :memo: Types: 121 | Supported types are: 122 | | type | description | 123 | |:-------|:----------| 124 | | **STR** | Simple string (must be encapsulated with ``"`` and double quotes inside must me escaped like this: ``\"``). | 125 | | **INT** | 32 bit signed integer. | 126 | | **FLOAT** | 32 bit float. | 127 | | **BOOL** | Either ``true`` or ``false``. | 128 | | **JSON** | Behaves the same as strings (must be encapsulated with ``"`` and double quotes inside must me escaped like this: ``\"``). | 129 | 130 | --- 131 | 132 | ### :memo: CASP protocol specification: 133 | For the specification of the custom protocol used for communicating with a CachewDB instance check this document: [CASP_SPECIFICATION.md](./CASP_SPECIFICATION.md) 134 | 135 | --- 136 | 137 | #### Todo 138 | - [x] add graceful shutdown 139 | - [x] add command line flag handler 140 | - [x] built CLI client 141 | - [ ] add persistance 142 | - [ ] add INCR/DECR commands for INT type 143 | -------------------------------------------------------------------------------- /cachew/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.3.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is-terminal", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.0.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 49 | dependencies = [ 50 | "windows-sys", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "1.0.1" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" 58 | dependencies = [ 59 | "anstyle", 60 | "windows-sys", 61 | ] 62 | 63 | [[package]] 64 | name = "atty" 65 | version = "0.2.14" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 68 | dependencies = [ 69 | "hermit-abi 0.1.19", 70 | "libc", 71 | "winapi", 72 | ] 73 | 74 | [[package]] 75 | name = "autocfg" 76 | version = "1.1.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 79 | 80 | [[package]] 81 | name = "bincode" 82 | version = "1.3.3" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 85 | dependencies = [ 86 | "serde", 87 | ] 88 | 89 | [[package]] 90 | name = "bitflags" 91 | version = "1.3.2" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 94 | 95 | [[package]] 96 | name = "bitflags" 97 | version = "2.3.3" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" 100 | 101 | [[package]] 102 | name = "bytes" 103 | version = "1.4.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 106 | 107 | [[package]] 108 | name = "cachew" 109 | version = "0.1.0" 110 | dependencies = [ 111 | "bincode", 112 | "clap", 113 | "env_logger", 114 | "log", 115 | "regex", 116 | "serde", 117 | "serde_json", 118 | "tokio", 119 | ] 120 | 121 | [[package]] 122 | name = "cc" 123 | version = "1.0.79" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 126 | 127 | [[package]] 128 | name = "cfg-if" 129 | version = "1.0.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 132 | 133 | [[package]] 134 | name = "clap" 135 | version = "4.3.19" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" 138 | dependencies = [ 139 | "clap_builder", 140 | "clap_derive", 141 | "once_cell", 142 | ] 143 | 144 | [[package]] 145 | name = "clap_builder" 146 | version = "4.3.19" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" 149 | dependencies = [ 150 | "anstream", 151 | "anstyle", 152 | "clap_lex", 153 | "strsim", 154 | ] 155 | 156 | [[package]] 157 | name = "clap_derive" 158 | version = "4.3.12" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" 161 | dependencies = [ 162 | "heck", 163 | "proc-macro2", 164 | "quote", 165 | "syn", 166 | ] 167 | 168 | [[package]] 169 | name = "clap_lex" 170 | version = "0.5.0" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" 173 | 174 | [[package]] 175 | name = "colorchoice" 176 | version = "1.0.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 179 | 180 | [[package]] 181 | name = "env_logger" 182 | version = "0.8.4" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" 185 | dependencies = [ 186 | "atty", 187 | "humantime", 188 | "log", 189 | "regex", 190 | "termcolor", 191 | ] 192 | 193 | [[package]] 194 | name = "errno" 195 | version = "0.3.1" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" 198 | dependencies = [ 199 | "errno-dragonfly", 200 | "libc", 201 | "windows-sys", 202 | ] 203 | 204 | [[package]] 205 | name = "errno-dragonfly" 206 | version = "0.1.2" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 209 | dependencies = [ 210 | "cc", 211 | "libc", 212 | ] 213 | 214 | [[package]] 215 | name = "heck" 216 | version = "0.4.1" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 219 | 220 | [[package]] 221 | name = "hermit-abi" 222 | version = "0.1.19" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 225 | dependencies = [ 226 | "libc", 227 | ] 228 | 229 | [[package]] 230 | name = "hermit-abi" 231 | version = "0.2.6" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 234 | dependencies = [ 235 | "libc", 236 | ] 237 | 238 | [[package]] 239 | name = "hermit-abi" 240 | version = "0.3.2" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" 243 | 244 | [[package]] 245 | name = "humantime" 246 | version = "2.1.0" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 249 | 250 | [[package]] 251 | name = "is-terminal" 252 | version = "0.4.9" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" 255 | dependencies = [ 256 | "hermit-abi 0.3.2", 257 | "rustix", 258 | "windows-sys", 259 | ] 260 | 261 | [[package]] 262 | name = "itoa" 263 | version = "1.0.6" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 266 | 267 | [[package]] 268 | name = "libc" 269 | version = "0.2.147" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 272 | 273 | [[package]] 274 | name = "linux-raw-sys" 275 | version = "0.4.3" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" 278 | 279 | [[package]] 280 | name = "lock_api" 281 | version = "0.4.10" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" 284 | dependencies = [ 285 | "autocfg", 286 | "scopeguard", 287 | ] 288 | 289 | [[package]] 290 | name = "log" 291 | version = "0.4.19" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" 294 | 295 | [[package]] 296 | name = "memchr" 297 | version = "2.5.0" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 300 | 301 | [[package]] 302 | name = "mio" 303 | version = "0.8.8" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" 306 | dependencies = [ 307 | "libc", 308 | "wasi", 309 | "windows-sys", 310 | ] 311 | 312 | [[package]] 313 | name = "num_cpus" 314 | version = "1.15.0" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 317 | dependencies = [ 318 | "hermit-abi 0.2.6", 319 | "libc", 320 | ] 321 | 322 | [[package]] 323 | name = "once_cell" 324 | version = "1.18.0" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 327 | 328 | [[package]] 329 | name = "parking_lot" 330 | version = "0.12.1" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 333 | dependencies = [ 334 | "lock_api", 335 | "parking_lot_core", 336 | ] 337 | 338 | [[package]] 339 | name = "parking_lot_core" 340 | version = "0.9.8" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" 343 | dependencies = [ 344 | "cfg-if", 345 | "libc", 346 | "redox_syscall", 347 | "smallvec", 348 | "windows-targets", 349 | ] 350 | 351 | [[package]] 352 | name = "pin-project-lite" 353 | version = "0.2.9" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 356 | 357 | [[package]] 358 | name = "proc-macro2" 359 | version = "1.0.60" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" 362 | dependencies = [ 363 | "unicode-ident", 364 | ] 365 | 366 | [[package]] 367 | name = "quote" 368 | version = "1.0.28" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" 371 | dependencies = [ 372 | "proc-macro2", 373 | ] 374 | 375 | [[package]] 376 | name = "redox_syscall" 377 | version = "0.3.5" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 380 | dependencies = [ 381 | "bitflags 1.3.2", 382 | ] 383 | 384 | [[package]] 385 | name = "regex" 386 | version = "1.8.4" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" 389 | dependencies = [ 390 | "aho-corasick", 391 | "memchr", 392 | "regex-syntax", 393 | ] 394 | 395 | [[package]] 396 | name = "regex-syntax" 397 | version = "0.7.2" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" 400 | 401 | [[package]] 402 | name = "rustix" 403 | version = "0.38.4" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" 406 | dependencies = [ 407 | "bitflags 2.3.3", 408 | "errno", 409 | "libc", 410 | "linux-raw-sys", 411 | "windows-sys", 412 | ] 413 | 414 | [[package]] 415 | name = "ryu" 416 | version = "1.0.13" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 419 | 420 | [[package]] 421 | name = "scopeguard" 422 | version = "1.1.0" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 425 | 426 | [[package]] 427 | name = "serde" 428 | version = "1.0.164" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" 431 | dependencies = [ 432 | "serde_derive", 433 | ] 434 | 435 | [[package]] 436 | name = "serde_derive" 437 | version = "1.0.164" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" 440 | dependencies = [ 441 | "proc-macro2", 442 | "quote", 443 | "syn", 444 | ] 445 | 446 | [[package]] 447 | name = "serde_json" 448 | version = "1.0.96" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" 451 | dependencies = [ 452 | "itoa", 453 | "ryu", 454 | "serde", 455 | ] 456 | 457 | [[package]] 458 | name = "signal-hook-registry" 459 | version = "1.4.1" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 462 | dependencies = [ 463 | "libc", 464 | ] 465 | 466 | [[package]] 467 | name = "smallvec" 468 | version = "1.10.0" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 471 | 472 | [[package]] 473 | name = "socket2" 474 | version = "0.4.9" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 477 | dependencies = [ 478 | "libc", 479 | "winapi", 480 | ] 481 | 482 | [[package]] 483 | name = "strsim" 484 | version = "0.10.0" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 487 | 488 | [[package]] 489 | name = "syn" 490 | version = "2.0.18" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" 493 | dependencies = [ 494 | "proc-macro2", 495 | "quote", 496 | "unicode-ident", 497 | ] 498 | 499 | [[package]] 500 | name = "termcolor" 501 | version = "1.2.0" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 504 | dependencies = [ 505 | "winapi-util", 506 | ] 507 | 508 | [[package]] 509 | name = "tokio" 510 | version = "1.28.2" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" 513 | dependencies = [ 514 | "autocfg", 515 | "bytes", 516 | "libc", 517 | "mio", 518 | "num_cpus", 519 | "parking_lot", 520 | "pin-project-lite", 521 | "signal-hook-registry", 522 | "socket2", 523 | "tokio-macros", 524 | "windows-sys", 525 | ] 526 | 527 | [[package]] 528 | name = "tokio-macros" 529 | version = "2.1.0" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 532 | dependencies = [ 533 | "proc-macro2", 534 | "quote", 535 | "syn", 536 | ] 537 | 538 | [[package]] 539 | name = "unicode-ident" 540 | version = "1.0.9" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" 543 | 544 | [[package]] 545 | name = "utf8parse" 546 | version = "0.2.1" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 549 | 550 | [[package]] 551 | name = "wasi" 552 | version = "0.11.0+wasi-snapshot-preview1" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 555 | 556 | [[package]] 557 | name = "winapi" 558 | version = "0.3.9" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 561 | dependencies = [ 562 | "winapi-i686-pc-windows-gnu", 563 | "winapi-x86_64-pc-windows-gnu", 564 | ] 565 | 566 | [[package]] 567 | name = "winapi-i686-pc-windows-gnu" 568 | version = "0.4.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 571 | 572 | [[package]] 573 | name = "winapi-util" 574 | version = "0.1.5" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 577 | dependencies = [ 578 | "winapi", 579 | ] 580 | 581 | [[package]] 582 | name = "winapi-x86_64-pc-windows-gnu" 583 | version = "0.4.0" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 586 | 587 | [[package]] 588 | name = "windows-sys" 589 | version = "0.48.0" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 592 | dependencies = [ 593 | "windows-targets", 594 | ] 595 | 596 | [[package]] 597 | name = "windows-targets" 598 | version = "0.48.0" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 601 | dependencies = [ 602 | "windows_aarch64_gnullvm", 603 | "windows_aarch64_msvc", 604 | "windows_i686_gnu", 605 | "windows_i686_msvc", 606 | "windows_x86_64_gnu", 607 | "windows_x86_64_gnullvm", 608 | "windows_x86_64_msvc", 609 | ] 610 | 611 | [[package]] 612 | name = "windows_aarch64_gnullvm" 613 | version = "0.48.0" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 616 | 617 | [[package]] 618 | name = "windows_aarch64_msvc" 619 | version = "0.48.0" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 622 | 623 | [[package]] 624 | name = "windows_i686_gnu" 625 | version = "0.48.0" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 628 | 629 | [[package]] 630 | name = "windows_i686_msvc" 631 | version = "0.48.0" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 634 | 635 | [[package]] 636 | name = "windows_x86_64_gnu" 637 | version = "0.48.0" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 640 | 641 | [[package]] 642 | name = "windows_x86_64_gnullvm" 643 | version = "0.48.0" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 646 | 647 | [[package]] 648 | name = "windows_x86_64_msvc" 649 | version = "0.48.0" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 652 | -------------------------------------------------------------------------------- /cachew/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cachew" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | serde = { version = "1.0.104", features = ["derive"] } 10 | serde_json = "1.0" 11 | bincode = "1.0" 12 | regex = "1.5" 13 | tokio = { version = "1", features = ["full"] } 14 | log = "0.4" 15 | env_logger = "0.8" 16 | clap = { version = "4.3.0", features = ["derive"] } -------------------------------------------------------------------------------- /cachew/src/cli/arguments.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use crate::schemas::{DatabaseType}; 3 | use std::env::{self, VarError}; 4 | use log::{info, warn, error}; 5 | 6 | use crate::cli::validators::{validate_database_type, validate_password}; 7 | 8 | /// Stores the CLI arguments for starting the CachewDB server. 9 | /// 10 | /// # Fields: 11 | /// * Optional: `database_type`: The value type the b-tree map will store. 12 | /// * Optional: `password`: The password needed to communitcate with the CachewDB server. 13 | /// * Optional: `host`: The address which the CachewDB server is hostet on. 14 | /// * Optional: `port`: The port on which the CachewDB server is accessible. 15 | #[derive(Parser, Debug)] 16 | #[command(author, version, about, long_about = None)] 17 | pub struct CliArgs { 18 | #[arg(short = 't', long = "db-type")] 19 | pub database_type: Option, 20 | 21 | #[arg(short = 'p', long = "password")] 22 | pub password: Option, 23 | 24 | #[arg(long = "host")] 25 | pub host: Option, 26 | 27 | #[arg(long = "port")] 28 | pub port: Option, 29 | } 30 | 31 | /// Stores initial arguments needed for starting a CachewDB instance. 32 | /// 33 | /// # Fields: 34 | /// * `database_type`: The value type the b-tree map will store. 35 | /// * `password`: The password needed to communitcate with the CachewDB server. 36 | /// * `host`: The address which the CachewDB server is hostet on. 37 | /// * `port`: The port on which the CachewDB server is accessible. 38 | pub struct CachewDbArgs { 39 | pub database_type: DatabaseType, 40 | pub password: String, 41 | pub host: String, 42 | pub port: String 43 | } 44 | 45 | /// Gets the initial arguments needed for starting a CachewDB instance from CLI flags or ENV variables. 46 | /// 47 | /// # Returns: 48 | /// An CachewDbArgs instance storing the database-type, password, host and port. 49 | pub fn get_cachew_db_args() -> CachewDbArgs { 50 | let cli_args = CliArgs::parse(); 51 | 52 | CachewDbArgs { 53 | database_type: get_argument::(cli_args.database_type, "CACHEW_DB_TYPE", validate_database_type, None), 54 | password: get_argument::(cli_args.password, "CACHEW_DB_PASSWORD", validate_password, None), 55 | host: get_argument::(cli_args.host, "CACHEW_DB_HOST", |x| x, Some("127.0.0.1".to_string())), 56 | port: get_argument::(cli_args.port, "CACHEW_DB_PORT", |x| x, Some("8080".to_string())), 57 | } 58 | } 59 | 60 | /// Gets an argument provided by the user using the CLI flag or ENV variable. 61 | /// The generic ``T`` is the type of the provided argument after parsing. 62 | /// 63 | /// # Arguments: 64 | /// * `cli_argument`: The argument provided by the CLI flag (will be None if none was provided). 65 | /// * `env_var`: The name of the environment variable that can possibly store the argument. 66 | /// * `validator`: A function to validate the argument provided by the user 67 | /// 68 | /// # Returns: 69 | /// Returns the valdiated argument provided by the user. 70 | /// If the argument is invalid or none was provided, the program panics. 71 | pub fn get_argument(cli_argument: Option, env_var: &str, validator: fn (String) -> T, default: Option) -> T { 72 | match cli_argument { 73 | Some(cli_argument) => validator(cli_argument), 74 | None => { 75 | let env: Result = env::var(env_var); 76 | match env { 77 | Ok(env_argument) => validator(env_argument), 78 | Err(_) => { 79 | 80 | match default { 81 | Some(default) => default, 82 | None => { 83 | let error_message: String = format!("Environment variable '{}' is not set and no according flag was provided.", env_var); 84 | error!("{}", error_message); 85 | panic!("{}", error_message); 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | use super::*; 99 | 100 | #[test] 101 | fn test_get_argument() { 102 | // test database type provided by the CLI and ENV variable 103 | let database_type_cli = get_argument::(Some("STR".to_string()), "CACHEW_DB_TYPE", validate_database_type, None); 104 | assert_eq!(database_type_cli, DatabaseType::Str); 105 | 106 | std::env::set_var("CACHEW_DB_TYPE", "INT"); 107 | 108 | let database_type_env = get_argument::(None, "CACHEW_DB_TYPE", validate_database_type, None); 109 | assert_eq!(database_type_env, DatabaseType::Int); 110 | 111 | 112 | // test password provided by the CLI and ENV variable 113 | let password_cli = get_argument::(Some("Valid%Password123".to_string()), "CACHEW_DB_PASSWORD", validate_password, None); 114 | assert_eq!(password_cli, "Valid%Password123".to_string()); 115 | 116 | std::env::set_var("CACHEW_DB_PASSWORD", "Valid%Password123"); 117 | 118 | let password_env = get_argument::(None, "CACHEW_DB_PASSWORD", validate_password, None); 119 | assert_eq!(password_env, "Valid%Password123".to_string()); 120 | 121 | // test host provided by the CLI and ENV variable 122 | let host_cli = get_argument::(Some("127.0.0.1".to_string()), "CACHEW_DB_HOST", |x| x, Some("127.0.0.1".to_string())); 123 | assert_eq!(host_cli, "127.0.0.1".to_string()); 124 | 125 | std::env::set_var("CACHEW_DB_HOST", "127.0.0.1"); 126 | 127 | let host_env = get_argument::(None, "CACHEW_DB_HOST", |x| x, Some("127.0.0.1".to_string())); 128 | assert_eq!(host_env, "127.0.0.1".to_string()); 129 | 130 | 131 | // test host provided by the CLI and ENV variable 132 | let port_cli = get_argument::(Some("5432".to_string()), "CACHEW_DB_PORT", |x| x, Some("8080".to_string())); 133 | assert_eq!(port_cli, "5432".to_string()); 134 | 135 | std::env::set_var("CACHEW_DB_PORT", "5432"); 136 | 137 | let port_env = get_argument::(None, "CACHEW_DB_PORT", |x| x, Some("8080".to_string())); 138 | assert_eq!(port_env, "5432".to_string()); 139 | } 140 | 141 | #[test] 142 | fn test_get_default_host() { 143 | std::env::remove_var("CACHEW_DB_HOST"); 144 | let host_env = get_argument::(None, "CACHEW_DB_HOST", |x| x, Some("0.0.0.0".to_string())); 145 | assert_eq!(host_env, "0.0.0.0".to_string()); 146 | } 147 | 148 | #[test] 149 | fn test_get_default_port() { 150 | std::env::remove_var("CACHEW_DB_PORT"); 151 | let port_env = get_argument::(None, "CACHEW_DB_PORT", |x| x, Some("8080".to_string())); 152 | assert_eq!(port_env, "8080".to_string()); 153 | } 154 | 155 | #[test] 156 | #[should_panic(expected = "Environment variable 'CACHEW_DB_TYPE' is not set and no according flag was provided.")] 157 | fn test_missing_database_type() { 158 | std::env::remove_var("CACHEW_DB_TYPE"); 159 | let _ = get_argument::(None, "CACHEW_DB_TYPE", validate_database_type, None); 160 | } 161 | 162 | #[test] 163 | #[should_panic(expected = "Environment variable 'CACHEW_DB_PASSWORD' is not set and no according flag was provided.")] 164 | fn test_missing_password() { 165 | std::env::remove_var("CACHEW_DB_PASSWORD"); 166 | let _ = get_argument::(None, "CACHEW_DB_PASSWORD", validate_password, None); 167 | } 168 | 169 | } -------------------------------------------------------------------------------- /cachew/src/cli/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod arguments; 2 | pub mod validators; -------------------------------------------------------------------------------- /cachew/src/cli/validators.rs: -------------------------------------------------------------------------------- 1 | use log::{info, warn, error}; 2 | use regex::Regex; 3 | 4 | use crate::schemas::{DatabaseType}; 5 | 6 | 7 | /// Validates the database-type provided by the user. 8 | /// 9 | /// # Arguments: 10 | /// * `database_type_arg`: The database-type provided by the user. 11 | /// 12 | /// # Returns: 13 | /// Returns a DatabaseType instance based on the the database-type the user provided. 14 | /// If the database-type is invalid, the program panics. 15 | pub fn validate_database_type(database_type_arg: String) -> DatabaseType { 16 | match database_type_arg.as_str() { 17 | "STR" => DatabaseType::Str, 18 | "INT" => DatabaseType::Int, 19 | "FLOAT" => DatabaseType::Float, 20 | "BOOL" => DatabaseType::Bool, 21 | "JSON" => DatabaseType::Json, 22 | _ => { 23 | let error_message: String = format!("Invalid database type '{}'. Choose one of: STR, INT, FLOAT, BOOL or JSON.", database_type_arg); 24 | error!("{}", error_message); 25 | panic!("{}", error_message); 26 | } 27 | } 28 | } 29 | 30 | /// Validates the password strength provided by the user using regex. 31 | /// Criteria: At least 8 characters, 1 uppercase letter, 1 lowercase letter, 1 special character, one digit. 32 | /// 33 | /// # Arguments: 34 | /// * `password`: The password provided by the user. 35 | /// 36 | /// # Returns: 37 | /// Returns the same password if validation passes. If not, the program panics. 38 | pub fn validate_password(password: String) -> String { 39 | let mut is_strong: bool = true; 40 | 41 | // TODO: replace with regex, but it's hard since look-arounds and look-behinds are not supported. 42 | 43 | // check if password is under 8 chars 44 | if password.len() < 8 { 45 | is_strong = false; 46 | } 47 | 48 | // check if it contains at least one uppercase letter 49 | if !password.chars().any(|c| c.is_uppercase()) { 50 | is_strong = false; 51 | } 52 | 53 | // check if it contains at least one lowercase letter 54 | if !password.chars().any(|c| c.is_lowercase()) { 55 | is_strong = false; 56 | } 57 | 58 | // check if it contains at least one digit 59 | if !password.chars().any(|c| c.is_ascii_digit()) { 60 | is_strong = false; 61 | } 62 | 63 | // check if it contains at least one special character (non-alphanumeric) 64 | if password.chars().all(|c| c.is_alphanumeric()) { 65 | is_strong = false; 66 | } 67 | 68 | if !is_strong { 69 | let error_message: String = "Password too weak.".to_string(); 70 | error!("{}", error_message); 71 | panic!("{}", error_message); 72 | } 73 | password 74 | } 75 | 76 | 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use super::*; 81 | 82 | #[test] 83 | fn test_validate_database_type() { 84 | let db_types: &[&str] = &["STR", "INT", "FLOAT", "BOOL", "JSON"]; 85 | let expected_db_types: &[DatabaseType] = &[DatabaseType::Str, DatabaseType::Int, DatabaseType::Float, DatabaseType::Bool, DatabaseType::Json]; 86 | 87 | for (idx, db_type) in db_types.iter().enumerate() { 88 | assert_eq!(validate_database_type(db_type.to_string()), expected_db_types[idx]); 89 | } 90 | } 91 | 92 | #[test] 93 | #[should_panic(expected = "Invalid database type 'WOOL'. Choose one of: STR, INT, FLOAT, BOOL or JSON.")] 94 | fn test_validate_wrong_database_type() { 95 | validate_database_type("WOOL".to_string()); 96 | } 97 | 98 | #[test] 99 | fn test_validate_correct_password() { 100 | assert_eq!(validate_password("Ottffss8%".to_string()), "Ottffss8%".to_string()); 101 | } 102 | 103 | #[test] 104 | #[should_panic(expected = "Password too weak.")] 105 | fn test_validate_wrong_password() { 106 | validate_password("p".to_string()); 107 | } 108 | 109 | /*#[test] 110 | #[should_panic(expected = "Invalid database type 'WOOL'. Choose one of: STR, INT, FLOAT, BOOL or JSON.")] 111 | fn test_failed_database_type_env() { 112 | std::env::set_var("CACHEW_DB_TYPE", "WOOL"); 113 | get_database_type(None); 114 | } 115 | 116 | #[test] 117 | #[should_panic(expected = "Environment variable 'CACHEW_DB_TYPE' is not set and no 'db-type' flag was provided.")] 118 | fn test_no_database_type_env() { 119 | std::env::remove_var("CACHEW_DB_TYPE"); 120 | get_database_type(None); 121 | }*/ 122 | 123 | } -------------------------------------------------------------------------------- /cachew/src/database.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{BTreeMap, HashMap}; 2 | use std::str::FromStr; 3 | use bincode::{serialize, deserialize}; 4 | use std::ops::Bound::Included; 5 | use std::sync::{Arc, Mutex}; 6 | 7 | use crate::schemas::{KeyValuePair, ValueType, QueryResponseType, QueryRequest, DatabaseType}; 8 | use crate::{database_error}; 9 | use crate::errors::database_errors::{DatabaseErrorType}; 10 | 11 | use std::marker::{Send, Copy}; 12 | use std::fmt::{Debug, Display}; 13 | 14 | 15 | pub struct Database { 16 | pub database_type: DatabaseType, 17 | storage: BTreeMap>, 18 | } 19 | 20 | 21 | impl Database { 22 | 23 | pub fn new(database_type: DatabaseType) -> Self { 24 | let storage: BTreeMap> = BTreeMap::new(); 25 | 26 | Self { 27 | database_type, 28 | storage 29 | } 30 | } 31 | 32 | pub fn check_value_type(&self, value: &ValueType) -> bool { 33 | match self.database_type { 34 | DatabaseType::Str => { 35 | matches!(value, ValueType::Str(_)) 36 | }, 37 | DatabaseType::Int => { 38 | matches!(value, ValueType::Int(_)) 39 | }, 40 | DatabaseType::Float => { 41 | matches!(value, ValueType::Float(_)) 42 | }, 43 | DatabaseType::Bool => { 44 | matches!(value, ValueType::Bool(_)) 45 | }, 46 | DatabaseType::Json => { 47 | matches!(value, ValueType::Json(_)) 48 | } 49 | } 50 | } 51 | 52 | /// Gets a value by its key. 53 | /// 54 | /// # Arguments: 55 | /// * `key`: The query key. 56 | /// 57 | /// # Returns: 58 | /// Either the queried value in a GET_OK enum or an error. 59 | pub fn get(&self, key: &str) -> Result { 60 | if let Some(serialized_value) = self.storage.get(key) { 61 | let deserialized_value: ValueType = deserialize(serialized_value).unwrap(); 62 | return Ok(QueryResponseType::GET_OK(deserialized_value)); 63 | } 64 | 65 | database_error!(DatabaseErrorType::KeyNotFound(key.to_string())) 66 | } 67 | 68 | /// Gets values from a range of keys. 69 | /// 70 | /// # Arguments: 71 | /// * `key_lower`: The lower query key. 72 | /// * `key_upper`: The upper query key. 73 | /// 74 | /// # Returns: 75 | /// Either the queried values in a GET_RANGE_OK enum or an error. 76 | pub fn get_range(&self, key_lower: String, key_upper: String) -> Result { 77 | if key_lower > key_upper { 78 | return database_error!(DatabaseErrorType::InvalidRangeOrder); 79 | } 80 | 81 | let mut values: Vec = Vec::new(); 82 | let range = self.storage.range((Included(key_lower), Included(key_upper))); 83 | for (_, value) in range { 84 | values.push(deserialize(value).unwrap()); 85 | } 86 | 87 | Ok(QueryResponseType::GET_RANGE_OK(values)) 88 | } 89 | 90 | /// Gets values from a list of keys. 91 | /// 92 | /// # Arguments: 93 | /// * `keys`: The a vector of multiple keys. 94 | /// 95 | /// # Returns: 96 | /// Either the queried values in a GET_MANY_OK enum or an error. 97 | pub fn get_many(&self, keys: Vec<&str>) -> Result { 98 | let mut values: Vec = Vec::new(); 99 | for key in keys { 100 | if let Some(serialized_value) = self.storage.get(key) { 101 | let deserialized_value: ValueType = deserialize(serialized_value).unwrap(); 102 | values.push(deserialized_value); 103 | } 104 | else { 105 | return database_error!(DatabaseErrorType::KeyNotFound(key.to_string())); 106 | } 107 | } 108 | 109 | Ok(QueryResponseType::GET_MANY_OK(values)) 110 | } 111 | 112 | /// Deletes a value by its key. 113 | /// 114 | /// # Arguments: 115 | /// * `key`: The query key. 116 | /// 117 | /// # Returns: 118 | /// Either a GET_DEL enum on deletion or an error. 119 | pub fn del(&mut self, key: &str) -> Result { 120 | let _ = self.storage.remove(key); 121 | Ok(QueryResponseType::DEL_OK) 122 | } 123 | 124 | /// Deletes values by a range of keys. 125 | /// 126 | /// # Arguments: 127 | /// * `key_lower`: The lower query key. 128 | /// * `key_upper`: The upper query key. 129 | /// 130 | /// # Returns: 131 | /// Either a DEL_RANGE_OK enum on deletion or an error. 132 | pub fn del_range(&mut self, key_lower: String, key_upper: String) -> Result { 133 | if key_lower > key_upper { 134 | return database_error!(DatabaseErrorType::InvalidRangeOrder); 135 | } 136 | 137 | let keys_to_remove: Vec = self.storage 138 | .range((Included(key_lower), Included(key_upper))) 139 | .map(|(key, _)| key.clone()) 140 | .collect(); 141 | 142 | for key in keys_to_remove { 143 | self.storage.remove(&key); 144 | } 145 | 146 | Ok(QueryResponseType::DEL_RANGE_OK) 147 | } 148 | 149 | /// Deletes values by a list of keys. 150 | /// 151 | /// # Arguments: 152 | /// * `store`: The BTreeMap storing the ordered key-value pairs. 153 | /// * `keys`: The a vector of multiple keys. 154 | /// 155 | /// # Returns: 156 | /// A DEL_MANY_OK enum. 157 | pub fn del_many(&mut self, keys: Vec<&str>) -> Result { 158 | for key in keys { 159 | let _ = self.storage.remove(key); 160 | } 161 | 162 | Ok(QueryResponseType::DEL_MANY_OK) 163 | } 164 | 165 | /// Inserts a new key value pair. 166 | /// 167 | /// # Arguments: 168 | /// * `key`: The new key. 169 | /// * `value`: The value. 170 | /// 171 | /// # Returns: 172 | /// A SET_OK enum. 173 | pub fn set(&mut self, key: &str, value: ValueType) -> Result { 174 | if !self.check_value_type(&value) { 175 | return database_error!(DatabaseErrorType::WrongValueType); 176 | } 177 | 178 | self.storage.insert(key.to_owned(), serialize(&value).unwrap()); 179 | Ok(QueryResponseType::SET_OK) 180 | } 181 | 182 | /// Inserts multiple key value pairs. 183 | /// 184 | /// # Arguments: 185 | /// * `key_value_pairs`: A vector of `key_value_pairs`. 186 | /// 187 | /// # Returns: 188 | /// A SET_OK enum. 189 | pub fn set_many(&mut self, key_value_pairs: Vec) -> Result { 190 | for pair in key_value_pairs { 191 | 192 | if !self.check_value_type(&pair.value) { 193 | return database_error!(DatabaseErrorType::WrongValueType); 194 | } 195 | 196 | self.storage.insert(pair.key.to_owned(), serialize(&pair.value).unwrap()); 197 | } 198 | Ok(QueryResponseType::SET_MANY_OK) 199 | } 200 | 201 | pub fn clear(&mut self) -> Result { 202 | self.storage = BTreeMap::new(); 203 | Ok(QueryResponseType::CLEAR_OK) 204 | } 205 | 206 | pub fn len(&mut self) -> Result { 207 | Ok(QueryResponseType::LEN_OK(self.storage.len())) 208 | } 209 | 210 | pub fn exists(&self, key: &str) -> Result { 211 | Ok(QueryResponseType::EXISTS_OK(self.storage.get(key).is_some())) 212 | } 213 | } 214 | 215 | 216 | 217 | 218 | 219 | 220 | #[cfg(test)] 221 | mod tests { 222 | use crate::database; 223 | use super::*; 224 | 225 | #[test] 226 | fn test_get() { 227 | // get string 228 | let mut database: database::Database = database::Database::new(DatabaseType::Str); 229 | let _ = database.set("key1", ValueType::Str("val1".to_string())); 230 | let response = database.get("key1"); 231 | assert_eq!(response, Ok(database::QueryResponseType::GET_OK(ValueType::Str("val1".to_string())))); 232 | 233 | // get int 234 | let mut database: database::Database = database::Database::new(DatabaseType::Int); 235 | let _ = database.set("key", ValueType::Int(23)); 236 | let response = database.get("key"); 237 | assert_eq!(response, Ok(database::QueryResponseType::GET_OK(ValueType::Int(23)))); 238 | 239 | // get float 240 | let mut database: database::Database = database::Database::new(DatabaseType::Float); 241 | let _ = database.set("key", ValueType::Float(-0.1)); 242 | let response = database.get("key"); 243 | assert_eq!(response, Ok(database::QueryResponseType::GET_OK(ValueType::Float(-0.1)))); 244 | 245 | // get bool 246 | let mut database: database::Database = database::Database::new(DatabaseType::Bool); 247 | let _ = database.set("key", ValueType::Bool(true)); 248 | let response = database.get("key"); 249 | assert_eq!(response, Ok(database::QueryResponseType::GET_OK(ValueType::Bool(true)))); 250 | 251 | // get json 252 | let mut database: database::Database = database::Database::new(DatabaseType::Json); 253 | let _ = database.set("key", ValueType::Json("{key: \"value\"}".to_string())); 254 | let response = database.get("key"); 255 | assert_eq!(response, Ok(database::QueryResponseType::GET_OK(ValueType::Json("{key: \"value\"}".to_string())))); 256 | 257 | // fail to get key 258 | let mut database: database::Database = database::Database::new(DatabaseType::Int); 259 | let _ = database.set("key0", ValueType::Int(23)); 260 | let response = database.get("key2"); 261 | assert_eq!(response, database_error!(DatabaseErrorType::KeyNotFound("key2".to_string()))); 262 | } 263 | 264 | #[test] 265 | fn test_get_many() { 266 | let mut database: database::Database = database::Database::new(DatabaseType::Str); 267 | 268 | // get many 269 | for i in 0..5 { 270 | let _ = database.set(&format!("key{}", i), ValueType::Str(format!("val{}", i))); 271 | } 272 | let response = database.get_many(vec!["key1", "key3", "key4"]); 273 | assert_eq!(response, Ok(QueryResponseType::GET_MANY_OK(vec![ 274 | ValueType::Str("val1".to_string()), 275 | ValueType::Str("val3".to_string()), 276 | ValueType::Str("val4".to_string()) 277 | ]))); 278 | 279 | // fail to get key 280 | for i in 0..5 { 281 | let _ = database.set(&format!("key{}", i), ValueType::Str(format!("val{}", i))); 282 | } 283 | let response = database.get_many(vec!["key4", "key5"]); 284 | assert_eq!(response, database_error!(DatabaseErrorType::KeyNotFound("key5".to_string()))); 285 | } 286 | 287 | #[test] 288 | fn test_get_range() { 289 | let mut database: database::Database = database::Database::new(DatabaseType::Float); 290 | 291 | // get range 292 | for i in 0..5 { 293 | let _ = database.set(&format!("key{}", i), ValueType::Float(0.75f32)); 294 | } 295 | let response = database.get_range("key2".to_owned(), "key5".to_owned()); 296 | assert_eq!(response, Ok(QueryResponseType::GET_RANGE_OK(vec![ 297 | ValueType::Float(0.75f32), 298 | ValueType::Float(0.75f32), 299 | ValueType::Float(0.75f32) 300 | ]))); 301 | 302 | // wrong range order 303 | for i in 0..5 { 304 | let _ = database.set(&format!("key{}", i), ValueType::Int(i)); 305 | } 306 | let response = database.get_range("key5".to_owned(), "key2".to_owned()); 307 | assert_eq!(response, database_error!(DatabaseErrorType::InvalidRangeOrder)); 308 | 309 | // get empty range 310 | let _ = database.set("key1", ValueType::Str("val1".to_owned())); 311 | let response = database.get_range("a".to_owned(), "b".to_owned()); 312 | assert_eq!(response, Ok(QueryResponseType::GET_RANGE_OK(vec![]))); 313 | } 314 | 315 | #[test] 316 | fn test_del() { 317 | let mut database: database::Database = database::Database::new(DatabaseType::Float); 318 | let _ = database.set("key", ValueType::Float(-9.99f32)); 319 | let _ = database.del("key"); 320 | 321 | let response = database.get("key"); 322 | assert_eq!(response, database_error!(DatabaseErrorType::KeyNotFound("key".to_string()))); 323 | } 324 | 325 | #[test] 326 | fn test_del_range() { 327 | let mut database: database::Database = database::Database::new(DatabaseType::Str); 328 | 329 | // delete range 330 | for i in 0..5 { 331 | let _ = database.set(&format!("key{}", i), ValueType::Str(format!("val{}", i))); 332 | } 333 | let _ = database.del_range("key2".to_owned(), "key5".to_owned()); 334 | let response = database.get("key3"); 335 | assert_eq!(response, database_error!(DatabaseErrorType::KeyNotFound("key3".to_string()))); 336 | 337 | // wrong order range 338 | for i in 0..5 { 339 | let _ = database.set(&format!("key{}", i), ValueType::Str(format!("val{}", i))); 340 | } 341 | let response = database.del_range("key5".to_owned(), "key2".to_owned()); 342 | assert_eq!(response, database_error!(DatabaseErrorType::InvalidRangeOrder)); 343 | } 344 | 345 | #[test] 346 | fn test_del_many() { 347 | let mut database: database::Database = database::Database::new(DatabaseType::Int); 348 | 349 | // delete many 350 | for i in 0..5 { 351 | let _ = database.set(&format!("key{}", i), ValueType::Int(i)); 352 | } 353 | let _ = database.del_many(vec!["key1", "key4"]); 354 | let response = database.get("key1"); 355 | assert_eq!(response, database_error!(DatabaseErrorType::KeyNotFound("key1".to_string()))); 356 | } 357 | 358 | #[test] 359 | fn test_set() { 360 | let mut database: database::Database = database::Database::new(DatabaseType::Str); 361 | 362 | // set and get a key to check 363 | let response = database.set("key", ValueType::Str("val".to_string())); 364 | assert_eq!(response, Ok(database::QueryResponseType::SET_OK)); 365 | 366 | let response = database.get("key"); 367 | assert_eq!(response, Ok(database::QueryResponseType::GET_OK(ValueType::Str("val".to_string())))); 368 | 369 | // set wrong value type 370 | let response = database.set("key", ValueType::Int(1)); 371 | assert_eq!(response, database_error!(DatabaseErrorType::WrongValueType)); 372 | } 373 | 374 | #[test] 375 | fn test_set_many() { 376 | let mut database: database::Database = database::Database::new(DatabaseType::Int); 377 | 378 | // set many and get a key to check 379 | let response = database.set_many(vec![ 380 | KeyValuePair { key: "key0".to_owned(), value: ValueType::Int(1) }, 381 | KeyValuePair { key: "key1".to_owned(), value: ValueType::Int(2) }, 382 | KeyValuePair { key: "key2".to_owned(), value: ValueType::Int(3) }, 383 | KeyValuePair { key: "key3".to_owned(), value: ValueType::Int(4) }, 384 | ]); 385 | assert_eq!(response, Ok(database::QueryResponseType::SET_MANY_OK)); 386 | 387 | let response = database.get("key2"); 388 | assert_eq!(response, Ok(database::QueryResponseType::GET_OK(ValueType::Int(3)))); 389 | 390 | // set wrong value type 391 | let response = database.set_many(vec![ 392 | KeyValuePair { key: "key4".to_owned(), value: ValueType::Int(4) }, 393 | KeyValuePair { key: "key5".to_owned(), value: ValueType::Int(5) }, 394 | KeyValuePair { key: "key6".to_owned(), value: ValueType::Float(6.1) }, 395 | KeyValuePair { key: "key7".to_owned(), value: ValueType::Int(7) }, 396 | ]); 397 | assert_eq!(response, database_error!(DatabaseErrorType::WrongValueType)); 398 | } 399 | 400 | #[test] 401 | fn test_clear() { 402 | let mut database: database::Database = database::Database::new(DatabaseType::Str); 403 | 404 | // set and get a key to check 405 | let _ = database.set("key", ValueType::Int(1)); 406 | let response = database.clear(); 407 | assert_eq!(response, Ok(database::QueryResponseType::CLEAR_OK)); 408 | 409 | assert_eq!(database.storage.len(), 0); 410 | } 411 | 412 | #[test] 413 | fn test_len() { 414 | let mut database: database::Database = database::Database::new(DatabaseType::Int); 415 | 416 | let _ = database.set_many(vec![ 417 | KeyValuePair { key: "key1".to_owned(), value: ValueType::Int(1) }, 418 | KeyValuePair { key: "key2".to_owned(), value: ValueType::Int(2) }, 419 | KeyValuePair { key: "key3".to_owned(), value: ValueType::Int(3) }, 420 | ]); 421 | 422 | let response = database.len(); 423 | assert_eq!(response, Ok(database::QueryResponseType::LEN_OK(3))); 424 | 425 | let _ = database.clear(); 426 | let response = database.len(); 427 | assert_eq!(response, Ok(database::QueryResponseType::LEN_OK(0))); 428 | } 429 | 430 | #[test] 431 | fn test_exists() { 432 | let mut database: database::Database = database::Database::new(DatabaseType::Float); 433 | 434 | let _ = database.set("key", ValueType::Float(-99.99)); 435 | 436 | let response = database.exists("key"); 437 | assert_eq!(response, Ok(database::QueryResponseType::EXISTS_OK(true))); 438 | 439 | let response = database.exists("notAKey"); 440 | assert_eq!(response, Ok(database::QueryResponseType::EXISTS_OK(false))); 441 | } 442 | 443 | } 444 | -------------------------------------------------------------------------------- /cachew/src/errors/authentication_errors.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt; 3 | 4 | 5 | #[derive(Debug)] 6 | pub enum AuthenticationErrorType { 7 | NotAuthenticated, 8 | AuthenticationFailed, 9 | //AlreadyAuthenticated 10 | } 11 | 12 | 13 | #[derive(Debug)] 14 | pub struct AuthenticationError { 15 | pub error_type: AuthenticationErrorType 16 | } 17 | 18 | 19 | impl Error for AuthenticationError {} 20 | 21 | impl fmt::Display for AuthenticationError { 22 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 23 | write!(f, "AuthenticationError "); 24 | match &self.error_type { 25 | AuthenticationErrorType::NotAuthenticated => write!(f, "'notAuthenticated': Please authenticate before executing queries."), 26 | AuthenticationErrorType::AuthenticationFailed => write!(f, "'authenticationFailed': Wrong password."), 27 | //AuthenticationErrorType::AlreadyAuthenticated => write!(f, "'alreadyAuthenticated': You are already authenticated."), 28 | } 29 | } 30 | } 31 | 32 | #[macro_export] 33 | macro_rules! auth_error { 34 | ($err_type:expr) => { 35 | return Err( 36 | (Box::new($crate::errors::authentication_errors::AuthenticationError { 37 | error_type: $err_type, 38 | }) as Box).to_string() 39 | ) 40 | }; 41 | } 42 | 43 | -------------------------------------------------------------------------------- /cachew/src/errors/database_errors.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt; 3 | 4 | 5 | #[derive(Debug)] 6 | pub enum DatabaseErrorType { 7 | KeyNotFound(String), 8 | InvalidRangeOrder, 9 | WrongValueType 10 | } 11 | 12 | 13 | #[derive(Debug)] 14 | pub struct DatabaseError { 15 | pub error_type: DatabaseErrorType 16 | } 17 | 18 | 19 | impl Error for DatabaseError {} 20 | 21 | impl fmt::Display for DatabaseError { 22 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 23 | write!(f, "DatabaseError "); 24 | match &self.error_type { 25 | DatabaseErrorType::KeyNotFound(key) => write!(f, "'keyNotFound': The key '{}' doesn't exist.", key), 26 | DatabaseErrorType::InvalidRangeOrder => write!(f, "'invalidRangeOrder': The lower key is bigger than the upper key."), 27 | DatabaseErrorType::WrongValueType => write!(f, "'wrongValueType': The value doesn't match the database type."), 28 | } 29 | } 30 | } 31 | 32 | #[macro_export] 33 | macro_rules! database_error { 34 | ($err_type:expr) => { 35 | Err( 36 | (Box::new($crate::errors::database_errors::DatabaseError { 37 | error_type: $err_type, 38 | }) as Box).to_string() 39 | ) 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /cachew/src/errors/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod parser_errors; 2 | pub mod database_errors; 3 | pub mod protocol_errors; 4 | pub mod authentication_errors; -------------------------------------------------------------------------------- /cachew/src/errors/parser_errors.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt; 3 | 4 | 5 | #[derive(Debug)] 6 | pub enum ParserErrorType { 7 | InvalidRange(usize), 8 | UnexpectedCharacter, 9 | InvalidKeyValuePair(usize), 10 | UnknownQueryOperation(String), 11 | WrongValueType(String), 12 | WrongAuthentication, 13 | StringQuotesNotFound, 14 | UnexpectedParameters(String), 15 | UnescapedDoubleQuote 16 | } 17 | 18 | #[derive(Debug)] 19 | pub struct ParserError { 20 | pub error_type: ParserErrorType 21 | } 22 | 23 | 24 | impl Error for ParserError {} 25 | 26 | impl fmt::Display for ParserError { 27 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 28 | write!(f, "ParserError "); 29 | match &self.error_type { 30 | ParserErrorType::InvalidRange(num) => write!(f, "'invalidRange': Expected two keys got {}.", num), 31 | ParserErrorType::UnexpectedCharacter => write!(f, "'unexpectedCharacter': Spaces, commata and slashes are not allowed in keys unless it is in quotes."), 32 | ParserErrorType::InvalidKeyValuePair(num) => write!(f, "'invalidKeyValuePair': Expected two parameters (key and value), found {}.", num), 33 | ParserErrorType::UnknownQueryOperation(op) => write!(f, "'unknownQueryOperation': Query '{}' not recognized.", op), 34 | ParserErrorType::WrongValueType(db_type) => write!(f, "'wrongValueType': The value doesn't match the database type '{}'.", db_type), 35 | ParserErrorType::WrongAuthentication => write!(f, "'wrongAuthentication': Couldn't read password. Expected: 'AUTH '."), 36 | ParserErrorType::StringQuotesNotFound => write!(f, "'stringQuotesNotFound': Expected double quotes around strings."), 37 | ParserErrorType::UnexpectedParameters(parameters) => write!(f, "'unexpectedParameters': The command '{}' doesn't take any parameters.", parameters), 38 | ParserErrorType::UnescapedDoubleQuote => write!(f, "'unescapedDoubleQuote': Double quotes must be escaped."), 39 | } 40 | } 41 | } 42 | 43 | #[macro_export] 44 | macro_rules! parser_error { 45 | ($err_type:expr) => { 46 | Err( 47 | (Box::new($crate::errors::parser_errors::ParserError { 48 | error_type: $err_type, 49 | }) as Box).to_string() 50 | ) 51 | }; 52 | } -------------------------------------------------------------------------------- /cachew/src/errors/protocol_errors.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt; 3 | 4 | 5 | #[derive(Debug)] 6 | pub enum ProtocolErrorType { 7 | EmptyRequest, 8 | StartMarkerNotFound(String), 9 | EndMarkerNotFound(String), 10 | //NoRequestBody 11 | } 12 | 13 | 14 | #[derive(Debug)] 15 | pub struct ProtocolError { 16 | pub error_type: ProtocolErrorType 17 | } 18 | 19 | 20 | impl Error for ProtocolError {} 21 | 22 | impl fmt::Display for ProtocolError { 23 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 24 | write!(f, "ProtocolError "); 25 | match &self.error_type { 26 | ProtocolErrorType::EmptyRequest => write!(f, "'emptyRequest': Can't process empty request."), 27 | ProtocolErrorType::StartMarkerNotFound(expected_marker) => write!(f, "'startMarkerNotFound': Expected request to start with '{}'.", expected_marker), 28 | ProtocolErrorType::EndMarkerNotFound(expected_marker) => write!(f, "'endMarkerNotFound': Expected request to end with '{}'.", expected_marker.replace('\n', "\\n")), 29 | //ProtocolErrorType::NoRequestBody => write!(f, "'noRequestBody': No request body found."), 30 | } 31 | } 32 | } 33 | 34 | #[macro_export] 35 | macro_rules! protocol_error { 36 | ($err_type:expr) => { 37 | return Err( 38 | (Box::new($crate::errors::protocol_errors::ProtocolError { 39 | error_type: $err_type, 40 | }) as Box).to_string() 41 | ) 42 | }; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /cachew/src/main.rs: -------------------------------------------------------------------------------- 1 | mod parser; 2 | mod server; 3 | mod schemas; 4 | mod database; 5 | mod response; 6 | mod state; 7 | mod cli; 8 | 9 | #[macro_use] 10 | mod errors; 11 | 12 | use state::State; 13 | use cli::arguments::{CachewDbArgs, get_cachew_db_args}; 14 | use log::{info}; 15 | 16 | 17 | #[tokio::main] 18 | async fn main() { 19 | std::env::set_var("RUST_LOG", "debug"); 20 | std::env::set_var("RUST_BACKTRACE", "1"); 21 | env_logger::init(); 22 | 23 | info!("Starting up CachewDB database."); 24 | 25 | let cachew_db_args: CachewDbArgs = get_cachew_db_args(); 26 | 27 | info!("Initializing b-tree storage of type '{}'.", cachew_db_args.database_type); 28 | let state: State = State::new(cachew_db_args.database_type, cachew_db_args.password); 29 | 30 | server::serve(state, &cachew_db_args.host, &cachew_db_args.port).await; 31 | } 32 | -------------------------------------------------------------------------------- /cachew/src/parser.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | 3 | use crate::schemas::{QueryRequest, KeyValuePair, ValueType, DatabaseType}; 4 | use crate::{parser_error}; 5 | use crate::errors::parser_errors::{ParserErrorType}; 6 | 7 | 8 | 9 | /// Parses a string expected to be consist of two ordered keys seperated by space. 10 | /// 11 | /// # Arguments: 12 | /// * `query_keys`: A string containing the two keys. 13 | /// 14 | /// # Returns: 15 | /// `None` if the string didn't contain two valid keys or the left key is smaller than the right one. 16 | /// Else, a vector of length two containing the keys. 17 | fn parse_ranged_keys(query_keys: &str) -> Result, String> { 18 | let tokens: Vec<&str> = split_at_delimiter(query_keys, ' '); 19 | if tokens.len() != 2 { 20 | return parser_error!(ParserErrorType::InvalidRange(tokens.len())); 21 | } 22 | 23 | let lower_key = match validate_key(tokens[0]) { 24 | Ok(key) => key, 25 | Err(error) => return Err(error) 26 | }; 27 | 28 | let upper_key = match validate_key(tokens[1]) { 29 | Ok(key) => key, 30 | Err(error) => return Err(error) 31 | }; 32 | 33 | Ok(vec![lower_key, upper_key]) 34 | } 35 | 36 | /// Parses a string expected to be consist of many keys (>1) seperated by space. 37 | /// 38 | /// # Arguments: 39 | /// * `query_keys`: A string containing the keys. 40 | /// 41 | /// # Returns: 42 | /// `None` if the string didn't contain valid keys. 43 | /// Else, a vector containing the keys. 44 | fn parse_many_keys(query_keys: &str) -> Result, String> { 45 | let mut tokens: Vec<&str> = split_at_delimiter(query_keys, ' '); 46 | 47 | tokens.iter_mut().try_for_each(|query_keys| { 48 | match validate_key(query_keys) { 49 | Ok(validated_key) => { 50 | *query_keys = validated_key; 51 | Ok(()) 52 | } 53 | Err(error) => Err(error), 54 | } 55 | })?; 56 | 57 | 58 | Ok(tokens) 59 | } 60 | 61 | 62 | fn validate_key(key: &str) -> Result<&str, String> { 63 | let forbidden_cars: [char; 2] = [',', '/']; 64 | 65 | // check if the key consists of multiple entities, ie. there is a spaces in the key but the key is not within quotes 66 | if split_at_delimiter(key, ' ').len() > 1 { 67 | println!("{:?}", split_at_delimiter(key, ' ')); 68 | return parser_error!(ParserErrorType::UnexpectedCharacter); 69 | } 70 | 71 | // remove quotes if there are any 72 | let mut key_norm: &str = key; 73 | if key.starts_with('"') && key.ends_with('"') { 74 | key_norm = key.strip_prefix('"').unwrap_or(key).strip_suffix('"').unwrap_or(key); 75 | } 76 | // if there aren't any quotes, check if the key contains illegal characters 77 | else if key.chars().any(|c| forbidden_cars.contains(&c)) { 78 | return parser_error!(ParserErrorType::UnexpectedCharacter); 79 | } 80 | 81 | // check if the key is empty 82 | if key_norm.is_empty() { 83 | return parser_error!(ParserErrorType::UnexpectedCharacter); 84 | } 85 | 86 | // check for unescaped double quotes 87 | let escaped_quote_pattern = r#"(^|[^\\])""#; 88 | let re = Regex::new(escaped_quote_pattern).unwrap(); 89 | if re.is_match(key_norm) { 90 | return parser_error!(ParserErrorType::UnescapedDoubleQuote); 91 | } 92 | 93 | Ok(key_norm) 94 | } 95 | 96 | 97 | /// Splits a string at a delimiter, unless the delimiter is in a substring enclosed by quotes. 98 | /// 99 | /// # Arguments: 100 | /// * `string`: The string to split. 101 | /// * `delimiter`: The delimiter on which to split. 102 | /// 103 | /// # Returns: 104 | /// A vector containing the parts of the string (with quotes removed). 105 | fn split_at_delimiter(string: &str, delimiter: char) -> Vec<&str> { 106 | let mut parts: Vec<&str> = Vec::new(); 107 | let mut current_part = 0; 108 | let mut inside_quotes = false; 109 | let mut prev_char: Option = None; 110 | 111 | for (index, ch) in string.char_indices() { 112 | match ch { 113 | '"' => { 114 | if prev_char != Some('\\') { 115 | inside_quotes = !inside_quotes; 116 | } 117 | } 118 | ch if ch == delimiter && !inside_quotes => { 119 | let part = &string[current_part..index]; 120 | parts.push(part.trim_matches(|c| c == ' ')); 121 | current_part = index + ch.len_utf8(); 122 | } 123 | _ => {} 124 | } 125 | prev_char = Some(ch); 126 | } 127 | 128 | let part = &string[current_part..]; 129 | parts.push(part.trim_matches(|c| c == ' ')); 130 | 131 | parts 132 | } 133 | 134 | /// Parses the parameters of a GET query. 135 | /// 136 | /// # Arguments: 137 | /// * `query`: A string containing the parameters of the query, e.g if the query was "GET key" or "GET RANGE a b" the the parameters are everything after "GET ". 138 | /// 139 | /// # Returns: 140 | /// An instance of `QueryRequest`, variants: GET, GET_RANGE, GET_MANY or ERROR (if the parse failed). 141 | fn parse_get(query: &str) -> Result { 142 | if query.starts_with("RANGE ") { 143 | match parse_ranged_keys(query.strip_prefix("RANGE ").unwrap()) { 144 | Ok(range_keys) => return Ok(QueryRequest::GET_RANGE { key_lower: range_keys[0].to_owned(), key_upper: range_keys[1].to_owned() }), 145 | Err(error) => return Err(error), 146 | } 147 | } 148 | 149 | if query.starts_with("MANY ") { 150 | match parse_many_keys(query.strip_prefix("MANY ").unwrap()) { 151 | Ok(keys) => return Ok(QueryRequest::GET_MANY(keys)), 152 | Err(error) => return Err(error), 153 | } 154 | } 155 | 156 | let key = match validate_key(query) { 157 | Ok(key) => key, 158 | Err(error) => return Err(error) 159 | }; 160 | 161 | Ok(QueryRequest::GET(key.to_owned())) 162 | } 163 | 164 | 165 | /// Parses the parameters of a DEL query. 166 | /// 167 | /// # Arguments: 168 | /// * `query`: A string containing the parameters of the query, e.g if the query was "DEL key" or "DEL RANGE a b" the the parameters are everything after "DEL ". 169 | /// 170 | /// # Returns: 171 | /// An instance of `QueryRequest`, variants: DEL, DEL_RANGE, DEL_MANY or ERROR (if the parse failed). 172 | fn parse_del(query: &str) -> Result { 173 | if query.starts_with("RANGE ") { 174 | match parse_ranged_keys(query.strip_prefix("RANGE ").unwrap()) { 175 | Ok(range_keys) => return Ok(QueryRequest::DEL_RANGE { key_lower: range_keys[0].to_owned(), key_upper: range_keys[1].to_owned() }), 176 | Err(error) => return Err(error), 177 | } 178 | } 179 | 180 | if query.starts_with("MANY ") { 181 | match parse_many_keys(query.strip_prefix("MANY ").unwrap()) { 182 | Ok(keys) => return Ok(QueryRequest::DEL_MANY(keys)), 183 | Err(error) => return Err(error), 184 | } 185 | } 186 | 187 | let key = match validate_key(query) { 188 | Ok(key) => key, 189 | Err(error) => return Err(error) 190 | }; 191 | 192 | Ok(QueryRequest::DEL(key.to_owned())) 193 | } 194 | 195 | 196 | fn parse_set_value(value_query_parameter: &str, database_type: &DatabaseType) -> Result { 197 | match database_type { 198 | DatabaseType::Str => { 199 | if !(value_query_parameter.starts_with('"') && value_query_parameter.ends_with('"') ) { 200 | return parser_error!(ParserErrorType::StringQuotesNotFound) 201 | } 202 | 203 | let value = value_query_parameter.strip_prefix('"').unwrap().strip_suffix('"').unwrap(); 204 | 205 | // check for unescaped double quotes 206 | let escaped_quote_pattern = r#"([^\\])""#; 207 | let re = Regex::new(escaped_quote_pattern).unwrap(); 208 | if re.is_match(value) { 209 | return parser_error!(ParserErrorType::UnescapedDoubleQuote); 210 | } 211 | 212 | let parsed_value: String = match value.parse::() { 213 | Ok(parsed) => parsed, 214 | Err(_) => return parser_error!(ParserErrorType::WrongValueType(database_type.to_string())) 215 | }; 216 | Ok(ValueType::Str(parsed_value)) 217 | } 218 | DatabaseType::Int => { 219 | let parsed_value: i32 = match value_query_parameter.parse::() { 220 | Ok(parsed) => parsed, 221 | Err(_) => return parser_error!(ParserErrorType::WrongValueType(database_type.to_string())) 222 | }; 223 | Ok(ValueType::Int(parsed_value)) 224 | }, 225 | DatabaseType::Float => { 226 | let parsed_value: f32 = match value_query_parameter.parse::() { 227 | Ok(parsed) => parsed, 228 | Err(_) => return parser_error!(ParserErrorType::WrongValueType(database_type.to_string())) 229 | }; 230 | Ok(ValueType::Float(parsed_value)) 231 | }, 232 | DatabaseType::Bool => { 233 | let parsed_value: bool = match value_query_parameter.parse::() { 234 | Ok(parsed) => parsed, 235 | Err(_) => return parser_error!(ParserErrorType::WrongValueType(database_type.to_string())) 236 | }; 237 | Ok(ValueType::Bool(parsed_value)) 238 | }, 239 | DatabaseType::Json => { 240 | if !(value_query_parameter.starts_with('"') && value_query_parameter.ends_with('"') ) { 241 | return parser_error!(ParserErrorType::StringQuotesNotFound) 242 | } 243 | 244 | let value = value_query_parameter.strip_prefix('"').unwrap().strip_suffix('"').unwrap(); 245 | 246 | let escaped_quote_pattern = r#"([^\\])""#; 247 | let re = Regex::new(escaped_quote_pattern).unwrap(); 248 | if re.is_match(value) { 249 | return parser_error!(ParserErrorType::UnescapedDoubleQuote); 250 | } 251 | 252 | let parsed_value: String = match value.parse::() { 253 | Ok(parsed) => parsed, 254 | Err(_) => return parser_error!(ParserErrorType::WrongValueType(database_type.to_string())) 255 | }; 256 | Ok(ValueType::Json(parsed_value)) 257 | } 258 | } 259 | 260 | } 261 | 262 | 263 | fn parse_set<'a>(query: &'a str, database_type: &DatabaseType) -> Result, String> { 264 | if query.starts_with("MANY ") { 265 | let key_value_pairs: Vec<&str> = split_at_delimiter(query.strip_prefix("MANY ").unwrap(), ','); 266 | 267 | let mut parsed_pairs: Vec = vec![]; 268 | for pair in key_value_pairs { 269 | let parameters: Vec<&str> = split_at_delimiter(pair, ' '); 270 | 271 | if parameters.len() != 2 { 272 | return parser_error!(ParserErrorType::InvalidKeyValuePair(parameters.len())); 273 | } 274 | 275 | let key = match validate_key(parameters[0]) { 276 | Ok(key) => key, 277 | Err(error) => return Err(error) 278 | }; 279 | 280 | let parsed_value: Result = parse_set_value(parameters[1], database_type); 281 | match parsed_value { 282 | Ok(value) => parsed_pairs.push(KeyValuePair { key: key.to_owned(), value}), 283 | Err(err) => return Err(err), 284 | } 285 | } 286 | 287 | return Ok(QueryRequest::SET_MANY(parsed_pairs)); 288 | } 289 | 290 | // check if the query consists just of a key and value 291 | let parameters: Vec<&str> = split_at_delimiter(query, ' '); 292 | if parameters.len() != 2 { 293 | return parser_error!(ParserErrorType::InvalidKeyValuePair(parameters.len())); 294 | } 295 | 296 | let key = match validate_key(parameters[0]) { 297 | Ok(key) => key, 298 | Err(error) => return Err(error) 299 | }; 300 | 301 | // parse value into the right value type 302 | let parsed_value: Result = parse_set_value(parameters[1], database_type); 303 | match parsed_value { 304 | Ok(value) => Ok(QueryRequest::SET(KeyValuePair { key: key.to_owned(), value})), 305 | Err(err) => Err(err), 306 | } 307 | } 308 | 309 | 310 | fn parse_auth(password: &str) -> Result { 311 | if password.contains(' ') { 312 | return parser_error!(ParserErrorType::WrongAuthentication); 313 | } 314 | 315 | return Ok(QueryRequest::AUTH(password.to_owned())); 316 | } 317 | 318 | 319 | fn parse_exists(query: &str) -> Result { 320 | let key = match validate_key(query) { 321 | Ok(key) => key, 322 | Err(error) => return Err(error) 323 | }; 324 | 325 | return Ok(QueryRequest::EXISTS(key.to_owned())); 326 | } 327 | 328 | 329 | fn parse_single_command<'a>(request: &'a str, expected_command: &'a str, query_request: QueryRequest<'a>) -> Result, String> { 330 | if request.len() > expected_command.len() { 331 | return parser_error!(ParserErrorType::UnexpectedParameters(expected_command.to_string())); 332 | } 333 | Ok(query_request) 334 | } 335 | 336 | 337 | /// Parses a query string into a QueryRequest. 338 | /// 339 | /// # Arguments: 340 | /// * `query`: The query, e.g "GET key0". 341 | /// 342 | /// # Returns: 343 | /// An instance of `QueryRequest`, variants: GET, GET_RANGE, GET_MANY, SET, DEL, DEL_RANGE, DEL_MANY, or ERROR (if the parse failed). 344 | pub fn parse<'a>(request: &'a str, database_type: &DatabaseType) -> Result, String> { 345 | if request.starts_with("GET ") { 346 | return parse_get(request.strip_prefix("GET ").unwrap()); 347 | } 348 | else if request.starts_with("DEL ") { 349 | return parse_del(request.strip_prefix("DEL ").unwrap()); 350 | } 351 | else if request.starts_with("SET ") { 352 | return parse_set(request.strip_prefix("SET ").unwrap(), database_type); 353 | } 354 | else if request.starts_with("AUTH ") { 355 | return parse_auth(request.strip_prefix("AUTH ").unwrap()); 356 | } 357 | else if request.starts_with("CLEAR") { 358 | return parse_single_command(request, "CLEAR", QueryRequest::CLEAR); 359 | } 360 | else if request.starts_with("LEN") { 361 | return parse_single_command(request, "LEN", QueryRequest::LEN); 362 | } 363 | else if request.starts_with("PING") { 364 | return parse_single_command(request, "PING", QueryRequest::PING); 365 | } 366 | else if request.starts_with("EXISTS ") { 367 | return parse_exists(request.strip_prefix("EXISTS ").unwrap()); 368 | } 369 | else if request.starts_with("SHUTDOWN") { 370 | return parse_single_command(request, "SHUTDOWN", QueryRequest::SHUTDOWN); 371 | } 372 | 373 | parser_error!(ParserErrorType::UnknownQueryOperation(request.to_string())) 374 | } 375 | 376 | 377 | 378 | #[cfg(test)] 379 | mod tests { 380 | use std::vec; 381 | 382 | use super::*; 383 | 384 | // Unit tests for the `parse_get` function: 385 | 386 | #[test] 387 | fn test_validate_key() { 388 | let key = validate_key("\"key,/ one\""); 389 | assert_eq!(key, Ok("key,/ one")); 390 | 391 | let key = validate_key("my\\\"key"); 392 | assert_eq!(key, Ok("my\\\"key")); 393 | 394 | let key = validate_key("my,key"); 395 | assert_eq!(key, parser_error!(ParserErrorType::UnexpectedCharacter)); 396 | 397 | let key = validate_key("\"key\"key\"key\""); 398 | assert_eq!(key, parser_error!(ParserErrorType::UnescapedDoubleQuote)); 399 | 400 | let key = validate_key("\"key"); 401 | assert_eq!(key, parser_error!(ParserErrorType::UnescapedDoubleQuote)); 402 | 403 | let key = validate_key("key\""); 404 | assert_eq!(key, parser_error!(ParserErrorType::UnescapedDoubleQuote)); 405 | } 406 | 407 | #[test] 408 | fn test_parse_get() { 409 | let get_query = parse_get("key"); 410 | assert_eq!(get_query, Ok(QueryRequest::GET("key".to_string()))); 411 | 412 | let get_query = parse_get("\"key 1\""); 413 | assert_eq!(get_query, Ok(QueryRequest::GET("key 1".to_string()))); 414 | 415 | let get_query = parse_get("key0 key1"); 416 | assert_eq!(get_query, parser_error!(ParserErrorType::UnexpectedCharacter)); 417 | } 418 | 419 | #[test] 420 | fn test_parse_get_range() { 421 | let get_range_query = parse_get("RANGE key0 key1"); 422 | assert_eq!(get_range_query, Ok(QueryRequest::GET_RANGE { key_lower: "key0".to_string(), key_upper: "key1".to_string() })); 423 | 424 | let get_range_query = parse_get("RANGE \"key / 1\" \"key / 2\""); 425 | assert_eq!(get_range_query, Ok(QueryRequest::GET_RANGE { key_lower: "key / 1".to_string(), key_upper: "key / 2".to_string() })); 426 | 427 | let get_range_query = parse_get("RANGE key0"); 428 | assert_eq!(get_range_query, parser_error!(ParserErrorType::InvalidRange(1))); 429 | } 430 | 431 | #[test] 432 | fn test_parse_get_many() { 433 | let get_query = parse_get("MANY key0 key1 key2"); 434 | assert_eq!(get_query, Ok(QueryRequest::GET_MANY(vec!["key0", "key1", "key2"]))); 435 | 436 | let get_query = parse_get("MANY \"key,1\" \"key 2\" \"key/3\" \"key \\\"4\\\"\""); 437 | assert_eq!(get_query, Ok(QueryRequest::GET_MANY(vec!["key,1", "key 2", "key/3", "key \\\"4\\\""]))); 438 | } 439 | 440 | // Unit tests for the `parse_ranged_keys` function: 441 | 442 | #[test] 443 | fn test_parse_ranged_keys() { 444 | let range_keys = parse_ranged_keys("key0 key1"); 445 | assert_eq!(range_keys, Ok(vec!["key0", "key1"])); 446 | 447 | let range_keys = parse_ranged_keys("key0 key1 key2"); 448 | assert_eq!(range_keys, parser_error!(ParserErrorType::InvalidRange(3))); 449 | } 450 | 451 | // Unit tests for the `parse_many_keys` function: 452 | 453 | #[test] 454 | fn test_parse_many_keys() { 455 | let range_keys = parse_many_keys("key0 key1 02=2?%3"); 456 | assert_eq!(range_keys, Ok(vec!["key0", "key1", "02=2?%3"])); 457 | } 458 | 459 | // Unit tests for the `parse_del` function: 460 | #[test] 461 | fn test_parse_del() { 462 | let del_query = parse_del("key"); 463 | assert_eq!(del_query, Ok(QueryRequest::DEL("key".to_string()))); 464 | 465 | let del_query = parse_del("\"key one\""); 466 | assert_eq!(del_query, Ok(QueryRequest::DEL("key one".to_string()))); 467 | 468 | let del_query = parse_del("key0 key1"); 469 | assert_eq!(del_query, parser_error!(ParserErrorType::UnexpectedCharacter)) 470 | } 471 | 472 | #[test] 473 | fn test_parse_del_range() { 474 | let del_query = parse_del("RANGE \"key0\" key1"); 475 | assert_eq!(del_query, Ok(QueryRequest::DEL_RANGE { key_lower: "key0".to_string(), key_upper: "key1".to_string() })); 476 | 477 | let del_query = parse_del("RANGE key0"); 478 | assert_eq!(del_query, parser_error!(ParserErrorType::InvalidRange(1))); 479 | } 480 | 481 | #[test] 482 | fn test_parse_del_many() { 483 | let del_query = parse_del("MANY key0 key1 key2"); 484 | assert_eq!(del_query, Ok(QueryRequest::DEL_MANY(vec!["key0", "key1", "key2"]))); 485 | } 486 | 487 | // Unit tests for the `parse_set` function: 488 | #[test] 489 | fn test_parse_set() { 490 | let set_query = parse_set("key \"value\"", &DatabaseType::Str); 491 | assert_eq!(set_query, Ok(QueryRequest::SET(KeyValuePair { key: "key".to_owned(), value: ValueType::Str("value".to_owned()) }))); 492 | 493 | let set_query = parse_set("\"string key\" \"value\"", &DatabaseType::Str); 494 | assert_eq!(set_query, Ok(QueryRequest::SET(KeyValuePair { key: "string key".to_owned(), value: ValueType::Str("value".to_owned()) }))); 495 | 496 | let set_query = parse_set("key \"hello world\"", &DatabaseType::Str); 497 | assert_eq!(set_query, Ok(QueryRequest::SET(KeyValuePair { key: "key".to_owned(), value: ValueType::Str("hello world".to_owned()) }))); 498 | 499 | let set_query = parse_set("key 1", &DatabaseType::Int); 500 | assert_eq!(set_query, Ok(QueryRequest::SET(KeyValuePair { key: "key".to_owned(), value: ValueType::Int(1) }))); 501 | 502 | let set_query = parse_set("key 0.95", &DatabaseType::Float); 503 | assert_eq!(set_query, Ok(QueryRequest::SET(KeyValuePair { key: "key".to_owned(), value: ValueType::Float(0.95) }))); 504 | 505 | let set_query = parse_set("key true", &DatabaseType::Bool); 506 | assert_eq!(set_query, Ok(QueryRequest::SET(KeyValuePair { key: "key".to_owned(), value: ValueType::Bool(true) }))); 507 | 508 | let set_query = parse_set("key false", &DatabaseType::Bool); 509 | assert_eq!(set_query, Ok(QueryRequest::SET(KeyValuePair { key: "key".to_owned(), value: ValueType::Bool(false) }))); 510 | 511 | let set_query = parse_set("key \"{key1: 10, key2: 20}\"", &DatabaseType::Json); 512 | assert_eq!(set_query, Ok(QueryRequest::SET(KeyValuePair { key: "key".to_owned(), value: ValueType::Json("{key1: 10, key2: 20}".to_owned()) }))); 513 | 514 | // test escaped quotes 515 | let set_query = parse_set("key \"name: \\\"ANON\\\"\"",&DatabaseType::Str); 516 | assert_eq!(set_query, Ok(QueryRequest::SET(KeyValuePair { key: "key".to_owned(), value: ValueType::Str("name: \\\"ANON\\\"".to_owned()) }))); 517 | 518 | let set_query = parse_set("key \"name: \\\"escpaced quotes\\\" another one\\\"\"",&DatabaseType::Str); 519 | assert_eq!(set_query, Ok(QueryRequest::SET(KeyValuePair { key: "key".to_owned(), value: ValueType::Str("name: \\\"escpaced quotes\\\" another one\\\"".to_owned()) }))); 520 | 521 | let set_query = parse_set("key \"\"",&DatabaseType::Str); 522 | assert_eq!(set_query, Ok(QueryRequest::SET(KeyValuePair { key: "key".to_owned(), value: ValueType::Str("".to_owned()) }))); 523 | 524 | let set_query = parse_set("key \"val0\" \"val1\"", &DatabaseType::Str); 525 | assert_eq!(set_query, parser_error!(ParserErrorType::InvalidKeyValuePair(3))); 526 | 527 | let set_query = parse_set("key \"val\"ue\"", &DatabaseType::Json); 528 | assert_eq!(set_query, parser_error!(ParserErrorType::UnescapedDoubleQuote)); 529 | 530 | let set_query = parse_set("key value", &DatabaseType::Str); 531 | assert_eq!(set_query, parser_error!(ParserErrorType::StringQuotesNotFound)); 532 | 533 | let set_query = parse_set("key \"not\"allowed\"",&DatabaseType::Str); 534 | assert_eq!(set_query, parser_error!(ParserErrorType::UnescapedDoubleQuote)); 535 | 536 | let set_query = parse_set("\"\" \"value but empty key\"",&DatabaseType::Str); 537 | assert_eq!(set_query, parser_error!(ParserErrorType::UnexpectedCharacter)); 538 | 539 | let database_type: DatabaseType = DatabaseType::Float; 540 | let set_query = parse_set("MANY key notAFloat", &database_type); 541 | assert_eq!(set_query, parser_error!(ParserErrorType::WrongValueType(database_type.to_string()))); 542 | 543 | 544 | 545 | 546 | } 547 | 548 | #[test] 549 | fn test_parse_set_many() { 550 | let set_query = parse_set("MANY key0 \"val0\", key1 \"val1\" , key2 \"val2\",key3 \"val3\"", &DatabaseType::Str); 551 | assert_eq!(set_query, Ok(QueryRequest::SET_MANY(vec![ 552 | KeyValuePair { key: "key0".to_owned(), value: ValueType::Str("val0".to_owned()) }, 553 | KeyValuePair { key: "key1".to_owned(), value: ValueType::Str("val1".to_owned()) }, 554 | KeyValuePair { key: "key2".to_owned(), value: ValueType::Str("val2".to_owned()) }, 555 | KeyValuePair { key: "key3".to_owned(), value: ValueType::Str("val3".to_owned()) }, 556 | ]))); 557 | 558 | let set_query = parse_set("MANY key0 1, key1 22, key2 -22, key3 1000", &DatabaseType::Int); 559 | assert_eq!(set_query, Ok(QueryRequest::SET_MANY(vec![ 560 | KeyValuePair { key: "key0".to_owned(), value: ValueType::Int(1) }, 561 | KeyValuePair { key: "key1".to_owned(), value: ValueType::Int(22) }, 562 | KeyValuePair { key: "key2".to_owned(), value: ValueType::Int(-22) }, 563 | KeyValuePair { key: "key3".to_owned(), value: ValueType::Int(1000) }, 564 | ]))); 565 | 566 | let set_query = parse_set("MANY key0 \"val0\", key1,", &DatabaseType::Str); 567 | assert_eq!(set_query, parser_error!(ParserErrorType::InvalidKeyValuePair(1))); 568 | 569 | let set_query = parse_set("MANY key0 \"val0\", key1 \"hello, world\"", &DatabaseType::Str); 570 | assert_eq!(set_query, Ok(QueryRequest::SET_MANY(vec![ 571 | KeyValuePair { key: "key0".to_owned(), value: ValueType::Str("val0".to_string()) }, 572 | KeyValuePair { key: "key1".to_owned(), value: ValueType::Str("hello, world".to_string()) } 573 | ]))); 574 | 575 | } 576 | 577 | // Unit tests for the `parse_auth` function: 578 | 579 | #[test] 580 | fn test_parse_auth() { 581 | let auth_request = parse_auth("password123"); 582 | assert_eq!(auth_request, Ok(QueryRequest::AUTH("password123".to_string()))); 583 | 584 | let failed_auth_request = parse_auth("pass word 123"); 585 | assert_eq!(failed_auth_request, parser_error!(ParserErrorType::WrongAuthentication)); 586 | } 587 | 588 | // Unit tests for the `parse_single_command` function: 589 | 590 | #[test] 591 | fn test_parse_single_command() { 592 | let clear_request = parse_single_command("CLEAR", "CLEAR", QueryRequest::CLEAR); 593 | assert_eq!(clear_request, Ok(QueryRequest::CLEAR)); 594 | 595 | let len_request = parse_single_command("LEN", "LEN", QueryRequest::LEN); 596 | assert_eq!(len_request, Ok(QueryRequest::LEN)); 597 | 598 | let ping_request = parse_single_command("PING", "PING", QueryRequest::PING); 599 | assert_eq!(ping_request, Ok(QueryRequest::PING)); 600 | 601 | let failed_request = parse_single_command("CLEAR NOW", "CLEAR", QueryRequest::PING); 602 | assert_eq!(failed_request, parser_error!(ParserErrorType::UnexpectedParameters("CLEAR".to_string()))); 603 | } 604 | 605 | // Unit tests for the `parse_exists` function: 606 | 607 | #[test] 608 | fn test_parse_exists() { 609 | let exists_request = parse_exists("key"); 610 | assert_eq!(exists_request, Ok(QueryRequest::EXISTS("key".to_string()))); 611 | 612 | let failed_exists_request = parse_exists("key1,key2"); 613 | assert_eq!(failed_exists_request, parser_error!(ParserErrorType::UnexpectedCharacter)); 614 | } 615 | 616 | // Unit tests for the `parse` function: 617 | 618 | #[test] 619 | fn test_parse() { 620 | let get_query = parse("GET key", &DatabaseType::Str); 621 | assert_eq!(get_query, Ok(QueryRequest::GET("key".to_string()))); 622 | 623 | let get_range_query = parse("GET RANGE key0 key1", &DatabaseType::Int); 624 | assert_eq!(get_range_query, Ok(QueryRequest::GET_RANGE { key_lower: "key0".to_string(), key_upper: "key1".to_string() })); 625 | 626 | let get_range_query = parse("GET RANGE key0", &DatabaseType::Int); 627 | assert_eq!(get_range_query, parser_error!(ParserErrorType::InvalidRange(1))); 628 | 629 | let get_many_query = parse("GET MANY key0 key1 key2", &DatabaseType::Float); 630 | assert_eq!(get_many_query, Ok(QueryRequest::GET_MANY(vec!["key0", "key1", "key2"]))); 631 | 632 | let get_query = parse("GET MANY key0, key1, key2", &DatabaseType::Float); 633 | assert_eq!(get_query, parser_error!(ParserErrorType::UnexpectedCharacter)); 634 | 635 | let del_query = parse("DEL key", &DatabaseType::Str); 636 | assert_eq!(del_query, Ok(QueryRequest::DEL("key".to_string()))); 637 | 638 | let del_range_query = parse("DEL RANGE key0 key1", &DatabaseType::Int); 639 | assert_eq!(del_range_query, Ok(QueryRequest::DEL_RANGE { key_lower: "key0".to_string(), key_upper: "key1".to_string() })); 640 | 641 | let del_many_query = parse("DEL MANY key0 key1 key2", &DatabaseType::Float); 642 | assert_eq!(del_many_query, Ok(QueryRequest::DEL_MANY(vec!["key0", "key1", "key2"]))); 643 | 644 | let set_query = parse("SET key0 \"val1\"", &DatabaseType::Str); 645 | assert_eq!(set_query, Ok(QueryRequest::SET(KeyValuePair { key: "key0".to_owned(), value: ValueType::Str("val1".to_owned()) } ))); 646 | 647 | let set_many_query = parse("SET MANY key0 10, key1 -10", &DatabaseType::Int); 648 | assert_eq!(set_many_query, Ok(QueryRequest::SET_MANY(vec![ 649 | KeyValuePair { key: "key0".to_owned(), value: ValueType::Int(10) }, 650 | KeyValuePair { key: "key1".to_owned(), value: ValueType::Int(-10) }, 651 | ]))); 652 | 653 | let set_query = parse("UNKNOWN key \"val\"", &DatabaseType::Str); 654 | assert_eq!(set_query, parser_error!(ParserErrorType::UnknownQueryOperation("UNKNOWN key \"val\"".to_string()))); 655 | } 656 | 657 | #[test] 658 | fn test_split_at_delimiter() { 659 | // test splits at spaces 660 | let split_string: Vec<&str> = split_at_delimiter("test test \"in quotes\" test \"in quotes\"", ' '); 661 | assert_eq!(split_string, vec!["test", "test", "\"in quotes\"", "test", "\"in quotes\""]); 662 | 663 | let split_string: Vec<&str> = split_at_delimiter("hallo/\"li,ebe /welt\" /wi,e gets\",\"", ' '); 664 | assert_eq!(split_string, vec!["hallo/\"li,ebe /welt\"", "/wi,e", "gets\",\""]); 665 | 666 | // test splits at commata 667 | let split_string: Vec<&str> = split_at_delimiter("test , test \"in, quotes\" test, \",in quotes\"", ','); 668 | assert_eq!(split_string, vec!["test", "test \"in, quotes\" test", "\",in quotes\""]); 669 | 670 | let split_string: Vec<&str> = split_at_delimiter(" key1 \"hello, world\"\n , k2 \",test\" ", ','); 671 | assert_eq!(split_string, vec!["key1 \"hello, world\"\n", "k2 \",test\""]); 672 | 673 | let split_string: Vec<&str> = split_at_delimiter("CASP/OK/AUTH/\n", '/'); 674 | assert_eq!(split_string, vec!["CASP", "OK", "AUTH", "\n"]); 675 | 676 | let split_string: Vec<&str> = split_at_delimiter("CASP/OK/GET/STR/\"oh/no\"/\n", '/'); 677 | assert_eq!(split_string, vec!["CASP", "OK", "GET", "STR", "\"oh/no\"", "\n"]); 678 | 679 | /*let string = "CASP/OK/GET/INT/key \"val ue\"/\n".replace("\"", "\\\""); 680 | let split_string: Vec = split_at_delimiter(&string, ' ').into_iter().map(|p| p.replace("\\\"", "\"")).collect(); 681 | assert_eq!(split_string, vec!["CASP/OK/GET/INT/key", "key \"val ue\"", "\n"]);*/ 682 | 683 | let split_string: Vec<&str> = split_at_delimiter("no dash", '-'); 684 | assert_eq!(split_string, vec!["no dash"]); 685 | } 686 | } -------------------------------------------------------------------------------- /cachew/src/response.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | use std::fmt::{self, Write}; 3 | 4 | 5 | use crate::{schemas::{ValueType, QueryResponseType, DatabaseType}, database}; 6 | 7 | 8 | pub struct QueryResponse { } 9 | 10 | impl QueryResponse { 11 | 12 | const CASP_PREFIX: &str = "CASP"; 13 | const CASP_SUFFIX: &str = "\n"; 14 | const CASP_OK_INDENTIFIER: &str = "OK"; 15 | const CASP_ERROR_INDENTIFIER: &str = "ERROR"; 16 | const CASP_WARN_INDENTIFIER: &str = "WARN"; 17 | 18 | fn build_ok_response(query_identifier: String, content: Option, database_type: Option<&DatabaseType>) -> String { 19 | match (content, database_type) { 20 | (None, None) => format!("{}/{}/{}/{}", Self::CASP_PREFIX, Self::CASP_OK_INDENTIFIER, query_identifier, Self::CASP_SUFFIX), 21 | (Some(content), None) => format!("{}/{}/{}/{}/{}", Self::CASP_PREFIX, Self::CASP_OK_INDENTIFIER, query_identifier, content, Self::CASP_SUFFIX), 22 | (Some(content), Some(database_type)) => format!("{}/{}/{}/{}/{}/{}", Self::CASP_PREFIX, Self::CASP_OK_INDENTIFIER, query_identifier, database_type, content, Self::CASP_SUFFIX), 23 | (None, Some(database_type)) => format!("{}/{}/{}/{}/{}", Self::CASP_PREFIX, Self::CASP_OK_INDENTIFIER, query_identifier, database_type, Self::CASP_SUFFIX), 24 | } 25 | } 26 | 27 | fn handle_value_types(value: &ValueType) -> String { 28 | match value { 29 | ValueType::Str(value) => format!("\"{}\"", value), 30 | ValueType::Int(value) => format!("{}", value), 31 | ValueType::Float(value) => format!("{}", value), 32 | ValueType::Bool(value) => format!("{}", value), 33 | ValueType::Json(value) => format!("\"{}\"", value), 34 | } 35 | } 36 | 37 | pub fn ok(response: QueryResponseType, database_type: &DatabaseType) -> String { 38 | match response { 39 | QueryResponseType::GET_OK(value) => { 40 | Self::build_ok_response("GET".to_string(), Some(Self::handle_value_types(&value)), Some(database_type)) 41 | }, 42 | QueryResponseType::GET_RANGE_OK(values) => { 43 | let mut content: String = String::new(); 44 | for (idx, value) in values.iter().enumerate() { 45 | write!(&mut content, "{}", Self::handle_value_types(value)).expect(""); 46 | if idx < values.len() - 1 { 47 | write!(&mut content, ",").expect(""); 48 | } 49 | } 50 | 51 | if content.is_empty() { 52 | content = "NONE".to_string(); 53 | } 54 | 55 | Self::build_ok_response("GET RANGE".to_string(), Some(content), Some(database_type)) 56 | 57 | }, 58 | QueryResponseType::GET_MANY_OK(values) => { 59 | let mut content: String = String::new(); 60 | for (idx, value) in values.iter().enumerate() { 61 | write!(&mut content, "{}", Self::handle_value_types(value)).expect(""); 62 | if idx < values.len() - 1 { 63 | write!(&mut content, ",").expect(""); 64 | } 65 | } 66 | Self::build_ok_response("GET MANY".to_string(), Some(content), Some(database_type)) 67 | }, 68 | QueryResponseType::DEL_OK => { 69 | Self::build_ok_response("DEL".to_string(), None, None) 70 | }, 71 | QueryResponseType::DEL_RANGE_OK => { 72 | Self::build_ok_response("DEL RANGE".to_string(), None, None) 73 | }, 74 | QueryResponseType::DEL_MANY_OK => { 75 | Self::build_ok_response("DEL MANY".to_string(), None, None) 76 | }, 77 | QueryResponseType::SET_OK => { 78 | Self::build_ok_response("SET".to_string(), None, None) 79 | }, 80 | QueryResponseType::SET_MANY_OK => { 81 | Self::build_ok_response("SET MANY".to_string(), None, None) 82 | }, 83 | QueryResponseType::AUTH_OK => { 84 | Self::build_ok_response("AUTH".to_string(), None, None) 85 | } 86 | QueryResponseType::CLEAR_OK => { 87 | Self::build_ok_response("CLEAR".to_string(), None, None) 88 | }, 89 | QueryResponseType::LEN_OK(length) => { 90 | Self::build_ok_response("LEN".to_string(), Some(length.to_string()), None) 91 | }, 92 | QueryResponseType::PING_OK => { 93 | Self::build_ok_response("PING".to_string(), Some("PONG".to_string()), None) 94 | }, 95 | QueryResponseType::EXISTS_OK(exists) => { 96 | Self::build_ok_response("EXISTS".to_string(), Some(exists.to_string()), None) 97 | } 98 | QueryResponseType::SHUTDOWN_OK => { 99 | Self::build_ok_response("SHUTDOWN".to_string(), None, None) 100 | } 101 | } 102 | } 103 | 104 | pub fn error(error: &str) -> String { 105 | format!("{}/{}/{}/{}", Self::CASP_PREFIX, Self::CASP_ERROR_INDENTIFIER, error, Self::CASP_SUFFIX) 106 | } 107 | 108 | pub fn warn(message: &str) -> String { 109 | format!("{}/{}/{}/{}", Self::CASP_PREFIX, Self::CASP_WARN_INDENTIFIER, message, Self::CASP_SUFFIX) 110 | } 111 | } 112 | 113 | 114 | 115 | #[cfg(test)] 116 | mod tests { 117 | use super::*; 118 | 119 | #[test] 120 | fn test_get_string() { 121 | let response = QueryResponse::ok( 122 | QueryResponseType::GET_OK(ValueType::Str("value".to_string())), 123 | &DatabaseType::Str 124 | ); 125 | assert_eq!(response, "CASP/OK/GET/STR/\"value\"/\n") 126 | } 127 | 128 | #[test] 129 | fn test_get_int() { 130 | let response = QueryResponse::ok( 131 | QueryResponseType::GET_OK(ValueType::Int(-100)), 132 | &DatabaseType::Int 133 | ); 134 | assert_eq!(response, "CASP/OK/GET/INT/-100/\n") 135 | } 136 | 137 | #[test] 138 | fn test_get_float() { 139 | let response = QueryResponse::ok( 140 | QueryResponseType::GET_OK(ValueType::Float(0.01)), 141 | &DatabaseType::Float 142 | ); 143 | assert_eq!(response, "CASP/OK/GET/FLOAT/0.01/\n") 144 | } 145 | 146 | #[test] 147 | fn test_get_range_string() { 148 | let response = QueryResponse::ok( 149 | QueryResponseType::GET_RANGE_OK(vec![ValueType::Str("value1".to_string()), ValueType::Str("value2".to_string())]), 150 | &DatabaseType::Str 151 | ); 152 | assert_eq!(response, "CASP/OK/GET RANGE/STR/\"value1\",\"value2\"/\n") 153 | } 154 | 155 | #[test] 156 | fn test_get_range_float() { 157 | let response = QueryResponse::ok( 158 | QueryResponseType::GET_RANGE_OK(vec![ValueType::Float(0.01), ValueType::Float(0.02), ValueType::Float(0.03)]), 159 | &DatabaseType::Float 160 | ); 161 | assert_eq!(response, "CASP/OK/GET RANGE/FLOAT/0.01,0.02,0.03/\n"); 162 | 163 | let response = QueryResponse::ok( 164 | QueryResponseType::GET_RANGE_OK(vec![]), 165 | &DatabaseType::Float 166 | ); 167 | assert_eq!(response, "CASP/OK/GET RANGE/FLOAT/NONE/\n") 168 | } 169 | 170 | #[test] 171 | fn test_get_many_string() { 172 | let response = QueryResponse::ok( 173 | QueryResponseType::GET_MANY_OK(vec![ValueType::Str("value1".to_string()), ValueType::Str("value2".to_string())]), 174 | &DatabaseType::Str 175 | ); 176 | assert_eq!(response, "CASP/OK/GET MANY/STR/\"value1\",\"value2\"/\n") 177 | } 178 | 179 | #[test] 180 | fn test_del() { 181 | let response = QueryResponse::ok( 182 | QueryResponseType::DEL_OK, 183 | &DatabaseType::Float 184 | ); 185 | assert_eq!(response, "CASP/OK/DEL/\n") 186 | } 187 | 188 | #[test] 189 | fn test_del_range() { 190 | let response = QueryResponse::ok( 191 | QueryResponseType::DEL_RANGE_OK, 192 | &DatabaseType::Str 193 | ); 194 | assert_eq!(response, "CASP/OK/DEL RANGE/\n") 195 | } 196 | 197 | #[test] 198 | fn test_del_many() { 199 | let response = QueryResponse::ok( 200 | QueryResponseType::DEL_MANY_OK, 201 | &DatabaseType::Str 202 | ); 203 | assert_eq!(response, "CASP/OK/DEL MANY/\n") 204 | } 205 | 206 | #[test] 207 | fn test_set() { 208 | let response = QueryResponse::ok( 209 | QueryResponseType::SET_OK, 210 | &DatabaseType::Int 211 | ); 212 | assert_eq!(response, "CASP/OK/SET/\n") 213 | } 214 | 215 | #[test] 216 | fn test_set_many() { 217 | let response = QueryResponse::ok( 218 | QueryResponseType::SET_MANY_OK, 219 | &DatabaseType::Str 220 | ); 221 | assert_eq!(response, "CASP/OK/SET MANY/\n") 222 | } 223 | 224 | #[test] 225 | fn test_auth() { 226 | let response = QueryResponse::ok( 227 | QueryResponseType::AUTH_OK, 228 | &DatabaseType::Float 229 | ); 230 | assert_eq!(response, "CASP/OK/AUTH/\n") 231 | } 232 | 233 | #[test] 234 | fn test_clear() { 235 | let response = QueryResponse::ok( 236 | QueryResponseType::CLEAR_OK, 237 | &DatabaseType::Bool 238 | ); 239 | assert_eq!(response, "CASP/OK/CLEAR/\n") 240 | } 241 | 242 | #[test] 243 | fn test_len() { 244 | let response = QueryResponse::ok( 245 | QueryResponseType::LEN_OK(3), 246 | &DatabaseType::Json 247 | ); 248 | assert_eq!(response, "CASP/OK/LEN/3/\n") 249 | } 250 | 251 | #[test] 252 | fn test_ping() { 253 | let response = QueryResponse::ok( 254 | QueryResponseType::PING_OK, 255 | &DatabaseType::Str 256 | ); 257 | assert_eq!(response, "CASP/OK/PING/PONG/\n") 258 | } 259 | 260 | #[test] 261 | fn test_exists() { 262 | let response = QueryResponse::ok( 263 | QueryResponseType::EXISTS_OK(true), 264 | &DatabaseType::Str 265 | ); 266 | assert_eq!(response, "CASP/OK/EXISTS/true/\n"); 267 | 268 | let response = QueryResponse::ok( 269 | QueryResponseType::EXISTS_OK(false), 270 | &DatabaseType::Str 271 | ); 272 | assert_eq!(response, "CASP/OK/EXISTS/false/\n") 273 | } 274 | 275 | #[test] 276 | fn test_error() { 277 | let response = QueryResponse::error("This is an error message."); 278 | assert_eq!(response, "CASP/ERROR/This is an error message./\n") 279 | } 280 | } -------------------------------------------------------------------------------- /cachew/src/schemas.rs: -------------------------------------------------------------------------------- 1 | use std::{str::FromStr, fmt}; 2 | 3 | use serde::{Serialize, Deserialize}; 4 | 5 | 6 | 7 | #[derive(Debug, PartialEq)] 8 | pub struct KeyValuePair { 9 | pub key: String, 10 | pub value: ValueType 11 | } 12 | 13 | 14 | #[derive(Debug, PartialEq)] 15 | pub enum QueryRequest<'a> { 16 | GET(String), 17 | SET(KeyValuePair), 18 | SET_MANY(Vec), 19 | GET_RANGE { key_lower: String, key_upper: String}, 20 | GET_MANY(Vec<&'a str>), 21 | DEL(String), 22 | DEL_RANGE { key_lower: String, key_upper: String}, 23 | DEL_MANY(Vec<&'a str>), 24 | AUTH(String), 25 | CLEAR, 26 | LEN, 27 | PING, 28 | EXISTS(String), 29 | SHUTDOWN 30 | } 31 | 32 | 33 | #[derive(Debug, Serialize, Deserialize, PartialEq)] 34 | pub enum ValueType { 35 | Str(String), 36 | Int(i32), 37 | Float(f32), 38 | Bool(bool), 39 | Json(String) 40 | } 41 | 42 | 43 | #[derive(Debug, PartialEq)] 44 | pub enum QueryResponseType { 45 | GET_OK(ValueType), 46 | GET_RANGE_OK(Vec), 47 | GET_MANY_OK(Vec), 48 | DEL_OK, 49 | DEL_RANGE_OK, 50 | DEL_MANY_OK, 51 | SET_OK, 52 | SET_MANY_OK, 53 | AUTH_OK, 54 | CLEAR_OK, 55 | LEN_OK(usize), 56 | PING_OK, 57 | EXISTS_OK(bool), 58 | SHUTDOWN_OK 59 | } 60 | 61 | 62 | #[derive(Copy, Clone, PartialEq, Debug)] 63 | pub enum DatabaseType { 64 | Str, 65 | Int, 66 | Float, 67 | Bool, 68 | Json 69 | } 70 | 71 | impl fmt::Display for DatabaseType { 72 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 73 | match self { 74 | DatabaseType::Str => write!(f, "STR"), 75 | DatabaseType::Int => write!(f, "INT"), 76 | DatabaseType::Float => write!(f, "FLOAT"), 77 | DatabaseType::Bool => write!(f, "BOOL"), 78 | DatabaseType::Json => write!(f, "JSON"), 79 | } 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /cachew/src/server.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::{Arc}, net::SocketAddr}; 2 | use tokio::{sync::{Mutex, broadcast}, signal::unix::{signal, SignalKind}}; 3 | use tokio::{ 4 | io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, 5 | net::{TcpListener, TcpStream, tcp::ReadHalf} 6 | }; 7 | use log::{info, warn, error}; 8 | 9 | use crate::{response::QueryResponse, state::State, schemas::QueryRequest}; 10 | use crate::{protocol_error}; 11 | use crate::parser; 12 | use crate::errors::protocol_errors::{ProtocolErrorType}; 13 | 14 | const REQUEST_START_MARKER: &str = "CASP/"; 15 | const REQUEST_END_MARKER: &str = "/\n"; 16 | 17 | 18 | fn check_protocol(request: &str) -> Result<(), String> { 19 | if request.is_empty() || request == "\n" { 20 | protocol_error!(ProtocolErrorType::EmptyRequest) 21 | } 22 | else if !(request.len() >= REQUEST_START_MARKER.len() && &request[..REQUEST_START_MARKER.len()] == REQUEST_START_MARKER) { 23 | protocol_error!(ProtocolErrorType::StartMarkerNotFound(REQUEST_START_MARKER.to_string())) 24 | } 25 | 26 | else if &request[request.len() - REQUEST_END_MARKER.len()..] != REQUEST_END_MARKER { 27 | protocol_error!(ProtocolErrorType::EndMarkerNotFound(REQUEST_END_MARKER.to_string())) 28 | } 29 | 30 | else if request.len() <= (REQUEST_START_MARKER.len() + REQUEST_END_MARKER.len()) { 31 | protocol_error!(ProtocolErrorType::EndMarkerNotFound(REQUEST_END_MARKER.to_string())) 32 | } 33 | 34 | Ok(()) 35 | } 36 | 37 | 38 | 39 | async fn handle_client(mut socket: TcpStream, address: SocketAddr, state_clone: Arc>, mut shutdown_rx: broadcast::Receiver<()>) { 40 | let (socket_reader, mut socket_writer) = socket.split(); 41 | 42 | let mut reader: BufReader = BufReader::new(socket_reader); 43 | let mut line: String = String::new(); 44 | 45 | loop { 46 | tokio::select! { 47 | _ = shutdown_rx.recv() => { 48 | let _ = socket_writer.write_all(QueryResponse::warn("SHUTDOWN").to_string().as_bytes()).await; 49 | break; 50 | } 51 | byte_amount = reader.read_line(&mut line) => { 52 | let mut state_lock = state_clone.lock().await; 53 | 54 | if byte_amount.unwrap() == 0 { 55 | warn!("Connection closed. ({})", address); 56 | state_lock.deauthenticate(&address.to_string()); 57 | return; 58 | } 59 | 60 | info!("Incoming request: {:?}.", &line); 61 | 62 | // check if the incoming message followed the protocol specification 63 | match check_protocol(&line) { 64 | Ok(_) => { } 65 | Err(error) => { 66 | error!("Invalid request. Request didn't follow CASP specification."); 67 | socket_writer.write_all(QueryResponse::error(&error).to_string().as_bytes()).await.unwrap(); 68 | break; 69 | } 70 | } 71 | 72 | // extract the raw database request form the message and parse it 73 | let request: &str = line.strip_prefix(REQUEST_START_MARKER).unwrap().strip_suffix(REQUEST_END_MARKER).unwrap().trim(); 74 | let query = parser::parse(request, &state_lock.database_type); 75 | 76 | match query { 77 | Ok(query) => { 78 | // handle shutdown on request 79 | if let QueryRequest::SHUTDOWN = query { 80 | warn!("Received shutdown request. Shutting down gracefully..."); 81 | 82 | // notify all client handlers to send shutdown message to their client 83 | state_lock.signal_shutdown().await; 84 | 85 | // send OK response to client who intiated shutdown 86 | socket_writer.write_all(QueryResponse::ok(crate::schemas::QueryResponseType::SHUTDOWN_OK, &state_lock.database_type).to_string().as_bytes()).await.unwrap(); 87 | 88 | // TODO: add persistance here if needed. 89 | 90 | info!("Graceful shutdown completed."); 91 | std::process::exit(0); 92 | } 93 | 94 | match state_lock.execute_request(&address.to_string(), query) { 95 | Ok(result) => { 96 | info!("Successfully executed request."); 97 | socket_writer.write_all(QueryResponse::ok(result, &state_lock.database_type).to_string().as_bytes()).await.unwrap(); 98 | } 99 | Err(error) => { 100 | error!("Failed to execute request. Error: {:?}.", &error); 101 | socket_writer.write_all(QueryResponse::error(&error).to_string().as_bytes()).await.unwrap(); 102 | } 103 | } 104 | } 105 | Err(error) => { 106 | error!("Failed to parse request. Error: {:?}.", &error); 107 | socket_writer.write_all(QueryResponse::error(&error).to_string().as_bytes()).await.unwrap(); 108 | } 109 | } 110 | 111 | line.clear(); 112 | } 113 | } 114 | } 115 | } 116 | 117 | 118 | 119 | pub async fn serve(state: State, host: &str, port: &str) { 120 | let listener = TcpListener::bind(format!("{}:{}", host, port)).await; 121 | 122 | match listener { 123 | Ok(listener) => { 124 | info!("Started CachewDB server. Listening on {}:{}.", host, port); 125 | let state: Arc> = Arc::new(Mutex::new(state)); 126 | let state_clone = Arc::clone(&state); 127 | 128 | let mut signal = signal(SignalKind::interrupt()).expect("Failed to create SIGINT signal handler."); 129 | tokio::spawn(async move { 130 | signal.recv().await; 131 | 132 | println!(); 133 | warn!("Received SIGINT signal (Ctrl+C). Shutting down gracefully..."); 134 | 135 | // notify all client handlers to send shutdown message to their client 136 | state_clone.lock().await.signal_shutdown().await; 137 | 138 | // TODO: add persistance here if needed. 139 | 140 | info!("Graceful shutdown completed."); 141 | std::process::exit(0); 142 | }); 143 | 144 | loop { 145 | let (socket, address) = listener.accept().await.unwrap(); 146 | info!("Accepted new client ({}).", address); 147 | 148 | let state_clone = Arc::clone(&state); 149 | let shutdown_rx_clone = state_clone.lock().await.subscribe_shutdown_channel(); 150 | tokio::spawn(handle_client(socket, address, state_clone, shutdown_rx_clone)); 151 | } 152 | } 153 | Err(error) => { 154 | error!("Failed to start CachewDB server! Error: {}", error); 155 | } 156 | } 157 | } 158 | 159 | 160 | 161 | #[cfg(test)] 162 | mod tests { 163 | use super::*; 164 | 165 | #[test] 166 | fn test_check_protocol() { 167 | let request_set = check_protocol("CASP/SET key value/\n"); 168 | assert_eq!(request_set, Ok(())); 169 | 170 | let protocol_validity = check_protocol(""); 171 | assert_eq!(protocol_validity.unwrap_err(), "ProtocolError 'emptyRequest': Can't process empty request."); 172 | 173 | let protocol_validity = check_protocol("CAS/SET key value/\n"); 174 | assert_eq!(protocol_validity.unwrap_err(), format!("ProtocolError 'startMarkerNotFound': Expected request to start with '{}'.", REQUEST_START_MARKER)); 175 | 176 | let protocol_validity = check_protocol("CASP/SET key value"); 177 | assert_eq!(protocol_validity.unwrap_err(), format!("ProtocolError 'endMarkerNotFound': Expected request to end with '{}'.", REQUEST_END_MARKER.replace('\n', "\\n"))); 178 | } 179 | 180 | } -------------------------------------------------------------------------------- /cachew/src/state.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap}; 2 | use std::time::{Duration}; 3 | use tokio::sync::broadcast; 4 | 5 | use crate::auth_error; 6 | use crate::schemas::{DatabaseType, QueryRequest, QueryResponseType}; 7 | use crate::database::Database; 8 | use crate::errors::authentication_errors::{AuthenticationErrorType}; 9 | 10 | 11 | 12 | pub struct State { 13 | pub db: Database, 14 | pub auth_table: HashMap, 15 | pub password: String, 16 | pub database_type: DatabaseType, 17 | pub shutdown_tx: broadcast::Sender<()>, 18 | pub shutdown_rx: broadcast::Receiver<()> 19 | } 20 | 21 | impl State { 22 | pub fn new(database_type: DatabaseType, password: String) -> Self { 23 | let db: Database = Database::new(database_type); 24 | let auth_table: HashMap = HashMap::new(); 25 | 26 | let (shutdown_tx, shutdown_rx) = broadcast::channel::<()>(1); 27 | 28 | Self { 29 | db, 30 | auth_table, 31 | password, 32 | database_type, 33 | shutdown_tx, 34 | shutdown_rx 35 | } 36 | } 37 | 38 | pub fn is_authenticated(&self, address: String) -> bool { 39 | matches!(self.auth_table.get(&address), Some(_)) 40 | } 41 | 42 | pub fn deauthenticate(&mut self, address: &str) { 43 | self.auth_table.remove(address); 44 | } 45 | 46 | pub fn authenticate(&mut self, address: &str, given_password: &str) -> Result { 47 | // if the passwords match, add the client to the auth table 48 | if given_password == self.password { 49 | self.auth_table.insert(address.to_owned(), true); 50 | 51 | return Ok(QueryResponseType::AUTH_OK); 52 | } 53 | auth_error!(AuthenticationErrorType::AuthenticationFailed) 54 | } 55 | 56 | pub fn execute_request(&mut self, address: &str, request: QueryRequest) -> Result { 57 | // before executing the query, check if the client address is authenticated 58 | if !self.is_authenticated(address.to_owned()) { 59 | 60 | // it not, allow to authenticate 61 | if let QueryRequest::AUTH(password) = request { 62 | return self.authenticate(address, &password); 63 | } 64 | // but disallow other requests 65 | else { 66 | auth_error!(AuthenticationErrorType::NotAuthenticated) 67 | } 68 | } 69 | 70 | match request { 71 | QueryRequest::GET(key) => self.db.get(&key), 72 | QueryRequest::GET_RANGE { key_lower, key_upper } => self.db.get_range(key_lower, key_upper), 73 | QueryRequest::GET_MANY(keys) => self.db.get_many(keys), 74 | QueryRequest::DEL(key) => self.db.del(&key), 75 | QueryRequest::DEL_RANGE { key_lower, key_upper } => self.db.del_range(key_lower, key_upper), 76 | QueryRequest::DEL_MANY(keys) => self.db.del_many(keys), 77 | QueryRequest::SET(key_value_pair) => self.db.set(&key_value_pair.key, key_value_pair.value), 78 | QueryRequest::SET_MANY(key_value_pairs) => self.db.set_many(key_value_pairs), 79 | QueryRequest::AUTH(password) => self.authenticate(address, &password), 80 | QueryRequest::CLEAR => self.db.clear(), 81 | QueryRequest::LEN => self.db.len(), 82 | QueryRequest::PING => Ok(QueryResponseType::PING_OK), 83 | QueryRequest::EXISTS(key) => self.db.exists(&key), 84 | QueryRequest::SHUTDOWN => Ok(QueryResponseType::SHUTDOWN_OK) 85 | } 86 | } 87 | 88 | pub async fn signal_shutdown(&self) { 89 | let _ = self.shutdown_tx.send(()); 90 | tokio::time::sleep(Duration::from_secs(1)).await; 91 | } 92 | 93 | pub fn subscribe_shutdown_channel(&self) -> broadcast::Receiver<()> { 94 | self.shutdown_rx.resubscribe() 95 | } 96 | } 97 | 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use crate::schemas::{KeyValuePair, ValueType}; 102 | use super::*; 103 | 104 | #[test] 105 | fn test_authentication() { 106 | let database_type = DatabaseType::Str; 107 | let mut state: State = State::new(database_type, "pwd123".to_string()); 108 | 109 | let client_address: &str = "0.0.0.0:0000"; 110 | 111 | let authenticated = state.authenticate(client_address, "pwd123"); 112 | assert_eq!(authenticated, Ok(QueryResponseType::AUTH_OK)); 113 | 114 | let is_authenticated = state.is_authenticated(client_address.to_owned()); 115 | assert!(is_authenticated); 116 | 117 | state.deauthenticate(client_address); 118 | assert!(!matches!(state.auth_table.get(client_address), Some(_))); 119 | 120 | let failed_authentication = state.authenticate(client_address, "wrongpassword"); 121 | assert_eq!(failed_authentication.unwrap_err(), "AuthenticationError 'authenticationFailed': Wrong password."); 122 | } 123 | 124 | #[test] 125 | fn test_execute_query() { 126 | let database_type = DatabaseType::Str; 127 | let mut state: State = State::new(database_type, "pwd123".to_string()); 128 | 129 | let client_address: &str = "0.0.0.0:0000"; 130 | 131 | // request without being authenticated 132 | let not_authenticated = state.execute_request(client_address, QueryRequest::SET(KeyValuePair { key: "key".to_string(), value: ValueType::Str("value".to_string()) })); 133 | assert_eq!(not_authenticated.unwrap_err(), "AuthenticationError 'notAuthenticated': Please authenticate before executing queries."); 134 | 135 | // make authentication request 136 | let authentication_request = state.execute_request(client_address, QueryRequest::AUTH("pwd123".to_string())); 137 | assert_eq!(authentication_request, Ok(QueryResponseType::AUTH_OK)); 138 | 139 | // test query requests 140 | 141 | let response_set = state.execute_request(client_address, QueryRequest::SET(KeyValuePair { key: "key".to_string(), value: ValueType::Str("value".to_string()) })); 142 | assert_eq!(response_set, Ok(QueryResponseType::SET_OK)); 143 | 144 | let response_set_many = state.execute_request(client_address, QueryRequest::SET_MANY(vec![ 145 | KeyValuePair { key: "key1".to_string(), value: ValueType::Str("value1".to_string()) }, 146 | KeyValuePair { key: "key2".to_string(), value: ValueType::Str("value2".to_string()) }, 147 | KeyValuePair { key: "key3".to_string(), value: ValueType::Str("value3".to_string()) }, 148 | KeyValuePair { key: "key4".to_string(), value: ValueType::Str("value4".to_string()) }, 149 | KeyValuePair { key: "key5".to_string(), value: ValueType::Str("value5".to_string()) } 150 | ])); 151 | assert_eq!(response_set_many, Ok(QueryResponseType::SET_MANY_OK)); 152 | 153 | let response_get = state.execute_request(client_address, QueryRequest::GET("key1".to_string())); 154 | assert_eq!(response_get, Ok(QueryResponseType::GET_OK(ValueType::Str("value1".to_string())))); 155 | 156 | let response_get_many = state.execute_request(client_address, QueryRequest::GET_MANY(vec!["key3", "key2"])); 157 | assert_eq!(response_get_many, Ok(QueryResponseType::GET_MANY_OK(vec![ValueType::Str("value3".to_string()), ValueType::Str("value2".to_string())]))); 158 | 159 | let response_get_range = state.execute_request(client_address, QueryRequest::GET_RANGE { key_lower: "key2".to_string(), key_upper: "key4".to_string() }); 160 | assert_eq!(response_get_range, Ok(QueryResponseType::GET_RANGE_OK(vec![ 161 | ValueType::Str("value2".to_string()), ValueType::Str("value3".to_string()), ValueType::Str("value4".to_string()) 162 | ]))); 163 | 164 | let response_ping = state.execute_request(client_address,QueryRequest::EXISTS("key2".to_owned())); 165 | assert_eq!(response_ping, Ok(QueryResponseType::EXISTS_OK(true))); 166 | 167 | let response_del = state.execute_request(client_address, QueryRequest::DEL("key1".to_string())); 168 | assert_eq!(response_del, Ok(QueryResponseType::DEL_OK)); 169 | 170 | let response_del_many = state.execute_request(client_address, QueryRequest::DEL_MANY(vec!["key4", "key3"])); 171 | assert_eq!(response_del_many, Ok(QueryResponseType::DEL_MANY_OK)); 172 | 173 | let response_del_range = state.execute_request(client_address, QueryRequest::DEL_RANGE { key_lower: "key2".to_string(), key_upper: "key5".to_string() }); 174 | assert_eq!(response_del_range, Ok(QueryResponseType::DEL_RANGE_OK)); 175 | 176 | let response_clear = state.execute_request(client_address, QueryRequest::CLEAR); 177 | assert_eq!(response_clear, Ok(QueryResponseType::CLEAR_OK)); 178 | 179 | let response_len = state.execute_request(client_address, QueryRequest::LEN); 180 | assert_eq!(response_len, Ok(QueryResponseType::LEN_OK(0))); 181 | 182 | let response_ping = state.execute_request(client_address,QueryRequest::PING); 183 | assert_eq!(response_ping, Ok(QueryResponseType::PING_OK)); 184 | } 185 | 186 | } 187 | 188 | -------------------------------------------------------------------------------- /cli-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cachew-cli-client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "4.3.0", features = ["derive"] } 10 | tokio = { version = "1", features = ["full"] } 11 | log = "0.4" 12 | env_logger = "0.8" 13 | colored = "2.0.4" 14 | regex = "1.5" 15 | -------------------------------------------------------------------------------- /cli-client/src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use colored::Colorize; 3 | use std::io::{self, Write}; 4 | use std::error::Error; 5 | use crate::parser::{ParsedResponse, ResponseStatus}; 6 | 7 | 8 | #[derive(Parser, Debug)] 9 | #[command(author, version, about, long_about = None)] 10 | pub struct CliArgs { 11 | #[arg(long = "host")] 12 | pub host: String, 13 | 14 | #[arg(long = "port")] 15 | pub port: String, 16 | 17 | #[arg(long = "password")] 18 | pub password: Option, 19 | } 20 | 21 | 22 | pub fn get_cli_arguments() -> CliArgs { 23 | CliArgs::parse() 24 | } 25 | 26 | 27 | pub enum CliCommand { 28 | DatabaseRequest(String), 29 | Exit, 30 | Help, 31 | Unknown(String) 32 | } 33 | 34 | 35 | pub fn get_cli_command(input: &str) -> CliCommand { 36 | const META_COMMAND_PREFIX: &str = "$"; 37 | 38 | if input.starts_with(META_COMMAND_PREFIX) { 39 | 40 | let command: &str = input.strip_prefix(META_COMMAND_PREFIX).unwrap().trim(); 41 | match command { 42 | "exit" => { 43 | return CliCommand::Exit; 44 | } 45 | "help" => { 46 | return CliCommand::Help; 47 | } 48 | _ => { 49 | return CliCommand::Unknown(command.to_owned()) 50 | } 51 | } 52 | } 53 | 54 | CliCommand::DatabaseRequest(input.to_string()) 55 | } 56 | 57 | 58 | 59 | pub fn build_request(input: &str) -> String { 60 | const CASP_PREFIX: &str = "CASP"; 61 | const CASP_SUFFIX: &str = "\n"; 62 | 63 | format!("{}/{}/{}", CASP_PREFIX, input.trim(), CASP_SUFFIX) 64 | } 65 | 66 | 67 | 68 | pub fn print_response(response: &ParsedResponse) { 69 | match response.status { 70 | ResponseStatus::OK => { 71 | match &response.value { 72 | None => println!("\r{} {} {}\n", "server >>".bright_black(), "OK".green(), response.command.as_ref().unwrap().green()), 73 | Some(value) => println!("\r{} {} {}: {}\n", "server >>".bright_black(), "OK".green(), response.command.as_ref().unwrap().green(), value), 74 | } 75 | }, 76 | ResponseStatus::WARN => { 77 | println!("\r{} {} {}\n", "server >>".bright_black(), "WARN".yellow(), response.command.as_ref().unwrap().green()) 78 | }, 79 | ResponseStatus::ERROR => { 80 | match &response.value { 81 | None => println!("\r{}: Failed to parse response.\n", "ERROR".red()), 82 | Some(value) => println!("\r{} {} {}\n", "server >>".bright_black(), "ERROR".red(), value), 83 | } 84 | } 85 | } 86 | 87 | } 88 | 89 | pub fn print_parser_error(error: &str) { 90 | println!("\r{}: {}\n", "ERROR".red(), error); 91 | } 92 | 93 | pub fn prompt_command() { 94 | print!("{} ", "cachew >>".bold()); 95 | io::stdout().flush().expect("Failed to flush stdout"); 96 | } 97 | 98 | pub fn print_warn(message: &str) { 99 | println!("{} {}", "WARN".bold().yellow(), message); 100 | } 101 | 102 | pub fn print_info(message: &str) { 103 | println!("{} {}", "INFO".bold().green(), message); 104 | } 105 | 106 | pub fn print_error(message: &str) { 107 | println!("{} {}", "ERROR".bold().red(), message); 108 | } 109 | 110 | pub fn print_help() { 111 | 112 | let meta_commands: Vec<(&str, &str)> = vec![ 113 | ("$exit", "Stops the client."), 114 | ("$help", "Shows this help text."), 115 | ]; 116 | 117 | let database_commands: Vec<(&str, &str)> = vec![ 118 | ("AUTH", "Authenticating on the server."), 119 | ("PING", "Checks if server is running (responses with 'PONG' if so)."), 120 | ("SET ", "Inserts a new key value pair."), 121 | ("SET MANY , ...", "Inserts multiple key value pairs."), 122 | ("GET ", "Gets a value by key."), 123 | ("GET MANY , ... ", "Gets multiple values by their key."), 124 | ("GET RANGE ", "Gets values in a range of keys."), 125 | ("DEL ", "Deletes a value by key."), 126 | ("DEL MANY , ... ", "Deletes multiple values by their key."), 127 | ("DEL RANGE ", "Deletes values in a range of keys."), 128 | ("EXISTS ", "Checks if a key exists or not."), 129 | ("LEN", "Returns the amount of entries in the database."), 130 | ("CLEAR", "Deletes all entries in the database."), 131 | ("SHUTDOWN", "Shuts down the server."), 132 | ]; 133 | 134 | let mut help_text = String::new(); 135 | 136 | help_text.push_str("1. Meta commands (prefixed with '$'):\n"); 137 | for (command, description) in meta_commands { 138 | help_text.push_str(&format!(" {}: {}\n", command.bold(), description)) 139 | } 140 | 141 | help_text.push_str("\n2. Database commands:\n"); 142 | for (command, description) in database_commands { 143 | help_text.push_str(&format!(" {}: {}\n", command.bold(), description)) 144 | } 145 | 146 | println!("\n{} The following is a list of all executable commands:\n{}", "HELP".bold().green(), help_text); 147 | } -------------------------------------------------------------------------------- /cli-client/src/main.rs: -------------------------------------------------------------------------------- 1 | mod cli; 2 | //mod interface; 3 | mod parser; 4 | 5 | use std::error::Error; 6 | use tokio::io::{AsyncReadExt, AsyncBufReadExt, AsyncWriteExt, WriteHalf, BufReader}; 7 | use tokio::task; 8 | use tokio::net::{TcpStream, tcp::ReadHalf}; 9 | use std::io; 10 | 11 | use cli::*; 12 | use parser::{ParsedResponse, ResponseStatus, parse_response}; 13 | 14 | 15 | #[tokio::main] 16 | async fn main() -> Result<(), Box> { 17 | let cli_arguments = get_cli_arguments(); 18 | let host_address = format!("{}:{}", cli_arguments.host, cli_arguments.port); 19 | 20 | // connect to CachewDB server 21 | let mut stream = TcpStream::connect(&host_address).await.map_err(|error| { 22 | print_error(&format!("Failed to connect to server {}. Error: {}", &host_address, error)) 23 | }).unwrap(); 24 | 25 | let (reader, mut writer) = stream.split(); 26 | let mut reader: BufReader = BufReader::new(reader); 27 | 28 | print_info(&format!("Connected to server {}.", &host_address)); 29 | 30 | // string buffer to store responses 31 | let mut line: String = String::new(); 32 | 33 | // make an initial authentication request 34 | if let Some(password) = cli_arguments.password { 35 | let auth_request: String = format!("AUTH {}", password); 36 | let _ = writer.write_all(build_request(&auth_request).as_bytes()).await; 37 | 38 | let _ = reader.read_line(&mut line).await; 39 | let parsed_response: Result = parse_response(&line); 40 | match parsed_response { 41 | Ok(response) => { 42 | if response.status == ResponseStatus::OK { 43 | print_info("Authentication successful."); 44 | } 45 | else { 46 | print_error(&format!("Authentication failed. Error: {}", response.value.unwrap())); 47 | std::process::exit(0); 48 | } 49 | }, 50 | Err(error) => { 51 | print_parser_error(&error); 52 | std::process::exit(0); 53 | } 54 | } 55 | } 56 | else { 57 | print_warn("Not authenticated. You won't be able to make requests."); 58 | } 59 | 60 | println!(); 61 | 62 | line = String::new(); 63 | // string buffer to store user input 64 | let mut stdin = tokio::io::BufReader::new(tokio::io::stdin()); 65 | let mut input = String::new(); 66 | 67 | loop { 68 | prompt_command(); 69 | 70 | input = String::new(); 71 | 72 | tokio::select! { 73 | _ = reader.read_line(&mut line) => { 74 | 75 | let parsed_response: Result = parse_response(&line); 76 | match &parsed_response { 77 | Ok(response) => { 78 | print_response(&response); 79 | 80 | if let Some(command) = &response.command { 81 | if command == "SHUTDOWN" { 82 | print_warn("Disconnecting."); 83 | std::process::exit(0); 84 | } 85 | } 86 | }, 87 | Err(error) => { 88 | print_parser_error(&error); 89 | } 90 | } 91 | 92 | line.clear(); 93 | } 94 | _ = stdin.read_line(&mut input) => { 95 | if input.len() > 1 { 96 | let cli_command: CliCommand = get_cli_command(&input); 97 | 98 | match cli_command { 99 | CliCommand::DatabaseRequest(request) => { 100 | let casp_request = build_request(&request); 101 | let _ = writer.write_all(casp_request.as_bytes()).await; 102 | } 103 | CliCommand::Exit => { 104 | print_warn("Stopping client."); 105 | std::process::exit(0); 106 | } 107 | CliCommand::Help => { 108 | //print_info("Help on its way."); 109 | print_help(); 110 | } 111 | CliCommand::Unknown(command) => { 112 | print_error(&format!("Unknown meta command '{}'. Type '$help' to see available commands.", command)); 113 | } 114 | } 115 | } 116 | } 117 | } 118 | } 119 | 120 | Ok(()) 121 | } 122 | 123 | 124 | 125 | /* 126 | // make an initial authentication request 127 | let auth_request: String = format!("AUTH {}", cli_arguments.password); 128 | writer.write_all(auth_request.as_bytes()).await; 129 | let _ = reader.read_line(&mut line).await; 130 | 131 | let parsed_response: Result = parse_response(&line); 132 | match parsed_response { 133 | Ok(response) => print_response(response), 134 | Err(error) => print_parser_error(&error) 135 | } 136 | */ -------------------------------------------------------------------------------- /cli-client/src/parser.rs: -------------------------------------------------------------------------------- 1 | use log::debug; 2 | 3 | 4 | #[derive(Debug, PartialEq)] 5 | pub enum ResponseStatus { 6 | OK, 7 | WARN, 8 | ERROR 9 | } 10 | 11 | 12 | #[derive(Debug, PartialEq)] 13 | pub struct ParsedResponse { 14 | pub status: ResponseStatus, 15 | pub command: Option, 16 | pub value: Option, 17 | } 18 | 19 | /// Splits a string at a delimiter, unless the delimiter is in a substring enclosed by quotes. 20 | /// 21 | /// # Arguments: 22 | /// * `string`: The string to split. 23 | /// * `delimiter`: The delimiter on which to split. 24 | /// 25 | /// # Returns: 26 | /// A vector containing the parts of the string (with quotes removed). 27 | fn split_at_delimiter(string: &str, delimiter: char) -> Vec<&str> { 28 | let mut parts: Vec<&str> = Vec::new(); 29 | let mut current_part = 0; 30 | let mut inside_quotes = false; 31 | let mut prev_char: Option = None; 32 | 33 | for (index, ch) in string.char_indices() { 34 | match ch { 35 | '"' => { 36 | if prev_char != Some('\\') { 37 | inside_quotes = !inside_quotes; 38 | } 39 | } 40 | ch if ch == delimiter && !inside_quotes => { 41 | let part = &string[current_part..index]; 42 | parts.push(part.trim_matches(|c| c == ' ')); 43 | current_part = index + ch.len_utf8(); 44 | } 45 | _ => {} 46 | } 47 | prev_char = Some(ch); 48 | } 49 | 50 | let part = &string[current_part..]; 51 | parts.push(part.trim_matches(|c| c == ' ')); 52 | 53 | parts 54 | } 55 | 56 | 57 | /// Don't trust the server... 58 | pub fn parse_response(response: &str) -> Result { 59 | const CASP_PREFIX: &str = "CASP"; 60 | const CASP_SUFFIX: &str = "\n"; 61 | const OK_IDENTIFIER: &str = "OK"; 62 | const WARN_IDENTIFIER: &str = "WARN"; 63 | const ERROR_IDENTIFIER: &str = "ERROR"; 64 | 65 | // split response parts at delimiter "/" 66 | 67 | 68 | // this doesnt work... 69 | let response_parts: Vec<&str> = split_at_delimiter(&response, '/'); 70 | 71 | if response_parts.len() == 1 && response_parts[0] == "" { 72 | return Err("Failed to parse response: Received empty response.".to_string()); 73 | } 74 | 75 | // check if the 'CASP' prefix exists 76 | if response_parts[0] != CASP_PREFIX { 77 | return Err("Failed to parse response: Prefix 'CASP' not found.".to_string()); 78 | } 79 | 80 | // check if the '\n' suffix exists 81 | if response_parts.last().unwrap() != &CASP_SUFFIX { 82 | return Err(r#"Failed to parse response: Suffix '\n' not found."#.to_string()); 83 | } 84 | 85 | // check if the response is OK, ERROR or something invalid 86 | match response_parts[1] { 87 | OK_IDENTIFIER => { 88 | if response_parts[2].starts_with("GET") { 89 | if response_parts.len() != 6 { 90 | return Err(r#"Failed to parse response: Expected GET OK responses to consist of six parts (CASP + OK + + + + \n)."#.to_string()); 91 | } 92 | 93 | //let escaped_response = response_parts[4].replace("\"", "\\\"").to_string().replace("\\\"", "\""); 94 | 95 | 96 | Ok(ParsedResponse { 97 | status: ResponseStatus::OK, 98 | command: Some(response_parts[2].to_string()), 99 | value: Some(response_parts[4].to_string()), 100 | }) 101 | } 102 | else if response_parts[2].starts_with("EXISTS") || 103 | response_parts[2].starts_with("PING") || 104 | response_parts[2].starts_with("LEN") { 105 | if response_parts.len() != 5 { 106 | return Err(r#"Failed to parse response: Expected EXISTS, PING, LEN OK responses to consist of five parts (CASP + OK + + + \n)."#.to_string()); 107 | } 108 | 109 | Ok(ParsedResponse { 110 | status: ResponseStatus::OK, 111 | command: Some(response_parts[2].to_string()), 112 | value: Some(response_parts[3].to_string()), 113 | }) 114 | } 115 | else { 116 | if response_parts.len() != 4 { 117 | return Err(r#"Failed to parse response: Expected OK responses to consist of four parts (CASP + OK + + \n)."#.to_string()); 118 | } 119 | 120 | Ok(ParsedResponse { 121 | status: ResponseStatus::OK, 122 | command: Some(response_parts[2].to_string()), 123 | value: None, 124 | }) 125 | } 126 | }, 127 | WARN_IDENTIFIER => { 128 | if response_parts.len() != 4 { 129 | return Err(r#"Failed to parse response: Expected WARN responses to consist of four parts (CASP + WARN + + \n)."#.to_string()); 130 | } 131 | 132 | Ok(ParsedResponse { 133 | status: ResponseStatus::WARN, 134 | command: Some(response_parts[2].to_string()), 135 | value: None, 136 | }) 137 | }, 138 | ERROR_IDENTIFIER => { 139 | if response_parts.len() != 4 { 140 | return Err(r#"Failed to parse response: Expected ERROR responses to consist of four parts (CASP + ERROR + message + \n)."#.to_string()); 141 | } 142 | 143 | Ok(ParsedResponse { 144 | status: ResponseStatus::ERROR, 145 | command: None, 146 | value: Some(response_parts[2].to_string()), 147 | }) 148 | }, 149 | _ => { 150 | Err("Failed to parse response: No status identifier found (expected one of: OK, ERROR).".to_string()) 151 | } 152 | } 153 | } 154 | 155 | 156 | 157 | #[cfg(test)] 158 | mod tests { 159 | use super::*; 160 | 161 | #[test] 162 | fn test_split_slash_delimiter() { 163 | 164 | let split_string: Vec<&str> = split_at_delimiter("CASP/OK/GET/STR/\"oh/no\"/\n", '/'); 165 | assert_eq!(split_string, vec!["CASP", "OK", "GET", "STR", "\"oh/no\"", "\n"]); 166 | 167 | let split_string: Vec<&str> = split_at_delimiter("\"\"", '/'); 168 | assert_eq!(split_string, vec!["\"\""]); 169 | 170 | let string = "CASP/OK/GET/INT/key \"value/1\"/\n"; 171 | let split_string: Vec<&str> = split_at_delimiter(&string, '/'); 172 | assert_eq!(split_string, vec!["CASP", "OK", "GET", "INT", "key \"value/1\"", "\n"]); 173 | 174 | let string = "\"A/B/C//D\""; 175 | let split_string: Vec<&str> = split_at_delimiter(&string, '/'); 176 | assert_eq!(split_string, vec!["\"A/B/C//D\""]); 177 | 178 | let string = "CASP/OK/GET/STR/\"sdf\\\"sdf\"/\n"; 179 | let split_string: Vec<&str> = split_at_delimiter(&string, '/'); 180 | assert_eq!(split_string, vec!["CASP", "OK", "GET", "STR", "\"sdf\\\"sdf\"", "\n"]); 181 | } 182 | 183 | #[test] 184 | fn test_parse_response() { 185 | // test success cases 186 | 187 | let parsed_response = parse_response("CASP/OK/SET/\n"); 188 | assert_eq!(parsed_response, Ok(ParsedResponse { status: ResponseStatus::OK, command: Some("SET".to_string()), value: None })); 189 | 190 | let parsed_response = parse_response("CASP/OK/GET MANY/INT/10,20,30/\n"); 191 | assert_eq!(parsed_response, Ok(ParsedResponse { 192 | status: ResponseStatus::OK, command: Some("GET MANY".to_string()), value: Some("10,20,30".to_string()) 193 | })); 194 | 195 | let parsed_response = parse_response("CASP/OK/LEN/10/\n"); 196 | assert_eq!(parsed_response, Ok(ParsedResponse { status: ResponseStatus::OK, command: Some("LEN".to_string()), value: Some("10".to_string()) })); 197 | 198 | let parsed_response = parse_response("CASP/ERROR/An error appeared./\n"); 199 | assert_eq!(parsed_response, Ok(ParsedResponse { status: ResponseStatus::ERROR, command: None, value: Some("An error appeared.".to_string()) })); 200 | 201 | let parsed_response = parse_response("CASP/WARN/SHUTDOWN/\n"); 202 | assert_eq!(parsed_response, Ok(ParsedResponse { status: ResponseStatus::WARN, command: Some("SHUTDOWN".to_string()), value: None })); 203 | 204 | // test failures 205 | let parsed_response = parse_response(""); 206 | assert_eq!(parsed_response.unwrap_err(), "Failed to parse response: Received empty response."); 207 | 208 | let parsed_response = parse_response("OK/SET/\n"); 209 | assert_eq!(parsed_response.unwrap_err(), "Failed to parse response: Prefix 'CASP' not found."); 210 | 211 | let parsed_response = parse_response("CA/SP/OK/SET/\n"); 212 | assert_eq!(parsed_response.unwrap_err(), "Failed to parse response: Prefix 'CASP' not found."); 213 | 214 | let parsed_response = parse_response("CASP/OK/GET MANY/1,2,3"); 215 | assert_eq!(parsed_response.unwrap_err(), r#"Failed to parse response: Suffix '\n' not found."#); 216 | 217 | let parsed_response = parse_response("CASP/SET/\n"); 218 | assert_eq!(parsed_response.unwrap_err(), "Failed to parse response: No status identifier found (expected one of: OK, ERROR)."); 219 | 220 | let parsed_response = parse_response("CASP/OK/SET/key/\n"); 221 | assert_eq!(parsed_response.unwrap_err(), r#"Failed to parse response: Expected OK responses to consist of four parts (CASP + OK + + \n)."#); 222 | 223 | let parsed_response = parse_response("CASP/OK/GET/\"value/1\"/\n"); 224 | assert_eq!(parsed_response.unwrap_err(), r#"Failed to parse response: Expected GET OK responses to consist of six parts (CASP + OK + + + + \n)."#); 225 | 226 | let parsed_response = parse_response("CASP/OK/EXISTS/\n"); 227 | assert_eq!(parsed_response.unwrap_err(), r#"Failed to parse response: Expected EXISTS, PING, LEN OK responses to consist of five parts (CASP + OK + + + \n)."#); 228 | 229 | let parsed_response = parse_response("CASP/ERROR/\n"); 230 | assert_eq!(parsed_response.unwrap_err(), r#"Failed to parse response: Expected ERROR responses to consist of four parts (CASP + ERROR + message + \n)."#); 231 | 232 | let parsed_response = parse_response("CASP/WARN/SHUTDOWN/Server shutting down!/\n"); 233 | assert_eq!(parsed_response.unwrap_err(), r#"Failed to parse response: Expected WARN responses to consist of four parts (CASP + WARN + + \n)."#); 234 | } 235 | } -------------------------------------------------------------------------------- /cli-client/test_client.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import time 3 | 4 | 5 | def build_request(message): 6 | return f"CASP/{message}/\n" 7 | 8 | 9 | def send_message(s, message, host, port): 10 | print("Sending:", message, end="") 11 | s.sendall(message.encode()) 12 | response = s.recv(1024).decode() 13 | print("Response:", response) 14 | 15 | if __name__ == "__main__": 16 | # Set the host and port to the server you want to send the message to 17 | host = '127.0.0.1' 18 | port = 8080 19 | 20 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 21 | s.connect((host, port)) 22 | 23 | # Message to send 24 | message = "Hello, server! This is a test message." 25 | 26 | send_message(s, build_request("AUTH Password123#"), host, port) 27 | send_message(s, build_request("SET key \"whut\"whut\""), host, port) 28 | send_message(s, build_request("GET key"), host, port) 29 | 30 | s.close() -------------------------------------------------------------------------------- /dashboard/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopfr/cachew-db/29fd2dd0a8369aeff9f656c103ea51be41cc99c5/dashboard/.gitkeep -------------------------------------------------------------------------------- /images/cachew-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopfr/cachew-db/29fd2dd0a8369aeff9f656c103ea51be41cc99c5/images/cachew-logo.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopfr/cachew-db/29fd2dd0a8369aeff9f656c103ea51be41cc99c5/images/logo.png -------------------------------------------------------------------------------- /python-client/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopfr/cachew-db/29fd2dd0a8369aeff9f656c103ea51be41cc99c5/python-client/.gitkeep --------------------------------------------------------------------------------