lance-context 0.3.3__tar.gz → 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. {lance_context-0.3.3 → lance_context-0.4.1}/Cargo.toml +1 -1
  2. {lance_context-0.3.3 → lance_context-0.4.1}/PKG-INFO +6 -3
  3. {lance_context-0.3.3 → lance_context-0.4.1}/crates/lance-context-api/Cargo.toml +1 -1
  4. {lance_context-0.3.3 → lance_context-0.4.1}/crates/lance-context-api/src/lib.rs +250 -2
  5. lance_context-0.4.1/crates/lance-context-client/Cargo.toml +16 -0
  6. lance_context-0.4.1/crates/lance-context-client/src/error.rs +12 -0
  7. lance_context-0.4.1/crates/lance-context-client/src/lib.rs +567 -0
  8. {lance_context-0.3.3 → lance_context-0.4.1}/crates/lance-context-core/Cargo.toml +2 -2
  9. lance_context-0.4.1/crates/lance-context-core/src/api_impl.rs +449 -0
  10. {lance_context-0.3.3 → lance_context-0.4.1}/crates/lance-context-core/src/lib.rs +7 -3
  11. lance_context-0.4.1/crates/lance-context-core/src/namespace.rs +550 -0
  12. lance_context-0.4.1/crates/lance-context-core/src/record.rs +487 -0
  13. {lance_context-0.3.3 → lance_context-0.4.1}/crates/lance-context-core/src/store.rs +1591 -64
  14. {lance_context-0.3.3 → lance_context-0.4.1}/pyproject.toml +4 -3
  15. {lance_context-0.3.3 → lance_context-0.4.1}/python/Cargo.toml +3 -1
  16. lance_context-0.4.1/python/lance_context/__init__.py +19 -0
  17. lance_context-0.4.1/python/lance_context/api.py +1693 -0
  18. lance_context-0.4.1/python/lance_context/embeddings.py +115 -0
  19. lance_context-0.4.1/python/src/lib.rs +1941 -0
  20. {lance_context-0.3.3 → lance_context-0.4.1}/python/tests/test_async.py +19 -0
  21. {lance_context-0.3.3 → lance_context-0.4.1}/python/tests/test_compaction.py +8 -7
  22. lance_context-0.4.1/python/tests/test_deferred_embedding.py +106 -0
  23. lance_context-0.4.1/python/tests/test_distance_metric.py +77 -0
  24. lance_context-0.4.1/python/tests/test_embeddings.py +306 -0
  25. {lance_context-0.3.3 → lance_context-0.4.1}/python/tests/test_packaging_metadata.py +16 -0
  26. {lance_context-0.3.3 → lance_context-0.4.1}/python/tests/test_persistence.py +209 -3
  27. {lance_context-0.3.3 → lance_context-0.4.1}/python/tests/test_search.py +625 -24
  28. lance_context-0.4.1/python/tests/test_state_metadata.py +59 -0
  29. {lance_context-0.3.3 → lance_context-0.4.1}/python/uv.lock +599 -8
  30. lance_context-0.3.3/crates/lance-context-core/src/api_impl.rs +0 -180
  31. lance_context-0.3.3/crates/lance-context-core/src/record.rs +0 -277
  32. lance_context-0.3.3/python/lance_context/__init__.py +0 -9
  33. lance_context-0.3.3/python/lance_context/api.py +0 -801
  34. lance_context-0.3.3/python/src/lib.rs +0 -857
  35. {lance_context-0.3.3 → lance_context-0.4.1}/LICENSE +0 -0
  36. {lance_context-0.3.3 → lance_context-0.4.1}/README.md +0 -0
  37. {lance_context-0.3.3 → lance_context-0.4.1}/crates/lance-context-core/README.md +0 -0
  38. {lance_context-0.3.3 → lance_context-0.4.1}/crates/lance-context-core/src/context.rs +0 -0
  39. {lance_context-0.3.3 → lance_context-0.4.1}/crates/lance-context-core/src/serde.rs +0 -0
  40. {lance_context-0.3.3 → lance_context-0.4.1}/python/Cargo.lock +0 -0
  41. {lance_context-0.3.3 → lance_context-0.4.1}/python/LICENSE +0 -0
  42. {lance_context-0.3.3 → lance_context-0.4.1}/python/README.md +0 -0
  43. {lance_context-0.3.3 → lance_context-0.4.1}/python/python/tests/test_context.py +0 -0
  44. {lance_context-0.3.3 → lance_context-0.4.1}/python/tests/test_add_many.py +0 -0
  45. {lance_context-0.3.3 → lance_context-0.4.1}/python/tests/test_delete.py +0 -0
  46. {lance_context-0.3.3 → lance_context-0.4.1}/python/tests/test_external_id.py +0 -0
  47. {lance_context-0.3.3 → lance_context-0.4.1}/python/tests/test_gcs_persistence.py +0 -0
  48. {lance_context-0.3.3 → lance_context-0.4.1}/python/tests/test_id_index.py +0 -0
  49. {lance_context-0.3.3 → lance_context-0.4.1}/python/tests/test_storage_options.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [workspace]
2
- members = ["crates/lance-context-core", "crates/lance-context-api", "python"]
2
+ members = ["crates/lance-context-core", "crates/lance-context-api", "crates/lance-context-client", "python"]
3
3
  resolver = "2"
4
4
 
5
5
  [workspace.package]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lance-context
3
- Version: 0.3.3
3
+ Version: 0.4.1
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Science/Research
6
6
  Classifier: License :: OSI Approved :: Apache Software License
@@ -8,10 +8,9 @@ Classifier: Operating System :: OS Independent
8
8
  Classifier: Programming Language :: Python
9
9
  Classifier: Programming Language :: Python :: 3
10
10
  Classifier: Programming Language :: Python :: 3 :: Only
11
- Classifier: Programming Language :: Python :: 3.9
12
- Classifier: Programming Language :: Python :: 3.10
13
11
  Classifier: Programming Language :: Python :: 3.11
14
12
  Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
15
14
  Classifier: Programming Language :: Rust
16
15
  Classifier: Topic :: Scientific/Engineering
17
16
  Requires-Dist: pyarrow>=14
@@ -21,6 +20,8 @@ Requires-Dist: lance-graph==0.5.4 ; extra == 'graph'
21
20
  Requires-Dist: pylance>=7.0.0,<8 ; extra == 'lance-python'
22
21
  Requires-Dist: lance-namespace>=0.7.7,<0.8 ; extra == 'lance-python'
23
22
  Requires-Dist: lancedb==0.27.1 ; extra == 'lance-python'
23
+ Requires-Dist: openai>=1.0 ; extra == 'openai'
24
+ Requires-Dist: sentence-transformers>=2.0 ; extra == 'sentence-transformers'
24
25
  Requires-Dist: pytest ; extra == 'tests'
25
26
  Requires-Dist: pytest-asyncio ; extra == 'tests'
26
27
  Requires-Dist: ruff ; extra == 'tests'
@@ -33,6 +34,8 @@ Requires-Dist: lancedb==0.27.1 ; extra == 'tests'
33
34
  Provides-Extra: dev
34
35
  Provides-Extra: graph
35
36
  Provides-Extra: lance-python
37
+ Provides-Extra: openai
38
+ Provides-Extra: sentence-transformers
36
39
  Provides-Extra: tests
37
40
  License-File: LICENSE
38
41
  Summary: Multimodal, versioned context storage for agentic workflows
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "lance-context-api"
3
- version = "0.2.4"
3
+ version = "0.4.1"
4
4
  edition = "2021"
5
5
  license = "Apache-2.0"
6
6
  authors = ["Lance Devs <dev@lancedb.com>"]
@@ -34,20 +34,61 @@ pub trait ContextStoreApi {
34
34
  records: &[AddRecordRequest],
35
35
  ) -> impl Future<Output = ContextResult<AddRecordsResponse>> + Send;
36
36
 
37
+ fn upsert(
38
+ &mut self,
39
+ request: &UpsertRecordRequest,
40
+ ) -> impl Future<Output = ContextResult<UpsertRecordResponse>> + Send;
41
+
42
+ fn update(
43
+ &mut self,
44
+ request: &UpdateRecordRequest,
45
+ ) -> impl Future<Output = ContextResult<UpdateRecordResponse>> + Send;
46
+
37
47
  fn get(&self, id: &str) -> impl Future<Output = ContextResult<Option<RecordDto>>> + Send;
38
48
 
49
+ fn get_by_external_id(
50
+ &self,
51
+ external_id: &str,
52
+ ) -> impl Future<Output = ContextResult<Option<RecordDto>>> + Send;
53
+
54
+ fn delete_by_id(
55
+ &mut self,
56
+ id: &str,
57
+ ) -> impl Future<Output = ContextResult<DeleteRecordResponse>> + Send;
58
+
59
+ fn delete_by_external_id(
60
+ &mut self,
61
+ external_id: &str,
62
+ ) -> impl Future<Output = ContextResult<DeleteRecordResponse>> + Send;
63
+
39
64
  fn list(
40
65
  &self,
41
66
  limit: Option<usize>,
42
67
  offset: Option<usize>,
68
+ filters: Option<Value>,
69
+ include_expired: bool,
70
+ include_retired: bool,
43
71
  ) -> impl Future<Output = ContextResult<Vec<RecordDto>>> + Send;
44
72
 
45
- fn search(
73
+ fn related(
46
74
  &self,
47
- query: &[f32],
75
+ target_id: &str,
76
+ relation: Option<&str>,
48
77
  limit: Option<usize>,
78
+ include_expired: bool,
79
+ include_retired: bool,
80
+ ) -> impl Future<Output = ContextResult<Vec<RecordDto>>> + Send;
81
+
82
+ fn search(
83
+ &self,
84
+ request: &SearchRequest,
49
85
  ) -> impl Future<Output = ContextResult<Vec<SearchResultDto>>> + Send;
50
86
 
87
+ fn retrieve(
88
+ &self,
89
+ request: &RetrieveRequest,
90
+ ) -> impl Future<Output = ContextResult<Vec<RetrieveResultDto>>> + Send;
91
+
51
92
  fn version(&self) -> u64;
52
93
 
53
94
  fn checkout(&mut self, version: u64) -> impl Future<Output = ContextResult<()>> + Send;
@@ -73,6 +114,10 @@ pub struct CreateContextRequest {
73
114
  pub id_index_type: Option<String>,
74
115
  #[serde(default)]
75
116
  pub blob_columns: Option<Vec<String>>,
117
+ #[serde(default)]
118
+ pub embedding_dim: Option<i32>,
119
+ #[serde(default)]
120
+ pub distance_metric: Option<String>,
76
121
  }
77
122
 
78
123
  #[derive(Debug, Serialize, Deserialize)]
@@ -103,6 +148,14 @@ pub struct StateMetadataDto {
103
148
  pub custom: Option<String>,
104
149
  }
105
150
 
151
+ #[derive(Debug, Clone, Default, Serialize, Deserialize)]
152
+ pub struct RelationshipDto {
153
+ pub target_id: String,
154
+ pub relation: String,
155
+ #[serde(default, skip_serializing_if = "Option::is_none")]
156
+ pub weight: Option<f32>,
157
+ }
158
+
106
159
  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
107
160
  pub struct AddRecordRequest {
108
161
  #[serde(default = "default_role")]
@@ -125,11 +178,17 @@ pub struct AddRecordRequest {
125
178
  #[serde(default, skip_serializing_if = "Option::is_none")]
126
179
  pub session_id: Option<String>,
127
180
  #[serde(default, skip_serializing_if = "Option::is_none")]
181
+ pub tenant: Option<String>,
182
+ #[serde(default, skip_serializing_if = "Option::is_none")]
183
+ pub source: Option<String>,
184
+ #[serde(default, skip_serializing_if = "Option::is_none")]
128
185
  pub external_id: Option<String>,
129
186
  #[serde(default, skip_serializing_if = "Option::is_none")]
130
187
  pub state_metadata: Option<StateMetadataDto>,
131
188
  #[serde(default, skip_serializing_if = "Option::is_none")]
132
189
  pub metadata: Option<Value>,
190
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
191
+ pub relationships: Vec<RelationshipDto>,
133
192
  #[serde(default, skip_serializing_if = "Option::is_none")]
134
193
  pub expires_at: Option<DateTime<Utc>>,
135
194
  #[serde(default, skip_serializing_if = "Option::is_none")]
@@ -150,6 +209,91 @@ pub struct AddRecordsResponse {
150
209
  pub count: usize,
151
210
  }
152
211
 
212
+ #[derive(Debug, Serialize, Deserialize)]
213
+ pub struct UpsertRecordRequest {
214
+ pub record: AddRecordRequest,
215
+ #[serde(default = "default_upsert_key")]
216
+ pub key: String,
217
+ }
218
+
219
+ #[derive(Debug, Serialize, Deserialize)]
220
+ pub struct UpsertRecordResponse {
221
+ pub version: u64,
222
+ pub inserted: bool,
223
+ #[serde(default, skip_serializing_if = "Option::is_none")]
224
+ pub replaced_id: Option<String>,
225
+ pub record: RecordDto,
226
+ }
227
+
228
+ #[derive(Debug, Clone, Default, Serialize, Deserialize)]
229
+ pub struct RecordPatchDto {
230
+ #[serde(default, skip_serializing_if = "Option::is_none")]
231
+ pub bot_id: Option<String>,
232
+ #[serde(default, skip_serializing_if = "Option::is_none")]
233
+ pub session_id: Option<String>,
234
+ #[serde(default, skip_serializing_if = "Option::is_none")]
235
+ pub tenant: Option<String>,
236
+ #[serde(default, skip_serializing_if = "Option::is_none")]
237
+ pub source: Option<String>,
238
+ #[serde(default, skip_serializing_if = "Option::is_none")]
239
+ pub state_metadata: Option<StateMetadataDto>,
240
+ #[serde(default, skip_serializing_if = "Option::is_none")]
241
+ pub metadata: Option<Value>,
242
+ #[serde(default, skip_serializing_if = "Option::is_none")]
243
+ pub relationships: Option<Vec<RelationshipDto>>,
244
+ #[serde(default, skip_serializing_if = "Option::is_none")]
245
+ pub expires_at: Option<DateTime<Utc>>,
246
+ #[serde(default, skip_serializing_if = "Option::is_none")]
247
+ pub retention_policy: Option<String>,
248
+ #[serde(default, skip_serializing_if = "Option::is_none")]
249
+ pub lifecycle_status: Option<String>,
250
+ #[serde(default, skip_serializing_if = "Option::is_none")]
251
+ pub retired_at: Option<DateTime<Utc>>,
252
+ #[serde(default, skip_serializing_if = "Option::is_none")]
253
+ pub retired_reason: Option<String>,
254
+ #[serde(default, skip_serializing_if = "Option::is_none")]
255
+ pub embedding: Option<Vec<f32>>,
256
+ }
257
+
258
+ impl RecordPatchDto {
259
+ #[must_use]
260
+ pub fn is_empty(&self) -> bool {
261
+ self.bot_id.is_none()
262
+ && self.session_id.is_none()
263
+ && self.tenant.is_none()
264
+ && self.source.is_none()
265
+ && self.state_metadata.is_none()
266
+ && self.metadata.is_none()
267
+ && self.relationships.is_none()
268
+ && self.expires_at.is_none()
269
+ && self.retention_policy.is_none()
270
+ && self.lifecycle_status.is_none()
271
+ && self.retired_at.is_none()
272
+ && self.retired_reason.is_none()
273
+ && self.embedding.is_none()
274
+ }
275
+ }
276
+
277
+ #[derive(Debug, Serialize, Deserialize)]
278
+ pub struct UpdateRecordRequest {
279
+ #[serde(default, skip_serializing_if = "Option::is_none")]
280
+ pub id: Option<String>,
281
+ #[serde(default, skip_serializing_if = "Option::is_none")]
282
+ pub external_id: Option<String>,
283
+ #[serde(default)]
284
+ pub patch: RecordPatchDto,
285
+ }
286
+
287
+ #[derive(Debug, Serialize, Deserialize)]
288
+ pub struct UpdateRecordResponse {
289
+ pub version: u64,
290
+ pub updated: bool,
291
+ #[serde(default, skip_serializing_if = "Option::is_none")]
292
+ pub replaced_id: Option<String>,
293
+ #[serde(default, skip_serializing_if = "Option::is_none")]
294
+ pub record: Option<RecordDto>,
295
+ }
296
+
153
297
  #[derive(Debug, Clone, Serialize, Deserialize)]
154
298
  pub struct RecordDto {
155
299
  pub id: String,
@@ -160,6 +304,10 @@ pub struct RecordDto {
160
304
  pub bot_id: Option<String>,
161
305
  #[serde(default, skip_serializing_if = "Option::is_none")]
162
306
  pub session_id: Option<String>,
307
+ #[serde(default, skip_serializing_if = "Option::is_none")]
308
+ pub tenant: Option<String>,
309
+ #[serde(default, skip_serializing_if = "Option::is_none")]
310
+ pub source: Option<String>,
163
311
  pub created_at: DateTime<Utc>,
164
312
  pub role: String,
165
313
  pub content_type: String,
@@ -178,6 +326,8 @@ pub struct RecordDto {
178
326
  pub state_metadata: Option<StateMetadataDto>,
179
327
  #[serde(default, skip_serializing_if = "Option::is_none")]
180
328
  pub metadata: Option<Value>,
329
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
330
+ pub relationships: Vec<RelationshipDto>,
181
331
  #[serde(default, skip_serializing_if = "Option::is_none")]
182
332
  pub expires_at: Option<DateTime<Utc>>,
183
333
  #[serde(default, skip_serializing_if = "Option::is_none")]
@@ -207,6 +357,12 @@ pub struct GetRecordResponse {
207
357
  pub record: Option<RecordDto>,
208
358
  }
209
359
 
360
+ #[derive(Debug, Serialize, Deserialize)]
361
+ pub struct DeleteRecordResponse {
362
+ pub deleted: bool,
363
+ pub version: u64,
364
+ }
365
+
210
366
  // ---------------------------------------------------------------------------
211
367
  // Search
212
368
  // ---------------------------------------------------------------------------
@@ -216,6 +372,14 @@ pub struct SearchRequest {
216
372
  pub query: Vec<f32>,
217
373
  #[serde(default = "default_search_limit")]
218
374
  pub limit: usize,
375
+ #[serde(default, skip_serializing_if = "Option::is_none")]
376
+ pub filters: Option<Value>,
377
+ #[serde(default)]
378
+ pub include_expired: bool,
379
+ #[serde(default)]
380
+ pub include_retired: bool,
381
+ #[serde(default)]
382
+ pub include_relationships: bool,
219
383
  }
220
384
 
221
385
  #[derive(Debug, Serialize, Deserialize)]
@@ -229,6 +393,47 @@ pub struct SearchResponse {
229
393
  pub results: Vec<SearchResultDto>,
230
394
  }
231
395
 
396
+ // ---------------------------------------------------------------------------
397
+ // Hybrid retrieval
398
+ // ---------------------------------------------------------------------------
399
+
400
+ #[derive(Debug, Serialize, Deserialize)]
401
+ pub struct RetrieveRequest {
402
+ #[serde(default, skip_serializing_if = "Option::is_none")]
403
+ pub text: Option<String>,
404
+ #[serde(default, skip_serializing_if = "Option::is_none")]
405
+ pub vector: Option<Vec<f32>>,
406
+ #[serde(default = "default_search_limit")]
407
+ pub limit: usize,
408
+ #[serde(default, skip_serializing_if = "Option::is_none")]
409
+ pub filters: Option<Value>,
410
+ #[serde(default)]
411
+ pub include_expired: bool,
412
+ #[serde(default)]
413
+ pub include_retired: bool,
414
+ #[serde(default)]
415
+ pub include_relationships: bool,
416
+ #[serde(default = "default_retrieve_fusion")]
417
+ pub fusion: String,
418
+ }
419
+
420
+ #[derive(Debug, Serialize, Deserialize)]
421
+ pub struct RetrieveResultDto {
422
+ pub record: RecordDto,
423
+ pub score: f32,
424
+ #[serde(default, skip_serializing_if = "Option::is_none")]
425
+ pub vector_distance: Option<f32>,
426
+ #[serde(default, skip_serializing_if = "Option::is_none")]
427
+ pub text_score: Option<f32>,
428
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
429
+ pub matched_channels: Vec<String>,
430
+ }
431
+
432
+ #[derive(Debug, Serialize, Deserialize)]
433
+ pub struct RetrieveResponse {
434
+ pub results: Vec<RetrieveResultDto>,
435
+ }
436
+
232
437
  // ---------------------------------------------------------------------------
233
438
  // Versioning
234
439
  // ---------------------------------------------------------------------------
@@ -301,10 +506,18 @@ fn default_role() -> String {
301
506
  "user".to_string()
302
507
  }
303
508
 
509
+ fn default_upsert_key() -> String {
510
+ "external_id".to_string()
511
+ }
512
+
304
513
  fn default_search_limit() -> usize {
305
514
  10
306
515
  }
307
516
 
517
+ fn default_retrieve_fusion() -> String {
518
+ "rrf".to_string()
519
+ }
520
+
308
521
  fn serialize_base64_opt<S>(data: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
309
522
  where
310
523
  S: serde::Serializer,
@@ -328,3 +541,38 @@ where
328
541
  None => Ok(None),
329
542
  }
330
543
  }
544
+
545
+ #[cfg(test)]
546
+ mod tests {
547
+ use super::*;
548
+
549
+ #[test]
550
+ fn search_request_legacy_payload_defaults_filters_and_lifecycle() {
551
+ // Clients written against the pre-#89 shape send only query/limit.
552
+ let req: SearchRequest =
553
+ serde_json::from_str(r#"{"query": [0.1, 0.2], "limit": 5}"#).unwrap();
554
+ assert_eq!(req.query, vec![0.1, 0.2]);
555
+ assert_eq!(req.limit, 5);
556
+ assert!(req.filters.is_none());
557
+ assert!(!req.include_expired);
558
+ assert!(!req.include_retired);
559
+ assert!(!req.include_relationships);
560
+ }
561
+
562
+ #[test]
563
+ fn search_request_defaults_limit_when_omitted() {
564
+ let req: SearchRequest = serde_json::from_str(r#"{"query": [1.0]}"#).unwrap();
565
+ assert_eq!(req.limit, default_search_limit());
566
+ }
567
+
568
+ #[test]
569
+ fn search_request_parses_filters_and_lifecycle() {
570
+ let req: SearchRequest = serde_json::from_str(
571
+ r#"{"query": [1.0], "filters": {"tenant": "acme"}, "include_expired": true, "include_retired": true}"#,
572
+ )
573
+ .unwrap();
574
+ assert_eq!(req.filters, Some(serde_json::json!({"tenant": "acme"})));
575
+ assert!(req.include_expired);
576
+ assert!(req.include_retired);
577
+ }
578
+ }
@@ -0,0 +1,16 @@
1
+ [package]
2
+ name = "lance-context-client"
3
+ version = "0.4.1"
4
+ edition = "2021"
5
+ license = "Apache-2.0"
6
+ authors = ["Lance Devs <dev@lancedb.com>"]
7
+ repository = "https://github.com/lancedb/lance-context"
8
+ description = "Rust client for the lance-context REST API"
9
+ keywords = ["context", "lance", "client", "api"]
10
+
11
+ [dependencies]
12
+ lance-context-api = { version = "0.4.1", path = "../lance-context-api" }
13
+ reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
14
+ serde = { version = "1", features = ["derive"] }
15
+ serde_json = "1"
16
+ thiserror = "2"
@@ -0,0 +1,12 @@
1
+ #[derive(Debug, thiserror::Error)]
2
+ pub enum ClientError {
3
+ #[error("HTTP error: {0}")]
4
+ Http(#[from] reqwest::Error),
5
+
6
+ #[error("API error ({status}): [{code}] {message}")]
7
+ Api {
8
+ status: u16,
9
+ code: String,
10
+ message: String,
11
+ },
12
+ }