Fixes
parent: tbd commit: b87f0a3
Showing 14 changed files with 187 insertions and 56 deletions
Cargo.lock
@@ -265,6 +265,33 @@ dependencies = [ | ||
265 | 265 | ] |
266 | 266 | |
267 | 267 | [[package]] |
268 | name = "color-eyre" | |
269 | version = "0.6.2" | |
270 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
271 | checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" | |
272 | dependencies = [ | |
273 | "backtrace", | |
274 | "color-spantrace", | |
275 | "eyre", | |
276 | "indenter", | |
277 | "once_cell", | |
278 | "owo-colors", | |
279 | "tracing-error", | |
280 | ] | |
281 | ||
282 | [[package]] | |
283 | name = "color-spantrace" | |
284 | version = "0.2.0" | |
285 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
286 | checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" | |
287 | dependencies = [ | |
288 | "once_cell", | |
289 | "owo-colors", | |
290 | "tracing-core", | |
291 | "tracing-error", | |
292 | ] | |
293 | ||
294 | [[package]] | |
268 | 295 | name = "const-oid" |
269 | 296 | version = "0.9.5" |
270 | 297 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -510,6 +537,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||
510 | 537 | checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" |
511 | 538 | |
512 | 539 | [[package]] |
540 | name = "eyre" | |
541 | version = "0.6.8" | |
542 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
543 | checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" | |
544 | dependencies = [ | |
545 | "indenter", | |
546 | "once_cell", | |
547 | ] | |
548 | ||
549 | [[package]] | |
513 | 550 | name = "fastrand" |
514 | 551 | version = "2.0.0" |
515 | 552 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -701,6 +738,7 @@ dependencies = [ | ||
701 | 738 | "async-trait", |
702 | 739 | "bincode", |
703 | 740 | "chrono", |
741 | "color-eyre", | |
704 | 742 | "deadpool", |
705 | 743 | "futures-util", |
706 | 744 | "giterated-models", |
@@ -978,6 +1016,12 @@ dependencies = [ | ||
978 | 1016 | ] |
979 | 1017 | |
980 | 1018 | [[package]] |
1019 | name = "indenter" | |
1020 | version = "0.3.3" | |
1021 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
1022 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" | |
1023 | ||
1024 | [[package]] | |
981 | 1025 | name = "indexmap" |
982 | 1026 | version = "1.9.3" |
983 | 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1381,6 +1425,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1381 | 1425 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" |
1382 | 1426 | |
1383 | 1427 | [[package]] |
1428 | name = "owo-colors" | |
1429 | version = "3.5.0" | |
1430 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
1431 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" | |
1432 | ||
1433 | [[package]] | |
1384 | 1434 | name = "parking_lot" |
1385 | 1435 | version = "0.12.1" |
1386 | 1436 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -2514,6 +2564,16 @@ dependencies = [ | ||
2514 | 2564 | ] |
2515 | 2565 | |
2516 | 2566 | [[package]] |
2567 | name = "tracing-error" | |
2568 | version = "0.2.0" | |
2569 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
2570 | checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" | |
2571 | dependencies = [ | |
2572 | "tracing", | |
2573 | "tracing-subscriber", | |
2574 | ] | |
2575 | ||
2576 | [[package]] | |
2517 | 2577 | name = "tracing-log" |
2518 | 2578 | version = "0.1.3" |
2519 | 2579 | source = "registry+https://github.com/rust-lang/crates.io-index" |
giterated-daemon/src/cache_backend.rs
@@ -14,7 +14,8 @@ impl ObjectBackend for CacheBackend { | ||
14 | 14 | async fn object_operation<O: GiteratedObject + Debug, D: GiteratedOperation<O> + Debug>( |
15 | 15 | &self, |
16 | 16 | _object: O, |
17 | _operation: D, | |
17 | operation: &str, | |
18 | payload: D, | |
18 | 19 | ) -> Result<D::Success, OperationError<D::Failure>> { |
19 | 20 | // We don't handle operations with this backend |
20 | 21 | Err(OperationError::Unhandled) |
giterated-daemon/src/connection/wrapper.rs
@@ -83,10 +83,17 @@ pub async fn connection_wrapper( | ||
83 | 83 | |
84 | 84 | let message: GiteratedMessage<AnyObject, AnyOperation> = message.into_message(); |
85 | 85 | |
86 | backend | |
87 | .object_operation(message.object, message.payload) | |
88 | .await | |
89 | .unwrap(); | |
86 | let result = backend | |
87 | .object_operation(message.object, &message.operation, message.payload) | |
88 | .await; | |
89 | ||
90 | let mut socket = connection_state.socket.lock().await; | |
91 | let _ = socket | |
92 | .send(Message::Binary(bincode::serialize(&result).unwrap())) | |
93 | .await; | |
94 | ||
95 | info!("Done!"); | |
96 | drop(socket); | |
90 | 97 | } |
91 | 98 | _ => { |
92 | 99 | return; |
giterated-daemon/src/database_backend/handler.rs
@@ -2,12 +2,12 @@ use std::{collections::HashMap, pin::Pin, sync::Arc}; | ||
2 | 2 | |
3 | 3 | use futures_util::{future::BoxFuture, Future, FutureExt}; |
4 | 4 | use giterated_models::{ |
5 | error::{GetValueError, OperationError}, | |
5 | error::{GetValueError, OperationError, UserError}, | |
6 | 6 | object::{AnyObject, GiteratedObject}, |
7 | 7 | operation::{AnyOperation, GiteratedOperation}, |
8 | repository::Repository, | |
8 | repository::{Repository, RepositorySummary}, | |
9 | 9 | settings::{AnySetting, GetSetting, GetSettingError, SetSetting, SetSettingError}, |
10 | user::User, | |
10 | user::{User, UserRepositoriesRequest}, | |
11 | 11 | value::{AnyValue, GetValue}, |
12 | 12 | }; |
13 | 13 | |
@@ -120,6 +120,25 @@ impl<S: Send + Sync + Clone + 'static> OperationWrapper<S> { | ||
120 | 120 | } |
121 | 121 | } |
122 | 122 | |
123 | pub fn user_get_repositories( | |
124 | object: &User, | |
125 | operation: UserRepositoriesRequest, | |
126 | state: DatabaseBackend, | |
127 | ) -> BoxFuture<'static, Result<Vec<RepositorySummary>, OperationError<UserError>>> { | |
128 | let object = object.clone(); | |
129 | ||
130 | async move { | |
131 | let mut repositories_backend = state.repository_backend.lock().await; | |
132 | let repositories = repositories_backend | |
133 | .repositories_for_user(None, &object) | |
134 | .await | |
135 | .map_err(|e| OperationError::Internal(e.to_string()))?; | |
136 | ||
137 | Ok(repositories) | |
138 | } | |
139 | .boxed() | |
140 | } | |
141 | ||
123 | 142 | pub fn user_get_value( |
124 | 143 | object: &User, |
125 | 144 | operation: GetValue<AnyValue<User>>, |
giterated-daemon/src/database_backend/mod.rs
@@ -6,7 +6,7 @@ use std::{str::FromStr, sync::Arc}; | ||
6 | 6 | use giterated_models::error::OperationError; |
7 | 7 | use giterated_models::instance::Instance; |
8 | 8 | use giterated_models::object::{ |
9 | GiteratedObject, Object, ObjectRequest, ObjectRequestError, ObjectResponse, | |
9 | AnyObject, GiteratedObject, Object, ObjectRequest, ObjectRequestError, ObjectResponse, | |
10 | 10 | }; |
11 | 11 | use giterated_models::object_backend::ObjectBackend; |
12 | 12 | use giterated_models::operation::{AnyOperation, GiteratedOperation}; |
@@ -18,8 +18,8 @@ use tokio::sync::Mutex; | ||
18 | 18 | use crate::backend::{RepositoryBackend, UserBackend}; |
19 | 19 | |
20 | 20 | use self::handler::{ |
21 | repository_get_setting, repository_get_value, repository_set_setting, user_get_setting, | |
22 | user_get_value, user_set_setting, OperationHandlers, | |
21 | repository_get_setting, repository_get_value, repository_set_setting, user_get_repositories, | |
22 | user_get_setting, user_get_value, user_set_setting, OperationHandlers, | |
23 | 23 | }; |
24 | 24 | |
25 | 25 | #[derive(Clone, Debug)] |
@@ -30,7 +30,8 @@ impl ObjectBackend for Foobackend { | ||
30 | 30 | async fn object_operation<O: GiteratedObject + Debug, D: GiteratedOperation<O> + Debug>( |
31 | 31 | &self, |
32 | 32 | _object: O, |
33 | _operation: D, | |
33 | operation: &str, | |
34 | payload: D, | |
34 | 35 | ) -> Result<D::Success, OperationError<D::Failure>> { |
35 | 36 | // We don't handle operations with this backend |
36 | 37 | Err(OperationError::Unhandled) |
@@ -77,15 +78,17 @@ impl ObjectBackend for DatabaseBackend { | ||
77 | 78 | async fn object_operation<O: GiteratedObject + Debug, D: GiteratedOperation<O> + Debug>( |
78 | 79 | &self, |
79 | 80 | object: O, |
80 | operation: D, | |
81 | operation: &str, | |
82 | payload: D, | |
81 | 83 | ) -> Result<D::Success, OperationError<D::Failure>> { |
82 | 84 | let serialized = |
83 | serde_json::to_value(operation).map_err(|e| OperationError::Internal(e.to_string()))?; | |
85 | serde_json::to_value(payload).map_err(|e| OperationError::Internal(e.to_string()))?; | |
84 | 86 | let object = object.to_string(); |
85 | 87 | if let Ok(user) = User::from_str(&object) { |
86 | 88 | let mut handler = OperationHandlers::default(); |
87 | 89 | |
88 | 90 | handler |
91 | .insert(user_get_repositories) | |
89 | 92 | .insert(user_get_value) |
90 | 93 | .insert(user_get_setting) |
91 | 94 | .insert(user_set_setting); |
@@ -93,14 +96,16 @@ impl ObjectBackend for DatabaseBackend { | ||
93 | 96 | match handler |
94 | 97 | .handle( |
95 | 98 | &user, |
96 | D::operation_name(), | |
99 | operation, | |
97 | 100 | serde_json::from_value(serialized.clone()).unwrap(), |
98 | 101 | self.clone(), |
99 | 102 | ) |
100 | 103 | .await |
101 | 104 | { |
102 | Ok(result) => Ok(serde_json::from_slice(&result) | |
103 | .map_err(|e| OperationError::Internal(e.to_string()))?), | |
105 | Ok(result) => Ok( | |
106 | serde_json::from_slice(&serde_json::to_vec(&result).unwrap()) | |
107 | .map_err(|e| OperationError::Internal(e.to_string()))?, | |
108 | ), | |
104 | 109 | Err(err) => match err { |
105 | 110 | OperationError::Internal(internal) => Err(OperationError::Internal(internal)), |
106 | 111 | OperationError::Unhandled => Err(OperationError::Unhandled), |
@@ -121,14 +126,16 @@ impl ObjectBackend for DatabaseBackend { | ||
121 | 126 | match handler |
122 | 127 | .handle( |
123 | 128 | &repository, |
124 | D::operation_name(), | |
129 | operation, | |
125 | 130 | serde_json::from_value(serialized.clone()).unwrap(), |
126 | 131 | self.clone(), |
127 | 132 | ) |
128 | 133 | .await |
129 | 134 | { |
130 | Ok(result) => Ok(serde_json::from_slice(&result) | |
131 | .map_err(|e| OperationError::Internal(e.to_string()))?), | |
135 | Ok(result) => Ok( | |
136 | serde_json::from_slice(&serde_json::to_vec(&result).unwrap()) | |
137 | .map_err(|e| OperationError::Internal(e.to_string()))?, | |
138 | ), | |
132 | 139 | Err(err) => match err { |
133 | 140 | OperationError::Internal(internal) => Err(OperationError::Internal(internal)), |
134 | 141 | OperationError::Unhandled => Err(OperationError::Unhandled), |
@@ -146,18 +153,34 @@ impl ObjectBackend for DatabaseBackend { | ||
146 | 153 | |
147 | 154 | if let Ok(object_request) = serde_json::from_value::<ObjectRequest>(serialized.clone()) |
148 | 155 | { |
149 | // No-op | |
150 | let response = serde_json::to_string(&ObjectResponse(object_request.0)).unwrap(); | |
151 | ||
152 | info!("Target: {}", type_name::<D>()); | |
153 | info!("Meow: {}", response); | |
154 | info!( | |
155 | "Im just a neko! {:?}", | |
156 | serde_json::from_str::<AnyOperation>(&response) | |
157 | ); | |
158 | ||
159 | Ok(serde_json::from_str(&response) | |
160 | .map_err(|e| OperationError::Internal(e.to_string()))?) | |
156 | let result = self.get_object::<AnyObject>(&object_request.0).await; | |
157 | ||
158 | let result = result | |
159 | .map(|success| serde_json::to_vec(&success.object()).unwrap()) | |
160 | .map_err(|err| match err { | |
161 | OperationError::Operation(err) => { | |
162 | OperationError::Operation(serde_json::to_vec(&err).unwrap()) | |
163 | } | |
164 | OperationError::Internal(internal) => OperationError::Internal(internal), | |
165 | OperationError::Unhandled => OperationError::Unhandled, | |
166 | }); | |
167 | ||
168 | match result { | |
169 | Ok(result) => Ok( | |
170 | serde_json::from_slice(&serde_json::to_vec(&result).unwrap()) | |
171 | .map_err(|e| OperationError::Internal(e.to_string()))?, | |
172 | ), | |
173 | Err(err) => match err { | |
174 | OperationError::Internal(internal) => { | |
175 | Err(OperationError::Internal(internal)) | |
176 | } | |
177 | OperationError::Unhandled => Err(OperationError::Unhandled), | |
178 | OperationError::Operation(err) => Err(OperationError::Operation( | |
179 | serde_json::from_slice(&err) | |
180 | .map_err(|e| OperationError::Internal(e.to_string()))?, | |
181 | )), | |
182 | }, | |
183 | } | |
161 | 184 | } else { |
162 | 185 | Err(OperationError::Unhandled) |
163 | 186 | } |
giterated-models/src/error.rs
@@ -21,7 +21,7 @@ pub enum GetValueError {} | ||
21 | 21 | pub enum OperationError<B> { |
22 | 22 | #[error("the operation was handled but an error occured")] |
23 | 23 | Operation(#[from] B), |
24 | #[error("an internal error occured")] | |
24 | #[error("an internal error occured: {0}")] | |
25 | 25 | Internal(String), |
26 | 26 | #[error("the operation was unhandled or unrecognized")] |
27 | 27 | Unhandled, |
giterated-models/src/object.rs
@@ -38,6 +38,14 @@ impl<'b, B: ObjectBackend + Send + Sync + Clone, O: GiteratedObject> Object<'b, | ||
38 | 38 | } |
39 | 39 | } |
40 | 40 | |
41 | impl<O: GiteratedObject + Display, B: ObjectBackend + Send + Sync + Clone> Display | |
42 | for Object<'_, O, B> | |
43 | { | |
44 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
45 | self.inner.fmt(f) | |
46 | } | |
47 | } | |
48 | ||
41 | 49 | pub trait GiteratedObject: Send + Display + FromStr { |
42 | 50 | fn object_name() -> &'static str; |
43 | 51 | |
@@ -81,7 +89,7 @@ impl<'b, O: GiteratedObject + Clone + Debug, B: ObjectBackend> Object<'b, O, B> | ||
81 | 89 | request: R, |
82 | 90 | ) -> Result<R::Success, OperationError<R::Failure>> { |
83 | 91 | self.backend |
84 | .object_operation(self.inner.clone(), request) | |
92 | .object_operation(self.inner.clone(), R::operation_name(), request) | |
85 | 93 | .await |
86 | 94 | } |
87 | 95 | } |
giterated-models/src/object/operations.rs
@@ -25,6 +25,7 @@ pub enum ObjectRequestError { | ||
25 | 25 | } |
26 | 26 | |
27 | 27 | #[derive(Clone, Debug, Serialize, Deserialize)] |
28 | #[serde(transparent)] | |
28 | 29 | #[repr(transparent)] |
29 | 30 | pub struct AnyObject(pub String); |
30 | 31 |
giterated-models/src/object_backend.rs
@@ -11,7 +11,8 @@ pub trait ObjectBackend: Send + Sync + Sized + Clone { | ||
11 | 11 | async fn object_operation<O, D>( |
12 | 12 | &self, |
13 | 13 | object: O, |
14 | operation: D, | |
14 | operation: &str, | |
15 | payload: D, | |
15 | 16 | ) -> Result<D::Success, OperationError<D::Failure>> |
16 | 17 | where |
17 | 18 | O: GiteratedObject + Debug, |
giterated-models/src/repository/mod.rs
@@ -78,19 +78,13 @@ impl FromStr for Repository { | ||
78 | 78 | |
79 | 79 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
80 | 80 | let mut by_ampersand = s.split('@'); |
81 | let mut path_split = by_ampersand | |
82 | .next() | |
83 | .ok_or_else(|| RepositoryParseError)? | |
84 | .split('/'); | |
81 | let mut path_split = by_ampersand.next().ok_or(RepositoryParseError)?.split('/'); | |
85 | 82 | |
86 | let instance = Instance::from_str(by_ampersand.next().ok_or_else(|| RepositoryParseError)?) | |
83 | let instance = Instance::from_str(by_ampersand.next().ok_or(RepositoryParseError)?) | |
87 | 84 | .map_err(|_| RepositoryParseError)?; |
88 | let owner = User::from_str(path_split.next().ok_or_else(|| RepositoryParseError)?) | |
85 | let owner = User::from_str(path_split.next().ok_or(RepositoryParseError)?) | |
89 | 86 | .map_err(|_| RepositoryParseError)?; |
90 | let name = path_split | |
91 | .next() | |
92 | .ok_or_else(|| RepositoryParseError)? | |
93 | .to_string(); | |
87 | let name = path_split.next().ok_or(RepositoryParseError)?.to_string(); | |
94 | 88 | |
95 | 89 | Ok(Self { |
96 | 90 | instance, |
giterated-models/src/repository/operations.rs
@@ -7,7 +7,7 @@ use crate::{ | ||
7 | 7 | operation::GiteratedOperation, |
8 | 8 | }; |
9 | 9 | |
10 | use super::{IssueLabel, Repository, RepositoryIssue, RepositoryTreeEntry}; | |
10 | use super::{IssueLabel, Repository, RepositoryIssue, RepositoryTreeEntry, RepositoryView}; | |
11 | 11 | |
12 | 12 | /// A request to get a repository's information. |
13 | 13 | /// |
@@ -19,6 +19,14 @@ use super::{IssueLabel, Repository, RepositoryIssue, RepositoryTreeEntry}; | ||
19 | 19 | /// - User Authorization |
20 | 20 | /// - Potential User permissions checks |
21 | 21 | #[derive(Clone, Debug, Serialize, Deserialize)] |
22 | pub struct RepositoryInfoRequest; | |
23 | ||
24 | impl GiteratedOperation<Repository> for RepositoryInfoRequest { | |
25 | type Success = RepositoryView; | |
26 | type Failure = RepositoryError; | |
27 | } | |
28 | ||
29 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
22 | 30 | pub struct RepositoryIssuesCountRequest; |
23 | 31 | |
24 | 32 | impl GiteratedOperation<Repository> for RepositoryIssuesCountRequest { |
@@ -80,10 +88,14 @@ impl GiteratedOperation<Repository> for RepositoryFileInspectRequest { | ||
80 | 88 | } |
81 | 89 | |
82 | 90 | impl<B: ObjectBackend + std::fmt::Debug> Object<'_, Repository, B> { |
83 | pub async fn issues_count(&mut self) -> Result<u64, OperationError<RepositoryError>> { | |
84 | self.request::<RepositoryIssuesCountRequest>(RepositoryIssuesCountRequest) | |
91 | pub async fn info(&mut self) -> Result<RepositoryView, OperationError<RepositoryError>> { | |
92 | self.request::<RepositoryInfoRequest>(RepositoryInfoRequest) | |
85 | 93 | .await |
86 | 94 | } |
95 | // pub async fn issues_count(&mut self) -> Result<u64, OperationError<RepositoryError>> { | |
96 | // self.request::<RepositoryIssuesCountRequest>(RepositoryIssuesCountRequest) | |
97 | // .await | |
98 | // } | |
87 | 99 | |
88 | 100 | pub async fn issue_labels( |
89 | 101 | &mut self, |
giterated-models/src/user/mod.rs
@@ -72,11 +72,8 @@ impl FromStr for User { | ||
72 | 72 | } |
73 | 73 | |
74 | 74 | let mut colon_split = s.split(':'); |
75 | let username = colon_split | |
76 | .next() | |
77 | .ok_or_else(|| UserParseError)? | |
78 | .to_string(); | |
79 | let instance = Instance::from_str(colon_split.next().ok_or_else(|| UserParseError)?) | |
75 | let username = colon_split.next().ok_or(UserParseError)?.to_string(); | |
76 | let instance = Instance::from_str(colon_split.next().ok_or(UserParseError)?) | |
80 | 77 | .map_err(|_| UserParseError)?; |
81 | 78 | |
82 | 79 | Ok(Self { username, instance }) |
giterated-models/src/user/operations.rs
@@ -6,7 +6,7 @@ use crate::{ | ||
6 | 6 | object::Object, |
7 | 7 | object_backend::ObjectBackend, |
8 | 8 | operation::GiteratedOperation, |
9 | repository::Repository, | |
9 | repository::RepositorySummary, | |
10 | 10 | }; |
11 | 11 | |
12 | 12 | use super::User; |
@@ -18,7 +18,7 @@ pub struct UserRepositoriesRequest { | ||
18 | 18 | } |
19 | 19 | |
20 | 20 | impl GiteratedOperation<User> for UserRepositoriesRequest { |
21 | type Success = Vec<Repository>; | |
21 | type Success = Vec<RepositorySummary>; | |
22 | 22 | type Failure = UserError; |
23 | 23 | } |
24 | 24 | |
@@ -26,7 +26,7 @@ impl<B: ObjectBackend + std::fmt::Debug> Object<'_, User, B> { | ||
26 | 26 | pub async fn repositories( |
27 | 27 | &mut self, |
28 | 28 | instance: &Instance, |
29 | ) -> Result<Vec<Repository>, OperationError<UserError>> { | |
29 | ) -> Result<Vec<RepositorySummary>, OperationError<UserError>> { | |
30 | 30 | self.request::<UserRepositoriesRequest>(UserRepositoriesRequest { |
31 | 31 | instance: instance.clone(), |
32 | 32 | user: self.inner.clone(), |
giterated-models/src/user/values.rs
@@ -1,3 +1,5 @@ | ||
1 | use std::fmt::Display; | |
2 | ||
1 | 3 | use serde::{Deserialize, Serialize}; |
2 | 4 | |
3 | 5 | use crate::value::GiteratedObjectValue; |
@@ -18,6 +20,12 @@ impl GiteratedObjectValue for Bio { | ||
18 | 20 | #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] |
19 | 21 | pub struct DisplayName(pub String); |
20 | 22 | |
23 | impl Display for DisplayName { | |
24 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
25 | f.write_str(&self.0) | |
26 | } | |
27 | } | |
28 | ||
21 | 29 | impl GiteratedObjectValue for DisplayName { |
22 | 30 | type Object = User; |
23 | 31 |